[med-svn] [openfovea] 11/13: New upstream version 0.1a162
Andreas Tille
tille at debian.org
Tue Dec 26 19:57:38 UTC 2017
This is an automated email from the git hooks/post-receive script.
tille pushed a commit to branch master
in repository openfovea.
commit c6828dc5f9ceb34fef12d5f71ea73154f64c43d6
Author: Andreas Tille <tille at debian.org>
Date: Tue Dec 26 20:56:17 2017 +0100
New upstream version 0.1a162
---
PKG-INFO | 10 +
debian/changelog | 5 -
debian/compat | 1 -
debian/control | 27 -
debian/copyright | 31 -
debian/manpages | 1 -
debian/openfovea.1 | 54 -
debian/rules | 7 -
debian/source/format | 1 -
debian/watch | 3 -
openfovea.desktop | 10 +
openfovea/Icon/CellFovea.ico | Bin 0 -> 162408 bytes
openfovea/Icon/CellFovea.png | Bin 0 -> 28558 bytes
openfovea/Icon/CellFovea.svg | 3596 +++++++++++++++
openfovea/Icon/Export_curve.svg | 88 +
openfovea/Icon/NoData.png | Bin 0 -> 19043 bytes
openfovea/Icon/NoData.svg | 335 ++
openfovea/Icon/event.ico | Bin 0 -> 900 bytes
openfovea/Icon/event.png | Bin 0 -> 352 bytes
openfovea/Icon/event.svg | 72 +
openfovea/Icon/export.ico | Bin 0 -> 16958 bytes
openfovea/Icon/export.png | Bin 0 -> 5929 bytes
openfovea/Icon/export.svg | 375 ++
openfovea/Icon/export_curve.ico | Bin 0 -> 900 bytes
openfovea/Icon/export_curve.png | Bin 0 -> 361 bytes
openfovea/Icon/icon64x64.svg | 290 ++
openfovea/Icon/openfovea.ico | Bin 0 -> 16958 bytes
openfovea/Icon/openfovea.png | Bin 0 -> 5560 bytes
openfovea/Icon/tomo_x_square.svg | 187 +
openfovea/Icon/tomo_y_square.svg | 173 +
openfovea/Icon/tomo_z_square.svg | 183 +
openfovea/Icon/two_evt.ico | Bin 0 -> 900 bytes
openfovea/Icon/two_evt.png | Bin 0 -> 377 bytes
openfovea/Icon/two_evt.svg | 69 +
openfovea/__init__.py | 1 +
openfovea/classes.py | 2738 ++++++++++++
openfovea/common.py | 3 +
openfovea/dialog_experiment_properties.py | 267 ++
openfovea/dialog_export_map.py | 478 ++
openfovea/dialog_flatten.py | 93 +
openfovea/dialog_mask_properties.py | 294 ++
openfovea/fovea_toolbox/__init__.py | 1 +
openfovea/fovea_toolbox/curve.py | 1174 +++++
openfovea/fovea_toolbox/file_util/__init__.py | 0
openfovea/fovea_toolbox/file_util/aex.py | 713 +++
openfovea/fovea_toolbox/file_util/afm_file.py | 353 ++
openfovea/fovea_toolbox/file_util/asylum.py | 524 +++
openfovea/fovea_toolbox/file_util/common.py | 288 ++
openfovea/fovea_toolbox/file_util/csem.py | 82 +
openfovea/fovea_toolbox/file_util/jpk.py | 1376 ++++++
openfovea/fovea_toolbox/file_util/nanoscope.py | 305 ++
.../fovea_toolbox/file_util/succellus_import.py | 323 ++
openfovea/fovea_toolbox/misc.py | 360 ++
openfovea/fovea_toolbox/opengl.py | 689 +++
openfovea/fovea_toolbox/opengl_gtk.py | 115 +
openfovea/fovea_toolbox/post_proc.py | 585 +++
openfovea/glade/dialog_experiment_properties.glade | 771 ++++
openfovea/glade/dialog_flatten.glade | 142 +
openfovea/glade/openfovea.glade | 4628 ++++++++++++++++++++
openfovea/glade/plot.glade | 104 +
openfovea/glade/stiffness_plot.glade | 163 +
openfovea/openfovea_gui.py | 2999 +++++++++++++
openfovea/plot_generic.py | 2239 ++++++++++
openfovea/plot_gtk.py | 702 +++
openfovea/simple_window_gtk.py | 80 +
script/openfovea | 21 +
setup.py | 29 +
67 files changed, 28028 insertions(+), 130 deletions(-)
diff --git a/PKG-INFO b/PKG-INFO
new file mode 100644
index 0000000..8e9680f
--- /dev/null
+++ b/PKG-INFO
@@ -0,0 +1,10 @@
+Metadata-Version: 1.0
+Name: openfovea
+Version: 0.1a162
+Summary: Postprocessing of force volume data acquired with AFM./nOpenFovea is a software to postprocess AFM force volume/nexperiment. It is able to compute the stiffness of the/nscanned area, detect unbinding event and the zero force/ntopography. It comes with a user friendly graphical interface/nthat allow the user to select and display the data.
+Home-page: http://www.freesbi.ch/openfovea
+Author: Charles Roduit
+Author-email: charles.roduit at gmail.com
+License: GNU Public License v3
+Description: UNKNOWN
+Platform: UNKNOWN
diff --git a/debian/changelog b/debian/changelog
deleted file mode 100644
index a906d24..0000000
--- a/debian/changelog
+++ /dev/null
@@ -1,5 +0,0 @@
-openfovea (0.1a162-1) UNRELEASED; urgency=low
-
- * Initial release (Closes: #<bug>)
-
- -- Andreas Tille <tille at debian.org> Thu, 12 Jul 2012 13:57:08 +0200
diff --git a/debian/compat b/debian/compat
deleted file mode 100644
index f599e28..0000000
--- a/debian/compat
+++ /dev/null
@@ -1 +0,0 @@
-10
diff --git a/debian/control b/debian/control
deleted file mode 100644
index f6d117a..0000000
--- a/debian/control
+++ /dev/null
@@ -1,27 +0,0 @@
-Source: openfovea
-Maintainer: Debian Med Packaging Team <debian-med-packaging at lists.alioth.debian.org>
-Uploaders: Charles Roduit <charles.roduit at gmail.com>,
- Andreas Tille <tille at debian.org>
-Section: science
-Priority: optional
-Build-Depends: debhelper (>= 10),
- python
-Standards-Version: 3.9.8
-Vcs-Browser: https://anonscm.debian.org/viewvc/debian-med/trunk/packages/openfovea/trunk/
-Vcs-Svn: svn://anonscm.debian.org/debian-med/trunk/packages/openfovea/trunk/
-Homepage: http://www.freesbi.ch/en/openfovea
-
-Package: openfovea
-Architecture: all
-Depends: ${misc:Depends},
- python-numpy,
- python-scipy,
- python-matplotlib,
- python-gtk2,
- python-gtkglext1,
- ${python:Depends}
-Description: Postprocessing of force volume data acquired with AFM.
- OpenFovea is a software to postprocess AFM force volume experiment.
- It is able to compute the stiffness of the scanned area, detect unbinding event
- and the zero force topography. It comes with a user friendly graphical
- interface that allow the user to select and display the data.
diff --git a/debian/copyright b/debian/copyright
deleted file mode 100644
index c958b13..0000000
--- a/debian/copyright
+++ /dev/null
@@ -1,31 +0,0 @@
-Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
-Upstream-Name: OpenFovea
-Upstream-Contact: Charles Roduit <charles.roduit at gmail.com>
-Source: https://launchpad.net/openfovea/+download
-
-Files: *
-Copyright: © 2008-2012 Charles Roduit <charles.roduit at gmail.com>
-License: GPL-3+
-
-Files: debian/*
-Copyright: © 2008-2012 Charles Roduit <charles.roduit at gmail.com>
- 2012 Andreas Tille <tille at debian.org>
-License: GPL-3+
-
-License: GPL-3+
- This package is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License, or
- (at your option) any later version.
- .
- This package is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- .
- You should have received a copy of the GNU General Public License
- along with this package; if not, write to the Free Software
- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
- .
- On Debian systems, the complete text of the GNU General
- Public License can be found in `/usr/share/common-licenses/GPL-3'.
diff --git a/debian/manpages b/debian/manpages
deleted file mode 100644
index 0f65186..0000000
--- a/debian/manpages
+++ /dev/null
@@ -1 +0,0 @@
-debian/*.1
diff --git a/debian/openfovea.1 b/debian/openfovea.1
deleted file mode 100644
index 8c8c1e2..0000000
--- a/debian/openfovea.1
+++ /dev/null
@@ -1,54 +0,0 @@
-.\" Hey, EMACS: -*- nroff -*-
-.\" First parameter, NAME, should be all caps
-.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection
-.\" other parameters are allowed: see man(7), man(1)
-.TH OPENFOVEA 1 "August 21, 2012"
-.\" Please adjust this date whenever revising the manpage.
-.\"
-.\" Some roff macros, for reference:
-.\" .nh disable hyphenation
-.\" .hy enable hyphenation
-.\" .ad l left justify
-.\" .ad b justify to both left and right margins
-.\" .nf disable filling
-.\" .fi enable filling
-.\" .br insert line break
-.\" .sp <n> insert n+1 empty lines
-.\" for manpage-specific macros, see man(7)
-.SH NAME
-openfovea \- Post-process your AFM data files.
-.SH SYNOPSIS
-.B openfovea
-.RI [ options ] " files" ...
-.br
-.B bar
-.RI [ options ] " files" ...
-.SH DESCRIPTION
-This manual page documents briefly the
-.B OpenFovea
-commands.
-.PP
-.\" TeX users may be more comfortable with the \fB<whatever>\fP and
-.\" \fI<whatever>\fP escape sequences to invode bold face and italics,
-.\" respectively.
-\fBOpenFovea\fP is a program that postprocess AFM force volume experiment. It is able to compute the stiffness of the scanned area, detect unbinding event and the zero force topography. It comes with a user friendly graphical interface that allow the user to select and display the data.
-.SH OPTIONS
-These programs follow the usual GNU command line syntax, with long
-options starting with two dashes (`-').
-A summary of options is included below.
-For a complete description, see the Info files.
-.TP
-.B \-h, \-\-help
-Show summary of options.
-.TP
-.B \-v, \-\-version
-Show version of program.
-.br
-The programs are documented fully by
-.IR "The Rise and Fall of a Fooish Bar" ,
-available via the Info system.
-.SH AUTHOR
-OpenFovea was written by <upstream author>.
-.PP
-This manual page was written by Charles Roduit <charles.roduit at gmail.com>,
-for the Debian project (and may be used by others).
diff --git a/debian/rules b/debian/rules
deleted file mode 100755
index 71feba2..0000000
--- a/debian/rules
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/usr/bin/make -f
-
-# Uncomment this to turn on verbose mode.
-export DH_VERBOSE=1
-
-%:
- dh $@ --with python2
\ No newline at end of file
diff --git a/debian/source/format b/debian/source/format
deleted file mode 100644
index 163aaf8..0000000
--- a/debian/source/format
+++ /dev/null
@@ -1 +0,0 @@
-3.0 (quilt)
diff --git a/debian/watch b/debian/watch
deleted file mode 100644
index 66a60a0..0000000
--- a/debian/watch
+++ /dev/null
@@ -1,3 +0,0 @@
-version=3
-https://launchpad.net/openfovea/+download \
- https://launchpad.net/openfovea/trunk/unstable/\+download/openfovea-(.*)\.tar\.gz
diff --git a/openfovea.desktop b/openfovea.desktop
new file mode 100644
index 0000000..fe76687
--- /dev/null
+++ b/openfovea.desktop
@@ -0,0 +1,10 @@
+[Desktop Entry]
+Version=0.1
+Terminal=false
+Exec=openfovea
+Icon=openfovea
+Type=Application
+Categories=Application;Education;Science;
+Name=OpenFovea
+Comment=Postprocess AFM force volume experiments
+Comment[fr]=Traiter des fichiers AFM en force volume
diff --git a/openfovea/Icon/CellFovea.ico b/openfovea/Icon/CellFovea.ico
new file mode 100644
index 0000000..b89cdb4
Binary files /dev/null and b/openfovea/Icon/CellFovea.ico differ
diff --git a/openfovea/Icon/CellFovea.png b/openfovea/Icon/CellFovea.png
new file mode 100644
index 0000000..89bd853
Binary files /dev/null and b/openfovea/Icon/CellFovea.png differ
diff --git a/openfovea/Icon/CellFovea.svg b/openfovea/Icon/CellFovea.svg
new file mode 100644
index 0000000..f822956
--- /dev/null
+++ b/openfovea/Icon/CellFovea.svg
@@ -0,0 +1,3596 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="240.64737"
+ height="162.86655"
+ id="svg3213"
+ sodipodi:version="0.32"
+ inkscape:version="0.47 r22583"
+ sodipodi:docname="CellFovea.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"
+ version="1.0"
+ inkscape:export-filename="CellFovea.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90">
+ <defs
+ id="defs3215">
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 526.18109 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="744.09448 : 526.18109 : 1"
+ inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+ id="perspective669" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="2"
+ inkscape:cx="-77.612393"
+ inkscape:cy="59.39276"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ inkscape:window-width="1680"
+ inkscape:window-height="975"
+ inkscape:window-x="0"
+ inkscape:window-y="25"
+ showgrid="false"
+ inkscape:window-maximized="0" />
+ <metadata
+ id="metadata3218">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:groupmode="layer"
+ id="layer4"
+ inkscape:label="Bits"
+ transform="translate(-45.700655,-119.62301)">
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="131.42107"
+ y="228.63101"
+ id="text7323"><tspan
+ sodipodi:role="line"
+ id="tspan7325"
+ x="131.42107"
+ y="228.63101">1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="223.06163"
+ y="270.31192"
+ id="text7327"><tspan
+ sodipodi:role="line"
+ id="tspan7329"
+ x="223.06163"
+ y="270.31192">0</tspan></text>
+ <text
+ id="text7331"
+ y="140.82446"
+ x="62.094151"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="140.82446"
+ x="62.094151"
+ id="tspan7333"
+ sodipodi:role="line">1</tspan></text>
+ <text
+ id="text7335"
+ y="164.16061"
+ x="153.89981"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="164.16061"
+ x="153.89981"
+ id="tspan7337"
+ sodipodi:role="line">0</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="251.19574"
+ y="160.35654"
+ id="text7339"><tspan
+ sodipodi:role="line"
+ id="tspan7341"
+ x="251.19574"
+ y="160.35654">1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="107.87391"
+ y="272.68326"
+ id="text7343"><tspan
+ sodipodi:role="line"
+ id="tspan7345"
+ x="107.87391"
+ y="272.68326">0</tspan></text>
+ <text
+ id="text7347"
+ y="138.54355"
+ x="209.77827"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="138.54355"
+ x="209.77827"
+ id="tspan7349"
+ sodipodi:role="line">1</tspan></text>
+ <text
+ id="text7351"
+ y="172.26157"
+ x="121.87239"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="172.26157"
+ x="121.87239"
+ id="tspan7353"
+ sodipodi:role="line">0</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="184.1967"
+ y="222.21594"
+ id="text7355"><tspan
+ sodipodi:role="line"
+ id="tspan7357"
+ x="184.1967"
+ y="222.21594">1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="64.094971"
+ y="211.85178"
+ id="text7359"><tspan
+ sodipodi:role="line"
+ id="tspan7361"
+ x="64.094971"
+ y="211.85178">0</tspan></text>
+ <text
+ id="text7363"
+ y="142.17252"
+ x="167.27603"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="142.17252"
+ x="167.27603"
+ id="tspan7365"
+ sodipodi:role="line">1</tspan></text>
+ <text
+ id="text7367"
+ y="179.45569"
+ x="108.6289"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="179.45569"
+ x="108.6289"
+ id="tspan7369"
+ sodipodi:role="line">0</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="196.16692"
+ y="243.49857"
+ id="text7371"><tspan
+ sodipodi:role="line"
+ id="tspan7373"
+ x="196.16692"
+ y="243.49857">1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="241.46648"
+ y="148.81821"
+ id="text7375"><tspan
+ sodipodi:role="line"
+ id="tspan7377"
+ x="241.46648"
+ y="148.81821">0</tspan></text>
+ <text
+ id="text7379"
+ y="191.34015"
+ x="200.73746"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="191.34015"
+ x="200.73746"
+ id="tspan7381"
+ sodipodi:role="line">1</tspan></text>
+ <text
+ id="text7383"
+ y="229.76044"
+ x="147.33078"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="229.76044"
+ x="147.33078"
+ id="tspan7385"
+ sodipodi:role="line">0</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="267.31656"
+ y="247.83063"
+ id="text7387"><tspan
+ sodipodi:role="line"
+ id="tspan7389"
+ x="267.31656"
+ y="247.83063">1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="189.32402"
+ y="233.61884"
+ id="text7391"><tspan
+ sodipodi:role="line"
+ id="tspan7393"
+ x="189.32402"
+ y="233.61884">0</tspan></text>
+ <text
+ id="text7395"
+ y="245.02402"
+ x="279.52615"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="245.02402"
+ x="279.52615"
+ id="tspan7397"
+ sodipodi:role="line">1</tspan></text>
+ <text
+ id="text7399"
+ y="177.79987"
+ x="159.79971"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="177.79987"
+ x="159.79971"
+ id="tspan7401"
+ sodipodi:role="line">0</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="230.61319"
+ y="216.32779"
+ id="text7403"><tspan
+ sodipodi:role="line"
+ id="tspan7405"
+ x="230.61319"
+ y="216.32779">1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="184.16879"
+ y="245.44888"
+ id="text7407"><tspan
+ sodipodi:role="line"
+ id="tspan7409"
+ x="184.16879"
+ y="245.44888">0</tspan></text>
+ <text
+ id="text7411"
+ y="256.19821"
+ x="277.21558"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="256.19821"
+ x="277.21558"
+ id="tspan7413"
+ sodipodi:role="line">1</tspan></text>
+ <text
+ id="text7415"
+ y="217.78836"
+ x="244.34319"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="217.78836"
+ x="244.34319"
+ id="tspan7417"
+ sodipodi:role="line">0</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="161.53256"
+ y="203.74268"
+ id="text7419"><tspan
+ sodipodi:role="line"
+ id="tspan7421"
+ x="161.53256"
+ y="203.74268">1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="193.90981"
+ y="252.92419"
+ id="text7423"><tspan
+ sodipodi:role="line"
+ id="tspan7425"
+ x="193.90981"
+ y="252.92419">0</tspan></text>
+ <text
+ id="text7427"
+ y="221.67325"
+ x="62.491062"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="221.67325"
+ x="62.491062"
+ id="tspan7429"
+ sodipodi:role="line">1</tspan></text>
+ <text
+ id="text7431"
+ y="148.5636"
+ x="132.19785"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="148.5636"
+ x="132.19785"
+ id="tspan7433"
+ sodipodi:role="line">0</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="201.76994"
+ y="233.55933"
+ id="text7435"><tspan
+ sodipodi:role="line"
+ id="tspan7437"
+ x="201.76994"
+ y="233.55933">1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="94.649147"
+ y="250.63531"
+ id="text7439"><tspan
+ sodipodi:role="line"
+ id="tspan7441"
+ x="94.649147"
+ y="250.63531">0</tspan></text>
+ <text
+ id="text7443"
+ y="227.9404"
+ x="238.36919"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="227.9404"
+ x="238.36919"
+ id="tspan7445"
+ sodipodi:role="line">1</tspan></text>
+ <text
+ id="text7447"
+ y="236.28308"
+ x="90.436417"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="236.28308"
+ x="90.436417"
+ id="tspan7449"
+ sodipodi:role="line">0</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="269.03665"
+ y="175.63049"
+ id="text7451"><tspan
+ sodipodi:role="line"
+ id="tspan7453"
+ x="269.03665"
+ y="175.63049">1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="68.647423"
+ y="170.26721"
+ id="text7455"><tspan
+ sodipodi:role="line"
+ id="tspan7457"
+ x="68.647423"
+ y="170.26721">0</tspan></text>
+ <text
+ id="text7459"
+ y="137.40594"
+ x="52.400578"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="137.40594"
+ x="52.400578"
+ id="tspan7461"
+ sodipodi:role="line">1</tspan></text>
+ <text
+ id="text7463"
+ y="157.71948"
+ x="121.76649"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="157.71948"
+ x="121.76649"
+ id="tspan7465"
+ sodipodi:role="line">0</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="131.57228"
+ y="253.93176"
+ id="text7467"><tspan
+ sodipodi:role="line"
+ id="tspan7469"
+ x="131.57228"
+ y="253.93176">1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="95.258995"
+ y="188.83554"
+ id="text7471"><tspan
+ sodipodi:role="line"
+ id="tspan7473"
+ x="95.258995"
+ y="188.83554">0</tspan></text>
+ <text
+ id="text7475"
+ y="260.15335"
+ x="70.6194"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="260.15335"
+ x="70.6194"
+ id="tspan7477"
+ sodipodi:role="line">1</tspan></text>
+ <text
+ id="text7479"
+ y="177.81973"
+ x="57.143078"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="177.81973"
+ x="57.143078"
+ id="tspan7481"
+ sodipodi:role="line">0</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="70.99781"
+ y="145.83261"
+ id="text7483"><tspan
+ sodipodi:role="line"
+ id="tspan7485"
+ x="70.99781"
+ y="145.83261">1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="124.69511"
+ y="218.29025"
+ id="text7487"><tspan
+ sodipodi:role="line"
+ id="tspan7489"
+ x="124.69511"
+ y="218.29025">0</tspan></text>
+ <text
+ id="text7491"
+ y="126.53004"
+ x="221.88689"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="126.53004"
+ x="221.88689"
+ id="tspan7493"
+ sodipodi:role="line">1</tspan></text>
+ <text
+ id="text7495"
+ y="169.52927"
+ x="209.61615"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="169.52927"
+ x="209.61615"
+ id="tspan7497"
+ sodipodi:role="line">0</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="268.06421"
+ y="213.08386"
+ id="text7499"><tspan
+ sodipodi:role="line"
+ id="tspan7501"
+ x="268.06421"
+ y="213.08386">1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="103.32587"
+ y="238.55936"
+ id="text7503"><tspan
+ sodipodi:role="line"
+ id="tspan7505"
+ x="103.32587"
+ y="238.55936">0</tspan></text>
+ <text
+ id="text7507"
+ y="247.54709"
+ x="245.06923"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="247.54709"
+ x="245.06923"
+ id="tspan7509"
+ sodipodi:role="line">1</tspan></text>
+ <text
+ id="text7511"
+ y="142.72232"
+ x="221.64601"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="142.72232"
+ x="221.64601"
+ id="tspan7513"
+ sodipodi:role="line">0</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="88.418098"
+ y="224.23447"
+ id="text7515"><tspan
+ sodipodi:role="line"
+ id="tspan7517"
+ x="88.418098"
+ y="224.23447">1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="211.20859"
+ y="182.65372"
+ id="text7519"><tspan
+ sodipodi:role="line"
+ id="tspan7521"
+ x="211.20859"
+ y="182.65372">0</tspan></text>
+ <text
+ id="text7523"
+ y="231.28979"
+ x="116.50174"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="231.28979"
+ x="116.50174"
+ id="tspan7525"
+ sodipodi:role="line">1</tspan></text>
+ <text
+ id="text7527"
+ y="193.50485"
+ x="267.6705"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="193.50485"
+ x="267.6705"
+ id="tspan7529"
+ sodipodi:role="line">0</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="199.69466"
+ y="219.73071"
+ id="text7531"><tspan
+ sodipodi:role="line"
+ id="tspan7533"
+ x="199.69466"
+ y="219.73071">1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="125.50278"
+ y="137.23776"
+ id="text7535"><tspan
+ sodipodi:role="line"
+ id="tspan7537"
+ x="125.50278"
+ y="137.23776">0</tspan></text>
+ <text
+ id="text7539"
+ y="231.14621"
+ x="262.4632"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="231.14621"
+ x="262.4632"
+ id="tspan7541"
+ sodipodi:role="line">1</tspan></text>
+ <text
+ id="text7543"
+ y="264.32819"
+ x="83.972054"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="264.32819"
+ x="83.972054"
+ id="tspan7545"
+ sodipodi:role="line">0</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="154.86562"
+ y="214.05109"
+ id="text7547"><tspan
+ sodipodi:role="line"
+ id="tspan7549"
+ x="154.86562"
+ y="214.05109">1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="256.22528"
+ y="275.93796"
+ id="text7551"><tspan
+ sodipodi:role="line"
+ id="tspan7553"
+ x="256.22528"
+ y="275.93796">0</tspan></text>
+ <text
+ id="text7555"
+ y="135.35281"
+ x="108.81109"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="135.35281"
+ x="108.81109"
+ id="tspan7557"
+ sodipodi:role="line">1</tspan></text>
+ <text
+ id="text7559"
+ y="258.58734"
+ x="106.2802"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="258.58734"
+ x="106.2802"
+ id="tspan7561"
+ sodipodi:role="line">0</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="277.11206"
+ y="268.27054"
+ id="text7563"><tspan
+ sodipodi:role="line"
+ id="tspan7565"
+ x="277.11206"
+ y="268.27054">1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="174.5488"
+ y="175.61255"
+ id="text7567"><tspan
+ sodipodi:role="line"
+ id="tspan7569"
+ x="174.5488"
+ y="175.61255">0</tspan></text>
+ <text
+ id="text7571"
+ y="134.92068"
+ x="152.73569"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="134.92068"
+ x="152.73569"
+ id="tspan7573"
+ sodipodi:role="line">1</tspan></text>
+ <text
+ id="text7575"
+ y="207.51483"
+ x="113.5559"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="207.51483"
+ x="113.5559"
+ id="tspan7577"
+ sodipodi:role="line">0</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="134.77467"
+ y="175.8103"
+ id="text7579"><tspan
+ sodipodi:role="line"
+ id="tspan7581"
+ x="134.77467"
+ y="175.8103">1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="120.65469"
+ y="124.00263"
+ id="text7583"><tspan
+ sodipodi:role="line"
+ id="tspan7585"
+ x="120.65469"
+ y="124.00263">0</tspan></text>
+ <text
+ id="text7587"
+ y="174.66803"
+ x="78.980545"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="174.66803"
+ x="78.980545"
+ id="tspan7589"
+ sodipodi:role="line">1</tspan></text>
+ <text
+ id="text7591"
+ y="247.42282"
+ x="67.847542"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="247.42282"
+ x="67.847542"
+ id="tspan7593"
+ sodipodi:role="line">0</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="234.17882"
+ y="268.25302"
+ id="text7595"><tspan
+ sodipodi:role="line"
+ id="tspan7597"
+ x="234.17882"
+ y="268.25302">1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="81.596985"
+ y="139.26016"
+ id="text7599"><tspan
+ sodipodi:role="line"
+ id="tspan7601"
+ x="81.596985"
+ y="139.26016">0</tspan></text>
+ <text
+ id="text7603"
+ y="172.61575"
+ x="243.68694"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="172.61575"
+ x="243.68694"
+ id="tspan7605"
+ sodipodi:role="line">1</tspan></text>
+ <text
+ id="text7607"
+ y="124.4159"
+ x="204.99699"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="124.4159"
+ x="204.99699"
+ id="tspan7609"
+ sodipodi:role="line">0</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="185.714"
+ y="167.75403"
+ id="text7611"><tspan
+ sodipodi:role="line"
+ id="tspan7613"
+ x="185.714"
+ y="167.75403">1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="58.348816"
+ y="165.0242"
+ id="text7615"><tspan
+ sodipodi:role="line"
+ id="tspan7617"
+ x="58.348816"
+ y="165.0242">0</tspan></text>
+ <text
+ id="text7619"
+ y="246.27271"
+ x="110.42152"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="246.27271"
+ x="110.42152"
+ id="tspan7621"
+ sodipodi:role="line">1</tspan></text>
+ <text
+ id="text7623"
+ y="270.323"
+ x="62.909519"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="270.323"
+ x="62.909519"
+ id="tspan7625"
+ sodipodi:role="line">0</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="237.15057"
+ y="124.3672"
+ id="text7627"><tspan
+ sodipodi:role="line"
+ id="tspan7629"
+ x="237.15057"
+ y="124.3672">1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="270.89532"
+ y="146.11148"
+ id="text7631"><tspan
+ sodipodi:role="line"
+ id="tspan7633"
+ x="270.89532"
+ y="146.11148">0</tspan></text>
+ <text
+ id="text7635"
+ y="160.84805"
+ x="239.36745"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="160.84805"
+ x="239.36745"
+ id="tspan7637"
+ sodipodi:role="line">1</tspan></text>
+ <text
+ id="text7639"
+ y="264.9032"
+ x="172.15738"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="264.9032"
+ x="172.15738"
+ id="tspan7641"
+ sodipodi:role="line">0</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="81.674652"
+ y="126.24647"
+ id="text7643"><tspan
+ sodipodi:role="line"
+ id="tspan7645"
+ x="81.674652"
+ y="126.24647">1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="123.13406"
+ y="268.15717"
+ id="text7647"><tspan
+ sodipodi:role="line"
+ id="tspan7649"
+ x="123.13406"
+ y="268.15717">0</tspan></text>
+ <text
+ id="text7651"
+ y="195.62305"
+ x="109.57259"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="195.62305"
+ x="109.57259"
+ id="tspan7653"
+ sodipodi:role="line">1</tspan></text>
+ <text
+ id="text7655"
+ y="211.34879"
+ x="186.72392"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="211.34879"
+ x="186.72392"
+ id="tspan7657"
+ sodipodi:role="line">0</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="100.37601"
+ y="157.28885"
+ id="text7659"><tspan
+ sodipodi:role="line"
+ id="tspan7661"
+ x="100.37601"
+ y="157.28885">1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="278.1217"
+ y="133.47122"
+ id="text7663"><tspan
+ sodipodi:role="line"
+ id="tspan7665"
+ x="278.1217"
+ y="133.47122">0</tspan></text>
+ <text
+ id="text7667"
+ y="138.38715"
+ x="232.15355"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="138.38715"
+ x="232.15355"
+ id="tspan7669"
+ sodipodi:role="line">1</tspan></text>
+ <text
+ id="text7671"
+ y="208.06494"
+ x="72.864136"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="208.06494"
+ x="72.864136"
+ id="tspan7673"
+ sodipodi:role="line">0</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="135.18149"
+ y="198.08893"
+ id="text7675"><tspan
+ sodipodi:role="line"
+ id="tspan7677"
+ x="135.18149"
+ y="198.08893">1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="188.43085"
+ y="183.0831"
+ id="text7679"><tspan
+ sodipodi:role="line"
+ id="tspan7681"
+ x="188.43085"
+ y="183.0831">0</tspan></text>
+ <text
+ id="text7683"
+ y="154.85855"
+ x="227.39803"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="154.85855"
+ x="227.39803"
+ id="tspan7685"
+ sodipodi:role="line">1</tspan></text>
+ <text
+ id="text7687"
+ y="239.40295"
+ x="235.49475"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="239.40295"
+ x="235.49475"
+ id="tspan7689"
+ sodipodi:role="line">0</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="188.15451"
+ y="124.07832"
+ id="text7691"><tspan
+ sodipodi:role="line"
+ id="tspan7693"
+ x="188.15451"
+ y="124.07832">1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="239.5519"
+ y="207.20532"
+ id="text7695"><tspan
+ sodipodi:role="line"
+ id="tspan7697"
+ x="239.5519"
+ y="207.20532">0</tspan></text>
+ <text
+ id="text7699"
+ y="146.14432"
+ x="255.6631"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="146.14432"
+ x="255.6631"
+ id="tspan7701"
+ sodipodi:role="line">1</tspan></text>
+ <text
+ id="text7703"
+ y="164.67694"
+ x="270.41199"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="164.67694"
+ x="270.41199"
+ id="tspan7705"
+ sodipodi:role="line">0</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="255.41878"
+ y="194.40753"
+ id="text7707"><tspan
+ sodipodi:role="line"
+ id="tspan7709"
+ x="255.41878"
+ y="194.40753">1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="205.46326"
+ y="264.65891"
+ id="text7711"><tspan
+ sodipodi:role="line"
+ id="tspan7713"
+ x="205.46326"
+ y="264.65891">0</tspan></text>
+ <text
+ id="text7715"
+ y="254.12766"
+ x="203.73886"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="254.12766"
+ x="203.73886"
+ id="tspan7717"
+ sodipodi:role="line">1</tspan></text>
+ <text
+ id="text7719"
+ y="153.10153"
+ x="79.12059"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="153.10153"
+ x="79.12059"
+ id="tspan7721"
+ sodipodi:role="line">0</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="82.240837"
+ y="213.31952"
+ id="text7723"><tspan
+ sodipodi:role="line"
+ id="tspan7725"
+ x="82.240837"
+ y="213.31952">1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="139.58028"
+ y="138.88788"
+ id="text7727"><tspan
+ sodipodi:role="line"
+ id="tspan7729"
+ x="139.58028"
+ y="138.88788">0</tspan></text>
+ <text
+ id="text7731"
+ y="207.01096"
+ x="203.29869"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="207.01096"
+ x="203.29869"
+ id="tspan7733"
+ sodipodi:role="line">1</tspan></text>
+ <text
+ id="text7735"
+ y="229.10562"
+ x="54.580288"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="229.10562"
+ x="54.580288"
+ id="tspan7737"
+ sodipodi:role="line">0</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="128.86438"
+ y="186.3273"
+ id="text7739"><tspan
+ sodipodi:role="line"
+ id="tspan7741"
+ x="128.86438"
+ y="186.3273">1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="251.71045"
+ y="226.49323"
+ id="text7743"><tspan
+ sodipodi:role="line"
+ id="tspan7745"
+ x="251.71045"
+ y="226.49323">0</tspan></text>
+ <text
+ id="text7747"
+ y="259.50558"
+ x="267.04922"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="259.50558"
+ x="267.04922"
+ id="tspan7749"
+ sodipodi:role="line">1</tspan></text>
+ <text
+ id="text7751"
+ y="165.45895"
+ x="142.70377"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="165.45895"
+ x="142.70377"
+ id="tspan7753"
+ sodipodi:role="line">0</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="218.15103"
+ y="205.93649"
+ id="text7755"><tspan
+ sodipodi:role="line"
+ id="tspan7757"
+ x="218.15103"
+ y="205.93649">1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="257.78766"
+ y="263.67184"
+ id="text7759"><tspan
+ sodipodi:role="line"
+ id="tspan7761"
+ x="257.78766"
+ y="263.67184">0</tspan></text>
+ <text
+ id="text7763"
+ y="278.85159"
+ x="92.469254"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="278.85159"
+ x="92.469254"
+ id="tspan7765"
+ sodipodi:role="line">1</tspan></text>
+ <text
+ id="text7767"
+ y="214.56992"
+ x="52.940365"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="214.56992"
+ x="52.940365"
+ id="tspan7769"
+ sodipodi:role="line">0</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="211.85077"
+ y="196.25562"
+ id="text7771"><tspan
+ sodipodi:role="line"
+ id="tspan7773"
+ x="211.85077"
+ y="196.25562">1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="148.51263"
+ y="177.56439"
+ id="text7775"><tspan
+ sodipodi:role="line"
+ id="tspan7777"
+ x="148.51263"
+ y="177.56439">0</tspan></text>
+ <text
+ id="text7779"
+ y="168.66425"
+ x="232.2695"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="168.66425"
+ x="232.2695"
+ id="tspan7781"
+ sodipodi:role="line">1</tspan></text>
+ <text
+ id="text7783"
+ y="158.5563"
+ x="68.976707"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="158.5563"
+ x="68.976707"
+ id="tspan7785"
+ sodipodi:role="line">0</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="134.01878"
+ y="209.83325"
+ id="text7787"><tspan
+ sodipodi:role="line"
+ id="tspan7789"
+ x="134.01878"
+ y="209.83325">1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="87.222809"
+ y="163.51364"
+ id="text7791"><tspan
+ sodipodi:role="line"
+ id="tspan7793"
+ x="87.222809"
+ y="163.51364">0</tspan></text>
+ <text
+ id="text7795"
+ y="245.26587"
+ x="146.75023"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="245.26587"
+ x="146.75023"
+ id="tspan7797"
+ sodipodi:role="line">1</tspan></text>
+ <text
+ id="text7799"
+ y="218.09555"
+ x="215.00714"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="218.09555"
+ x="215.00714"
+ id="tspan7801"
+ sodipodi:role="line">0</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="175.84486"
+ y="202.78647"
+ id="text7803"><tspan
+ sodipodi:role="line"
+ id="tspan7805"
+ x="175.84486"
+ y="202.78647">1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="166.60945"
+ y="189.87643"
+ id="text7807"><tspan
+ sodipodi:role="line"
+ id="tspan7809"
+ x="166.60945"
+ y="189.87643">0</tspan></text>
+ <text
+ id="text7811"
+ y="155.73206"
+ x="174.52493"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="155.73206"
+ x="174.52493"
+ id="tspan7813"
+ sodipodi:role="line">1</tspan></text>
+ <text
+ id="text7815"
+ y="147.48669"
+ x="52.792706"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="147.48669"
+ x="52.792706"
+ id="tspan7817"
+ sodipodi:role="line">0</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="160.22496"
+ y="236.4252"
+ id="text7819"><tspan
+ sodipodi:role="line"
+ id="tspan7821"
+ x="160.22496"
+ y="236.4252">1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="105.15429"
+ y="123.04322"
+ id="text7823"><tspan
+ sodipodi:role="line"
+ id="tspan7825"
+ x="105.15429"
+ y="123.04322">0</tspan></text>
+ <text
+ id="text7827"
+ y="201.86539"
+ x="98.401131"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="201.86539"
+ x="98.401131"
+ id="tspan7829"
+ sodipodi:role="line">1</tspan></text>
+ <text
+ id="text7831"
+ y="248.95572"
+ x="80.990288"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="248.95572"
+ x="80.990288"
+ id="tspan7833"
+ sodipodi:role="line">0</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="143.98083"
+ y="256.83417"
+ id="text7835"><tspan
+ sodipodi:role="line"
+ id="tspan7837"
+ x="143.98083"
+ y="256.83417">1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="186.36455"
+ y="257.64389"
+ id="text7839"><tspan
+ sodipodi:role="line"
+ id="tspan7841"
+ x="186.36455"
+ y="257.64389">0</tspan></text>
+ <text
+ id="text7843"
+ y="221.50293"
+ x="263.09351"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="221.50293"
+ x="263.09351"
+ id="tspan7845"
+ sodipodi:role="line">1</tspan></text>
+ <text
+ id="text7847"
+ y="260.75845"
+ x="214.45547"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="260.75845"
+ x="214.45547"
+ id="tspan7849"
+ sodipodi:role="line">0</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="45.899574"
+ y="188.49945"
+ id="text7851"><tspan
+ sodipodi:role="line"
+ id="tspan7853"
+ x="45.899574"
+ y="188.49945">1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="56.529663"
+ y="243.39795"
+ id="text7855"><tspan
+ sodipodi:role="line"
+ id="tspan7857"
+ x="56.529663"
+ y="243.39795">0</tspan></text>
+ <text
+ id="text7859"
+ y="191.96484"
+ x="153.80205"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="191.96484"
+ x="153.80205"
+ id="tspan7861"
+ sodipodi:role="line">1</tspan></text>
+ <text
+ id="text7863"
+ y="269.5448"
+ x="246.4796"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="269.5448"
+ x="246.4796"
+ id="tspan7865"
+ sodipodi:role="line">0</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="55.995922"
+ y="257.84549"
+ id="text7867"><tspan
+ sodipodi:role="line"
+ id="tspan7869"
+ x="55.995922"
+ y="257.84549">1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="171.88618"
+ y="241.60095"
+ id="text7871"><tspan
+ sodipodi:role="line"
+ id="tspan7873"
+ x="171.88618"
+ y="241.60095">0</tspan></text>
+ <text
+ id="text7875"
+ y="190.3486"
+ x="179.25259"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="190.3486"
+ x="179.25259"
+ id="tspan7877"
+ sodipodi:role="line">1</tspan></text>
+ <text
+ id="text7879"
+ y="235.20984"
+ x="78.651352"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="235.20984"
+ x="78.651352"
+ id="tspan7881"
+ sodipodi:role="line">0</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="84.658546"
+ y="200.48074"
+ id="text7883"><tspan
+ sodipodi:role="line"
+ id="tspan7885"
+ x="84.658546"
+ y="200.48074">1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="263.55734"
+ y="203.3121"
+ id="text7887"><tspan
+ sodipodi:role="line"
+ id="tspan7889"
+ x="263.55734"
+ y="203.3121">0</tspan></text>
+ <text
+ id="text7891"
+ y="222.24011"
+ x="74.2994"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="222.24011"
+ x="74.2994"
+ id="tspan7893"
+ sodipodi:role="line">1</tspan></text>
+ <text
+ id="text7895"
+ y="149.39575"
+ x="201.1241"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="149.39575"
+ x="201.1241"
+ id="tspan7897"
+ sodipodi:role="line">0</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="273.68958"
+ y="225.88272"
+ id="text7899"><tspan
+ sodipodi:role="line"
+ id="tspan7901"
+ x="273.68958"
+ y="225.88272">1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="248.6011"
+ y="183.89633"
+ id="text7903"><tspan
+ sodipodi:role="line"
+ id="tspan7905"
+ x="248.6011"
+ y="183.89633">0</tspan></text>
+ <text
+ id="text7907"
+ y="238.90292"
+ x="257.16061"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="238.90292"
+ x="257.16061"
+ id="tspan7909"
+ sodipodi:role="line">1</tspan></text>
+ <text
+ id="text7911"
+ y="185.04776"
+ x="274.09634"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="185.04776"
+ x="274.09634"
+ id="tspan7913"
+ sodipodi:role="line">0</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="145.54854"
+ y="204.84641"
+ id="text7915"><tspan
+ sodipodi:role="line"
+ id="tspan7917"
+ x="145.54854"
+ y="204.84641">1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="257.12903"
+ y="171.76752"
+ id="text7919"><tspan
+ sodipodi:role="line"
+ id="tspan7921"
+ x="257.12903"
+ y="171.76752">0</tspan></text>
+ <text
+ id="text7923"
+ y="201.78888"
+ x="124.00615"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="201.78888"
+ x="124.00615"
+ id="tspan7925"
+ sodipodi:role="line">1</tspan></text>
+ <text
+ id="text7927"
+ y="211.39737"
+ x="92.151649"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="211.39737"
+ x="92.151649"
+ id="tspan7929"
+ sodipodi:role="line">0</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="123.71606"
+ y="241.91376"
+ id="text7931"><tspan
+ sodipodi:role="line"
+ id="tspan7933"
+ x="123.71606"
+ y="241.91376">1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="229.65138"
+ y="199.33151"
+ id="text7935"><tspan
+ sodipodi:role="line"
+ id="tspan7937"
+ x="229.65138"
+ y="199.33151">0</tspan></text>
+ <text
+ id="text7939"
+ y="171.31924"
+ x="221.04245"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="171.31924"
+ x="221.04245"
+ id="tspan7941"
+ sodipodi:role="line">1</tspan></text>
+ <text
+ id="text7943"
+ y="252.89844"
+ x="232.88243"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="252.89844"
+ x="232.88243"
+ id="tspan7945"
+ sodipodi:role="line">0</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="58.925636"
+ y="189.922"
+ id="text7947"><tspan
+ sodipodi:role="line"
+ id="tspan7949"
+ x="58.925636"
+ y="189.922">1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="161.15739"
+ y="223.89349"
+ id="text7951"><tspan
+ sodipodi:role="line"
+ id="tspan7953"
+ x="161.15739"
+ y="223.89349">0</tspan></text>
+ <text
+ id="text7955"
+ y="152.68649"
+ x="188.14098"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="152.68649"
+ x="188.14098"
+ id="tspan7957"
+ sodipodi:role="line">1</tspan></text>
+ <text
+ id="text7959"
+ y="205.10791"
+ x="250.53149"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="205.10791"
+ x="250.53149"
+ id="tspan7961"
+ sodipodi:role="line">0</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="112.12592"
+ y="219.80997"
+ id="text7963"><tspan
+ sodipodi:role="line"
+ id="tspan7965"
+ x="112.12592"
+ y="219.80997">1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="257.03229"
+ y="250.08038"
+ id="text7967"><tspan
+ sodipodi:role="line"
+ id="tspan7969"
+ x="257.03229"
+ y="250.08038">0</tspan></text>
+ <text
+ id="text7971"
+ y="194.43082"
+ x="242.23183"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="194.43082"
+ x="242.23183"
+ id="tspan7973"
+ sodipodi:role="line">1</tspan></text>
+ <text
+ id="text7975"
+ y="265.25623"
+ x="96.08268"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="265.25623"
+ x="96.08268"
+ id="tspan7977"
+ sodipodi:role="line">0</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="145.5961"
+ y="150.26752"
+ id="text7979"><tspan
+ sodipodi:role="line"
+ id="tspan7981"
+ x="145.5961"
+ y="150.26752">1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="225.21077"
+ y="244.55939"
+ id="text7983"><tspan
+ sodipodi:role="line"
+ id="tspan7985"
+ x="225.21077"
+ y="244.55939">0</tspan></text>
+ <text
+ id="text7987"
+ y="227.19022"
+ x="102.21632"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="227.19022"
+ x="102.21632"
+ id="tspan7989"
+ sodipodi:role="line">1</tspan></text>
+ <text
+ id="text7991"
+ y="260.3157"
+ x="155.493"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="260.3157"
+ x="155.493"
+ id="tspan7993"
+ sodipodi:role="line">0</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="142.62978"
+ y="189.1004"
+ id="text7995"><tspan
+ sodipodi:role="line"
+ id="tspan7997"
+ x="142.62978"
+ y="189.1004">1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="93.911186"
+ y="133.0596"
+ id="text7999"><tspan
+ sodipodi:role="line"
+ id="tspan8001"
+ x="93.911186"
+ y="133.0596">0</tspan></text>
+ <text
+ id="text8003"
+ y="157.52823"
+ x="262.21701"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="157.52823"
+ x="262.21701"
+ id="tspan8005"
+ sodipodi:role="line">1</tspan></text>
+ <text
+ id="text8007"
+ y="123.38465"
+ x="252.02077"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="123.38465"
+ x="252.02077"
+ id="tspan8009"
+ sodipodi:role="line">0</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="180.50484"
+ y="265.0802"
+ id="text8011"><tspan
+ sodipodi:role="line"
+ id="tspan8013"
+ x="180.50484"
+ y="265.0802">1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="261.14484"
+ y="183.65457"
+ id="text8015"><tspan
+ sodipodi:role="line"
+ id="tspan8017"
+ x="261.14484"
+ y="183.65457">0</tspan></text>
+ <text
+ id="text8019"
+ y="253.15482"
+ x="174.89355"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="253.15482"
+ x="174.89355"
+ id="tspan8021"
+ sodipodi:role="line">1</tspan></text>
+ <text
+ id="text8023"
+ y="187.90659"
+ x="81.396645"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="187.90659"
+ x="81.396645"
+ id="tspan8025"
+ sodipodi:role="line">0</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="140.51141"
+ y="220.24677"
+ id="text8027"><tspan
+ sodipodi:role="line"
+ id="tspan8029"
+ x="140.51141"
+ y="220.24677">1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="247.1368"
+ y="236.3717"
+ id="text8031"><tspan
+ sodipodi:role="line"
+ id="tspan8033"
+ x="247.1368"
+ y="236.3717">0</tspan></text>
+ <text
+ id="text8035"
+ y="255.28009"
+ x="118.72974"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="255.28009"
+ x="118.72974"
+ id="tspan8037"
+ sodipodi:role="line">1</tspan></text>
+ <text
+ id="text8039"
+ y="136.74268"
+ x="194.19891"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="136.74268"
+ x="194.19891"
+ id="tspan8041"
+ sodipodi:role="line">0</tspan></text>
+ <text
+ id="text9133"
+ y="275.62961"
+ x="76.741791"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="275.62961"
+ x="76.741791"
+ id="tspan9135"
+ sodipodi:role="line">1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="136.49042"
+ y="239.63364"
+ id="text9137"><tspan
+ sodipodi:role="line"
+ id="tspan9139"
+ x="136.49042"
+ y="239.63364">1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="174.03481"
+ y="228.57138"
+ id="text9141"><tspan
+ sodipodi:role="line"
+ id="tspan9143"
+ x="174.03481"
+ y="228.57138">0</tspan></text>
+ <text
+ id="text9145"
+ y="187.03268"
+ x="222.60555"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="187.03268"
+ x="222.60555"
+ id="tspan9147"
+ sodipodi:role="line">0</tspan></text>
+ <text
+ id="text9149"
+ y="147.64297"
+ x="113.30415"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="147.64297"
+ x="113.30415"
+ id="tspan9151"
+ sodipodi:role="line">1</tspan></text>
+ <text
+ id="text9153"
+ y="227.30307"
+ x="210.40755"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="227.30307"
+ x="210.40755"
+ id="tspan9155"
+ sodipodi:role="line">1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="198.20656"
+ y="176.41068"
+ id="text9157"><tspan
+ sodipodi:role="line"
+ id="tspan9159"
+ x="198.20656"
+ y="176.41068">0</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="160.35732"
+ y="247.80856"
+ id="text9161"><tspan
+ sodipodi:role="line"
+ id="tspan9163"
+ x="160.35732"
+ y="247.80856">0</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="165.33409"
+ y="165.22528"
+ id="text9165"><tspan
+ sodipodi:role="line"
+ id="tspan9167"
+ x="165.33409"
+ y="165.22528">1</tspan></text>
+ <text
+ id="text9169"
+ y="198.93561"
+ x="190.03622"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="198.93561"
+ x="190.03622"
+ id="tspan9171"
+ sodipodi:role="line">0</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="89.460587"
+ y="177.40625"
+ id="text9173"><tspan
+ sodipodi:role="line"
+ id="tspan9175"
+ x="89.460587"
+ y="177.40625">1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="266.65631"
+ y="272.81451"
+ id="text9177"><tspan
+ sodipodi:role="line"
+ id="tspan9179"
+ x="266.65631"
+ y="272.81451">0</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="165.15221"
+ y="258.10254"
+ id="text9181"><tspan
+ sodipodi:role="line"
+ id="tspan9183"
+ x="165.15221"
+ y="258.10254">0</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="250.07803"
+ y="257.96936"
+ id="text9185"><tspan
+ sodipodi:role="line"
+ id="tspan9187"
+ x="250.07803"
+ y="257.96936">1</tspan></text>
+ <text
+ id="text9189"
+ y="215.28748"
+ x="101.39529"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="215.28748"
+ x="101.39529"
+ id="tspan9191"
+ sodipodi:role="line">1</tspan></text>
+ <text
+ id="text9193"
+ y="147.22446"
+ x="89.888008"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="147.22446"
+ x="89.888008"
+ id="tspan9195"
+ sodipodi:role="line">0</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="170.1925"
+ y="215.29181"
+ id="text9197"><tspan
+ sodipodi:role="line"
+ id="tspan9199"
+ x="170.1925"
+ y="215.29181">0</tspan></text>
+ <text
+ id="text9201"
+ y="151.06677"
+ x="159.22986"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="151.06677"
+ x="159.22986"
+ id="tspan9203"
+ sodipodi:role="line">1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="180.66072"
+ y="139.30685"
+ id="text9205"><tspan
+ sodipodi:role="line"
+ id="tspan9207"
+ x="180.66072"
+ y="139.30685">0</tspan></text>
+ <text
+ id="text9209"
+ y="154.93866"
+ x="214.17307"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="154.93866"
+ x="214.17307"
+ id="tspan9211"
+ sodipodi:role="line">1</tspan></text>
+ <text
+ id="text9213"
+ y="233.5549"
+ x="66.344627"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="233.5549"
+ x="66.344627"
+ id="tspan9215"
+ sodipodi:role="line">1</tspan></text>
+ <text
+ id="text9217"
+ y="236.38675"
+ x="46.764381"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="236.38675"
+ x="46.764381"
+ id="tspan9219"
+ sodipodi:role="line">1</tspan></text>
+ <text
+ id="text9221"
+ y="262.53342"
+ x="196.27524"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="262.53342"
+ x="196.27524"
+ id="tspan9223"
+ sodipodi:role="line">0</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="200.04375"
+ y="162.34601"
+ id="text9225"><tspan
+ sodipodi:role="line"
+ id="tspan9227"
+ x="200.04375"
+ y="162.34601">1</tspan></text>
+ <text
+ id="text9229"
+ y="237.20163"
+ x="269.33759"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="237.20163"
+ x="269.33759"
+ id="tspan9231"
+ sodipodi:role="line">1</tspan></text>
+ <text
+ id="text9233"
+ y="144.66498"
+ x="101.35098"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="144.66498"
+ x="101.35098"
+ id="tspan9235"
+ sodipodi:role="line">0</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="224.51021"
+ y="258.67438"
+ id="text9237"><tspan
+ sodipodi:role="line"
+ id="tspan9239"
+ x="224.51021"
+ y="258.67438">0</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="118.31021"
+ y="186.60919"
+ id="text9241"><tspan
+ sodipodi:role="line"
+ id="tspan9243"
+ x="118.31021"
+ y="186.60919">1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="110.93532"
+ y="163.6601"
+ id="text9245"><tspan
+ sodipodi:role="line"
+ id="tspan9247"
+ x="110.93532"
+ y="163.6601">1</tspan></text>
+ <text
+ id="text9249"
+ y="133.39905"
+ x="262.76294"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="133.39905"
+ x="262.76294"
+ id="tspan9251"
+ sodipodi:role="line">0</tspan></text>
+ <text
+ id="text9253"
+ y="195.44809"
+ x="70.369133"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="195.44809"
+ x="70.369133"
+ id="tspan9255"
+ sodipodi:role="line">0</tspan></text>
+ <text
+ id="text9257"
+ y="213.84482"
+ x="255.71587"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="213.84482"
+ x="255.71587"
+ id="tspan9259"
+ sodipodi:role="line">1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="137.7946"
+ y="124.68156"
+ id="text9261"><tspan
+ sodipodi:role="line"
+ id="tspan9263"
+ x="137.7946"
+ y="124.68156">0</tspan></text>
+ <text
+ id="text9265"
+ y="201.82272"
+ x="56.023602"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="201.82272"
+ x="56.023602"
+ id="tspan9267"
+ sodipodi:role="line">1</tspan></text>
+ <text
+ id="text9269"
+ y="160.70361"
+ x="131.81258"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="160.70361"
+ x="131.81258"
+ id="tspan9271"
+ sodipodi:role="line">0</tspan></text>
+ <text
+ id="text9273"
+ y="236.68692"
+ x="214.94283"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="236.68692"
+ x="214.94283"
+ id="tspan9275"
+ sodipodi:role="line">0</tspan></text>
+ <text
+ id="text9277"
+ y="182.85999"
+ x="69.354774"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="182.85999"
+ x="69.354774"
+ id="tspan9279"
+ sodipodi:role="line">1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="99.148666"
+ y="170.15384"
+ id="text9281"><tspan
+ sodipodi:role="line"
+ id="tspan9283"
+ x="99.148666"
+ y="170.15384">1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="215.68773"
+ y="249.82095"
+ id="text9285"><tspan
+ sodipodi:role="line"
+ id="tspan9287"
+ x="215.68773"
+ y="249.82095">0</tspan></text>
+ <text
+ id="text9289"
+ y="228.98578"
+ x="225.24254"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="228.98578"
+ x="225.24254"
+ id="tspan9291"
+ sodipodi:role="line">0</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="247.55975"
+ y="135.91595"
+ id="text9293"><tspan
+ sodipodi:role="line"
+ id="tspan9295"
+ x="247.55975"
+ y="135.91595">1</tspan></text>
+ <text
+ id="text9297"
+ y="133.06812"
+ x="71.292046"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><tspan
+ y="133.06812"
+ x="71.292046"
+ id="tspan9299"
+ sodipodi:role="line">0</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="233.8558"
+ y="181.68958"
+ id="text9301"><tspan
+ sodipodi:role="line"
+ id="tspan9303"
+ x="233.8558"
+ y="181.68958">1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="61.191753"
+ y="152.93503"
+ id="text9305"><tspan
+ sodipodi:role="line"
+ id="tspan9307"
+ x="61.191753"
+ y="152.93503">1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="240.61885"
+ y="259.27277"
+ id="text9309"><tspan
+ sodipodi:role="line"
+ id="tspan9311"
+ x="240.61885"
+ y="259.27277">1</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:4.60828304px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ x="206.93533"
+ y="243.84409"
+ id="text9313"><tspan
+ sodipodi:role="line"
+ id="tspan9315"
+ x="206.93533"
+ y="243.84409">0</tspan></text>
+ </g>
+ <g
+ inkscape:label="Fovea"
+ inkscape:groupmode="layer"
+ id="layer1"
+ style="display:inline"
+ transform="translate(-45.700655,-119.62301)">
+ <path
+ style="fill:#dca958;fill-opacity:1;stroke:#cd8d2b;stroke-width:0.76804715;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 46.648158,209.94851 L 80.210466,209.94851 C 87.298224,209.81407 99.75468,251.13536 114.35844,251.12768 C 128.37654,251.12001 140.34494,210.10691 147.92075,209.94851 L 181.65459,209.94851 L 181.65459,281.48257 L 46.648158,281.48257 L 46.648158,209.94851 z"
+ id="rect2851"
+ sodipodi:nodetypes="cczccccc" />
+ <g
+ id="g2647"
+ transform="translate(-105,-83)">
+ <g
+ transform="matrix(0.3840236,0,0,0.3840236,103.53412,157.89079)"
+ id="g5910">
+ <path
+ style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 142.43151,459.90771 C 141.14867,459.79597 139.08492,460.97504 139.65359,462.93817 C 140.23148,464.93313 141.59315,461.6746 140.4112,485.6666 C 140.2676,488.58138 137.75956,490.40169 137.38075,494.2529 C 137.00195,498.10411 138.58031,494.18977 138.51717,510.41534 C 138.47467,521.33918 138.3909,540.08857 138.3909,540.08857 L 142.93659,540.08857 C 142.93659,540.08857 141.77404,517.23242 141.80016,504.73323 C 141.81446,498.03027 145.08316,494.94738 145.20943,491.47498 C 145.3 [...]
+ id="path3266"
+ sodipodi:nodetypes="cssssccsssss" />
+ <path
+ sodipodi:nodetypes="cssssccsssss"
+ id="path3268"
+ d="M 150.00765,459.40263 C 148.72481,459.29089 146.66106,460.46996 147.22973,462.43309 C 147.80762,464.42805 148.15914,462.43221 147.98734,477.0803 C 147.95426,479.99843 145.3357,481.81539 144.95689,485.6666 C 144.57809,489.51781 146.15645,485.60347 146.09331,501.82904 C 146.05081,512.75288 145.46196,540.08857 145.46196,540.08857 L 150.00765,540.08857 C 150.00765,540.08857 149.35018,508.64612 149.3763,496.14693 C 149.3906,489.44397 152.6593,486.36108 152.78557,482.88868 C 152. [...]
+ style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <g
+ id="g4159">
+ <g
+ id="g4151">
+ <path
+ sodipodi:nodetypes="cssssccsssss"
+ id="path3270"
+ d="M 142.43151,459.90771 C 141.14867,459.79597 139.08492,460.97504 139.65359,462.93817 C 140.23148,464.93313 141.59315,461.6746 140.4112,485.6666 C 140.2676,488.58138 137.75956,490.40169 137.38075,494.2529 C 137.00195,498.10411 138.58031,494.18977 138.51717,510.41534 C 138.47467,521.33918 138.3909,540.08857 138.3909,540.08857 L 142.93659,540.08857 C 142.93659,540.08857 141.77404,517.23242 141.80016,504.73323 C 141.81446,498.03027 145.08316,494.94738 145.20943,491.47498 C 1 [...]
+ style="fill:#9ad8e8;fill-opacity:1;fill-rule:evenodd;stroke:#63b7de;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#99b2e9;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path4147"
+ sodipodi:cx="162.25575"
+ sodipodi:cy="474.80746"
+ sodipodi:rx="1.3889598"
+ sodipodi:ry="3.2829959"
+ d="m 163.64471,474.80746 c 0,1.81315 -0.62186,3.283 -1.38896,3.283 -0.7671,0 -1.38896,-1.46985 -1.38896,-3.283 0,-1.81314 0.62186,-3.28299 1.38896,-3.28299 0.7671,0 1.38896,1.46985 1.38896,3.28299 z"
+ transform="matrix(0.96429,0.26485,-0.26485,0.96429,110.0814,-7.582614)" />
+ </g>
+ <g
+ id="g4155">
+ <path
+ style="fill:#9ad8e8;fill-opacity:1;fill-rule:evenodd;stroke:#63b7de;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 150.00765,459.40263 C 148.72481,459.29089 146.66106,460.46996 147.22973,462.43309 C 147.80762,464.42805 148.15914,462.43221 147.98734,477.0803 C 147.95426,479.99843 145.3357,481.81539 144.95689,485.6666 C 144.57809,489.51781 146.15645,485.60347 146.09331,501.82904 C 146.05081,512.75288 145.46196,540.08857 145.46196,540.08857 L 150.00765,540.08857 C 150.00765,540.08857 149.35018,508.64612 149.3763,496.14693 C 149.3906,489.44397 152.6593,486.36108 152.78557,482.88868 C [...]
+ id="path3272"
+ sodipodi:nodetypes="cssssccsssss" />
+ <path
+ transform="matrix(0.992538,0.121935,-0.121935,0.992538,45.59577,-6.518967)"
+ d="m 163.64471,474.80746 c 0,1.81315 -0.62186,3.283 -1.38896,3.283 -0.7671,0 -1.38896,-1.46985 -1.38896,-3.283 0,-1.81314 0.62186,-3.28299 1.38896,-3.28299 0.7671,0 1.38896,1.46985 1.38896,3.28299 z"
+ sodipodi:ry="3.2829959"
+ sodipodi:rx="1.3889598"
+ sodipodi:cy="474.80746"
+ sodipodi:cx="162.25575"
+ id="path4149"
+ style="fill:#99b2e9;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:type="arc" />
+ </g>
+ </g>
+ <g
+ id="g4167"
+ transform="translate(-14,0)">
+ <g
+ id="g4169">
+ <path
+ style="fill:#9ad8e8;fill-opacity:1;fill-rule:evenodd;stroke:#63b7de;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 142.43151,459.90771 C 141.14867,459.79597 139.08492,460.97504 139.65359,462.93817 C 140.23148,464.93313 141.59315,461.6746 140.4112,485.6666 C 140.2676,488.58138 137.75956,490.40169 137.38075,494.2529 C 137.00195,498.10411 138.58031,494.18977 138.51717,510.41534 C 138.47467,521.33918 138.3909,540.08857 138.3909,540.08857 L 142.93659,540.08857 C 142.93659,540.08857 141.77404,517.23242 141.80016,504.73323 C 141.81446,498.03027 145.08316,494.94738 145.20943,491.47498 C 1 [...]
+ id="path4171"
+ sodipodi:nodetypes="cssssccsssss" />
+ <path
+ transform="matrix(0.96429,0.26485,-0.26485,0.96429,110.0814,-7.582614)"
+ d="m 163.64471,474.80746 c 0,1.81315 -0.62186,3.283 -1.38896,3.283 -0.7671,0 -1.38896,-1.46985 -1.38896,-3.283 0,-1.81314 0.62186,-3.28299 1.38896,-3.28299 0.7671,0 1.38896,1.46985 1.38896,3.28299 z"
+ sodipodi:ry="3.2829959"
+ sodipodi:rx="1.3889598"
+ sodipodi:cy="474.80746"
+ sodipodi:cx="162.25575"
+ id="path4173"
+ style="fill:#99b2e9;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:type="arc" />
+ </g>
+ <g
+ id="g4175">
+ <path
+ sodipodi:nodetypes="cssssccsssss"
+ id="path4177"
+ d="M 150.00765,459.40263 C 148.72481,459.29089 146.66106,460.46996 147.22973,462.43309 C 147.80762,464.42805 148.15914,462.43221 147.98734,477.0803 C 147.95426,479.99843 145.3357,481.81539 144.95689,485.6666 C 144.57809,489.51781 146.15645,485.60347 146.09331,501.82904 C 146.05081,512.75288 145.46196,540.08857 145.46196,540.08857 L 150.00765,540.08857 C 150.00765,540.08857 149.35018,508.64612 149.3763,496.14693 C 149.3906,489.44397 152.6593,486.36108 152.78557,482.88868 C [...]
+ style="fill:#9ad8e8;fill-opacity:1;fill-rule:evenodd;stroke:#63b7de;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#99b2e9;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path4179"
+ sodipodi:cx="162.25575"
+ sodipodi:cy="474.80746"
+ sodipodi:rx="1.3889598"
+ sodipodi:ry="3.2829959"
+ d="m 163.64471,474.80746 c 0,1.81315 -0.62186,3.283 -1.38896,3.283 -0.7671,0 -1.38896,-1.46985 -1.38896,-3.283 0,-1.81314 0.62186,-3.28299 1.38896,-3.28299 0.7671,0 1.38896,1.46985 1.38896,3.28299 z"
+ transform="matrix(0.992538,0.121935,-0.121935,0.992538,45.59577,-6.518967)" />
+ </g>
+ </g>
+ <g
+ id="g4181"
+ transform="translate(14,0)">
+ <g
+ id="g4183">
+ <path
+ sodipodi:nodetypes="cssssccsssss"
+ id="path4185"
+ d="M 142.43151,459.90771 C 141.14867,459.79597 139.08492,460.97504 139.65359,462.93817 C 140.23148,464.93313 141.59315,461.6746 140.4112,485.6666 C 140.2676,488.58138 137.75956,490.40169 137.38075,494.2529 C 137.00195,498.10411 138.58031,494.18977 138.51717,510.41534 C 138.47467,521.33918 138.3909,540.08857 138.3909,540.08857 L 142.93659,540.08857 C 142.93659,540.08857 141.77404,517.23242 141.80016,504.73323 C 141.81446,498.03027 145.08316,494.94738 145.20943,491.47498 C 1 [...]
+ style="fill:#9ad8e8;fill-opacity:1;fill-rule:evenodd;stroke:#63b7de;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#99b2e9;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path4187"
+ sodipodi:cx="162.25575"
+ sodipodi:cy="474.80746"
+ sodipodi:rx="1.3889598"
+ sodipodi:ry="3.2829959"
+ d="m 163.64471,474.80746 c 0,1.81315 -0.62186,3.283 -1.38896,3.283 -0.7671,0 -1.38896,-1.46985 -1.38896,-3.283 0,-1.81314 0.62186,-3.28299 1.38896,-3.28299 0.7671,0 1.38896,1.46985 1.38896,3.28299 z"
+ transform="matrix(0.96429,0.26485,-0.26485,0.96429,110.0814,-7.582614)" />
+ </g>
+ <g
+ id="g4189">
+ <path
+ style="fill:#9ad8e8;fill-opacity:1;fill-rule:evenodd;stroke:#63b7de;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 150.00765,459.40263 C 148.72481,459.29089 146.66106,460.46996 147.22973,462.43309 C 147.80762,464.42805 148.15914,462.43221 147.98734,477.0803 C 147.95426,479.99843 145.3357,481.81539 144.95689,485.6666 C 144.57809,489.51781 146.15645,485.60347 146.09331,501.82904 C 146.05081,512.75288 145.46196,540.08857 145.46196,540.08857 L 150.00765,540.08857 C 150.00765,540.08857 149.35018,508.64612 149.3763,496.14693 C 149.3906,489.44397 152.6593,486.36108 152.78557,482.88868 C [...]
+ id="path4191"
+ sodipodi:nodetypes="cssssccsssss" />
+ <path
+ transform="matrix(0.992538,0.121935,-0.121935,0.992538,45.59577,-6.518967)"
+ d="m 163.64471,474.80746 c 0,1.81315 -0.62186,3.283 -1.38896,3.283 -0.7671,0 -1.38896,-1.46985 -1.38896,-3.283 0,-1.81314 0.62186,-3.28299 1.38896,-3.28299 0.7671,0 1.38896,1.46985 1.38896,3.28299 z"
+ sodipodi:ry="3.2829959"
+ sodipodi:rx="1.3889598"
+ sodipodi:cy="474.80746"
+ sodipodi:cx="162.25575"
+ id="path4193"
+ style="fill:#99b2e9;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:type="arc" />
+ </g>
+ </g>
+ <g
+ transform="translate(28,0)"
+ id="g4195">
+ <g
+ id="g4197">
+ <path
+ style="fill:#9ad8e8;fill-opacity:1;fill-rule:evenodd;stroke:#63b7de;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 142.43151,459.90771 C 141.14867,459.79597 139.08492,460.97504 139.65359,462.93817 C 140.23148,464.93313 141.59315,461.6746 140.4112,485.6666 C 140.2676,488.58138 137.75956,490.40169 137.38075,494.2529 C 137.00195,498.10411 138.58031,494.18977 138.51717,510.41534 C 138.47467,521.33918 138.3909,540.08857 138.3909,540.08857 L 142.93659,540.08857 C 142.93659,540.08857 141.77404,517.23242 141.80016,504.73323 C 141.81446,498.03027 145.08316,494.94738 145.20943,491.47498 C 1 [...]
+ id="path4199"
+ sodipodi:nodetypes="cssssccsssss" />
+ <path
+ transform="matrix(0.96429,0.26485,-0.26485,0.96429,110.0814,-7.582614)"
+ d="m 163.64471,474.80746 c 0,1.81315 -0.62186,3.283 -1.38896,3.283 -0.7671,0 -1.38896,-1.46985 -1.38896,-3.283 0,-1.81314 0.62186,-3.28299 1.38896,-3.28299 0.7671,0 1.38896,1.46985 1.38896,3.28299 z"
+ sodipodi:ry="3.2829959"
+ sodipodi:rx="1.3889598"
+ sodipodi:cy="474.80746"
+ sodipodi:cx="162.25575"
+ id="path4201"
+ style="fill:#99b2e9;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:type="arc" />
+ </g>
+ <g
+ id="g4203">
+ <path
+ sodipodi:nodetypes="cssssccsssss"
+ id="path4205"
+ d="M 150.00765,459.40263 C 148.72481,459.29089 146.66106,460.46996 147.22973,462.43309 C 147.80762,464.42805 148.15914,462.43221 147.98734,477.0803 C 147.95426,479.99843 145.3357,481.81539 144.95689,485.6666 C 144.57809,489.51781 146.15645,485.60347 146.09331,501.82904 C 146.05081,512.75288 145.46196,540.08857 145.46196,540.08857 L 150.00765,540.08857 C 150.00765,540.08857 149.35018,508.64612 149.3763,496.14693 C 149.3906,489.44397 152.6593,486.36108 152.78557,482.88868 C [...]
+ style="fill:#9ad8e8;fill-opacity:1;fill-rule:evenodd;stroke:#63b7de;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#99b2e9;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path4207"
+ sodipodi:cx="162.25575"
+ sodipodi:cy="474.80746"
+ sodipodi:rx="1.3889598"
+ sodipodi:ry="3.2829959"
+ d="m 163.64471,474.80746 c 0,1.81315 -0.62186,3.283 -1.38896,3.283 -0.7671,0 -1.38896,-1.46985 -1.38896,-3.283 0,-1.81314 0.62186,-3.28299 1.38896,-3.28299 0.7671,0 1.38896,1.46985 1.38896,3.28299 z"
+ transform="matrix(0.992538,0.121935,-0.121935,0.992538,45.59577,-6.518967)" />
+ </g>
+ </g>
+ <g
+ id="g4209"
+ transform="translate(42.48476,0)">
+ <g
+ id="g4211">
+ <path
+ sodipodi:nodetypes="cssssccsssss"
+ id="path4213"
+ d="M 142.43151,459.90771 C 141.14867,459.79597 139.08492,460.97504 139.65359,462.93817 C 140.23148,464.93313 141.59315,461.6746 140.4112,485.6666 C 140.2676,488.58138 137.75956,490.40169 137.38075,494.2529 C 137.00195,498.10411 138.58031,494.18977 138.51717,510.41534 C 138.47467,521.33918 138.3909,540.08857 138.3909,540.08857 L 142.93659,540.08857 C 142.93659,540.08857 141.77404,517.23242 141.80016,504.73323 C 141.81446,498.03027 145.08316,494.94738 145.20943,491.47498 C 1 [...]
+ style="fill:#9ad8e8;fill-opacity:1;fill-rule:evenodd;stroke:#63b7de;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#99b2e9;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path4215"
+ sodipodi:cx="162.25575"
+ sodipodi:cy="474.80746"
+ sodipodi:rx="1.3889598"
+ sodipodi:ry="3.2829959"
+ d="m 163.64471,474.80746 c 0,1.81315 -0.62186,3.283 -1.38896,3.283 -0.7671,0 -1.38896,-1.46985 -1.38896,-3.283 0,-1.81314 0.62186,-3.28299 1.38896,-3.28299 0.7671,0 1.38896,1.46985 1.38896,3.28299 z"
+ transform="matrix(0.96429,0.26485,-0.26485,0.96429,110.0814,-7.582614)" />
+ </g>
+ <g
+ id="g4217">
+ <path
+ style="fill:#9ad8e8;fill-opacity:1;fill-rule:evenodd;stroke:#63b7de;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 150.00765,459.40263 C 148.72481,459.29089 146.66106,460.46996 147.22973,462.43309 C 147.80762,464.42805 148.15914,462.43221 147.98734,477.0803 C 147.95426,479.99843 145.3357,481.81539 144.95689,485.6666 C 144.57809,489.51781 146.15645,485.60347 146.09331,501.82904 C 146.05081,512.75288 145.46196,540.08857 145.46196,540.08857 L 150.00765,540.08857 C 150.00765,540.08857 149.35018,508.64612 149.3763,496.14693 C 149.3906,489.44397 152.6593,486.36108 152.78557,482.88868 C [...]
+ id="path4219"
+ sodipodi:nodetypes="cssssccsssss" />
+ <path
+ transform="matrix(0.992538,0.121935,-0.121935,0.992538,45.59577,-6.518967)"
+ d="m 163.64471,474.80746 c 0,1.81315 -0.62186,3.283 -1.38896,3.283 -0.7671,0 -1.38896,-1.46985 -1.38896,-3.283 0,-1.81314 0.62186,-3.28299 1.38896,-3.28299 0.7671,0 1.38896,1.46985 1.38896,3.28299 z"
+ sodipodi:ry="3.2829959"
+ sodipodi:rx="1.3889598"
+ sodipodi:cy="474.80746"
+ sodipodi:cx="162.25575"
+ id="path4221"
+ style="fill:#99b2e9;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:type="arc" />
+ </g>
+ </g>
+ <g
+ transform="translate(56.48476,0)"
+ id="g4223">
+ <g
+ id="g4225">
+ <path
+ style="fill:#9ad8e8;fill-opacity:1;fill-rule:evenodd;stroke:#63b7de;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 142.43151,459.90771 C 141.14867,459.79597 139.08492,460.97504 139.65359,462.93817 C 140.23148,464.93313 141.59315,461.6746 140.4112,485.6666 C 140.2676,488.58138 137.75956,490.40169 137.38075,494.2529 C 137.00195,498.10411 138.58031,494.18977 138.51717,510.41534 C 138.47467,521.33918 138.3909,540.08857 138.3909,540.08857 L 142.93659,540.08857 C 142.93659,540.08857 141.77404,517.23242 141.80016,504.73323 C 141.81446,498.03027 145.08316,494.94738 145.20943,491.47498 C 1 [...]
+ id="path4227"
+ sodipodi:nodetypes="cssssccsssss" />
+ <path
+ transform="matrix(0.96429,0.26485,-0.26485,0.96429,110.0814,-7.582614)"
+ d="m 163.64471,474.80746 c 0,1.81315 -0.62186,3.283 -1.38896,3.283 -0.7671,0 -1.38896,-1.46985 -1.38896,-3.283 0,-1.81314 0.62186,-3.28299 1.38896,-3.28299 0.7671,0 1.38896,1.46985 1.38896,3.28299 z"
+ sodipodi:ry="3.2829959"
+ sodipodi:rx="1.3889598"
+ sodipodi:cy="474.80746"
+ sodipodi:cx="162.25575"
+ id="path4229"
+ style="fill:#99b2e9;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:type="arc" />
+ </g>
+ <g
+ id="g4231">
+ <path
+ sodipodi:nodetypes="cssssccsssss"
+ id="path4233"
+ d="M 150.00765,459.40263 C 148.72481,459.29089 146.66106,460.46996 147.22973,462.43309 C 147.80762,464.42805 148.15914,462.43221 147.98734,477.0803 C 147.95426,479.99843 145.3357,481.81539 144.95689,485.6666 C 144.57809,489.51781 146.15645,485.60347 146.09331,501.82904 C 146.05081,512.75288 145.46196,540.08857 145.46196,540.08857 L 150.00765,540.08857 C 150.00765,540.08857 149.35018,508.64612 149.3763,496.14693 C 149.3906,489.44397 152.6593,486.36108 152.78557,482.88868 C [...]
+ style="fill:#9ad8e8;fill-opacity:1;fill-rule:evenodd;stroke:#63b7de;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#99b2e9;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path4235"
+ sodipodi:cx="162.25575"
+ sodipodi:cy="474.80746"
+ sodipodi:rx="1.3889598"
+ sodipodi:ry="3.2829959"
+ d="m 163.64471,474.80746 c 0,1.81315 -0.62186,3.283 -1.38896,3.283 -0.7671,0 -1.38896,-1.46985 -1.38896,-3.283 0,-1.81314 0.62186,-3.28299 1.38896,-3.28299 0.7671,0 1.38896,1.46985 1.38896,3.28299 z"
+ transform="matrix(0.992538,0.121935,-0.121935,0.992538,45.59577,-6.518967)" />
+ </g>
+ </g>
+ <g
+ id="g4237"
+ transform="translate(70.48476,0)">
+ <g
+ id="g4239">
+ <path
+ sodipodi:nodetypes="cssssccsssss"
+ id="path4241"
+ d="M 142.43151,459.90771 C 141.14867,459.79597 139.08492,460.97504 139.65359,462.93817 C 140.23148,464.93313 141.59315,461.6746 140.4112,485.6666 C 140.2676,488.58138 137.75956,490.40169 137.38075,494.2529 C 137.00195,498.10411 138.58031,494.18977 138.51717,510.41534 C 138.47467,521.33918 138.3909,540.08857 138.3909,540.08857 L 142.93659,540.08857 C 142.93659,540.08857 141.77404,517.23242 141.80016,504.73323 C 141.81446,498.03027 145.08316,494.94738 145.20943,491.47498 C 1 [...]
+ style="fill:#9ad8e8;fill-opacity:1;fill-rule:evenodd;stroke:#63b7de;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#99b2e9;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path4243"
+ sodipodi:cx="162.25575"
+ sodipodi:cy="474.80746"
+ sodipodi:rx="1.3889598"
+ sodipodi:ry="3.2829959"
+ d="m 163.64471,474.80746 c 0,1.81315 -0.62186,3.283 -1.38896,3.283 -0.7671,0 -1.38896,-1.46985 -1.38896,-3.283 0,-1.81314 0.62186,-3.28299 1.38896,-3.28299 0.7671,0 1.38896,1.46985 1.38896,3.28299 z"
+ transform="matrix(0.96429,0.26485,-0.26485,0.96429,110.0814,-7.582614)" />
+ </g>
+ <g
+ id="g4245">
+ <path
+ style="fill:#9ad8e8;fill-opacity:1;fill-rule:evenodd;stroke:#63b7de;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 150.00765,459.40263 C 148.72481,459.29089 146.66106,460.46996 147.22973,462.43309 C 147.80762,464.42805 148.15914,462.43221 147.98734,477.0803 C 147.95426,479.99843 145.3357,481.81539 144.95689,485.6666 C 144.57809,489.51781 146.15645,485.60347 146.09331,501.82904 C 146.05081,512.75288 145.46196,540.08857 145.46196,540.08857 L 150.00765,540.08857 C 150.00765,540.08857 149.35018,508.64612 149.3763,496.14693 C 149.3906,489.44397 152.6593,486.36108 152.78557,482.88868 C [...]
+ id="path4247"
+ sodipodi:nodetypes="cssssccsssss" />
+ <path
+ transform="matrix(0.992538,0.121935,-0.121935,0.992538,45.59577,-6.518967)"
+ d="m 163.64471,474.80746 c 0,1.81315 -0.62186,3.283 -1.38896,3.283 -0.7671,0 -1.38896,-1.46985 -1.38896,-3.283 0,-1.81314 0.62186,-3.28299 1.38896,-3.28299 0.7671,0 1.38896,1.46985 1.38896,3.28299 z"
+ sodipodi:ry="3.2829959"
+ sodipodi:rx="1.3889598"
+ sodipodi:cy="474.80746"
+ sodipodi:cx="162.25575"
+ id="path4249"
+ style="fill:#99b2e9;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:type="arc" />
+ </g>
+ </g>
+ <g
+ transform="translate(84.48476,0)"
+ id="g4251">
+ <g
+ id="g4253">
+ <path
+ style="fill:#9ad8e8;fill-opacity:1;fill-rule:evenodd;stroke:#63b7de;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 142.43151,459.90771 C 141.14867,459.79597 139.08492,460.97504 139.65359,462.93817 C 140.23148,464.93313 141.59315,461.6746 140.4112,485.6666 C 140.2676,488.58138 137.75956,490.40169 137.38075,494.2529 C 137.00195,498.10411 138.58031,494.18977 138.51717,510.41534 C 138.47467,521.33918 138.3909,540.08857 138.3909,540.08857 L 142.93659,540.08857 C 142.93659,540.08857 141.77404,517.23242 141.80016,504.73323 C 141.81446,498.03027 145.08316,494.94738 145.20943,491.47498 C 1 [...]
+ id="path4255"
+ sodipodi:nodetypes="cssssccsssss" />
+ <path
+ transform="matrix(0.96429,0.26485,-0.26485,0.96429,110.0814,-7.582614)"
+ d="m 163.64471,474.80746 c 0,1.81315 -0.62186,3.283 -1.38896,3.283 -0.7671,0 -1.38896,-1.46985 -1.38896,-3.283 0,-1.81314 0.62186,-3.28299 1.38896,-3.28299 0.7671,0 1.38896,1.46985 1.38896,3.28299 z"
+ sodipodi:ry="3.2829959"
+ sodipodi:rx="1.3889598"
+ sodipodi:cy="474.80746"
+ sodipodi:cx="162.25575"
+ id="path4257"
+ style="fill:#99b2e9;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:type="arc" />
+ </g>
+ <g
+ id="g4259">
+ <path
+ sodipodi:nodetypes="cssssccsssss"
+ id="path4261"
+ d="M 150.00765,459.40263 C 148.72481,459.29089 146.66106,460.46996 147.22973,462.43309 C 147.80762,464.42805 148.15914,462.43221 147.98734,477.0803 C 147.95426,479.99843 145.3357,481.81539 144.95689,485.6666 C 144.57809,489.51781 146.15645,485.60347 146.09331,501.82904 C 146.05081,512.75288 145.46196,540.08857 145.46196,540.08857 L 150.00765,540.08857 C 150.00765,540.08857 149.35018,508.64612 149.3763,496.14693 C 149.3906,489.44397 152.6593,486.36108 152.78557,482.88868 C [...]
+ style="fill:#9ad8e8;fill-opacity:1;fill-rule:evenodd;stroke:#63b7de;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#99b2e9;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path4263"
+ sodipodi:cx="162.25575"
+ sodipodi:cy="474.80746"
+ sodipodi:rx="1.3889598"
+ sodipodi:ry="3.2829959"
+ d="m 163.64471,474.80746 c 0,1.81315 -0.62186,3.283 -1.38896,3.283 -0.7671,0 -1.38896,-1.46985 -1.38896,-3.283 0,-1.81314 0.62186,-3.28299 1.38896,-3.28299 0.7671,0 1.38896,1.46985 1.38896,3.28299 z"
+ transform="matrix(0.992538,0.121935,-0.121935,0.992538,45.59577,-6.518967)" />
+ </g>
+ </g>
+ <g
+ id="g4265"
+ transform="translate(98.48476,0)">
+ <g
+ id="g4267">
+ <path
+ sodipodi:nodetypes="cssssccsssss"
+ id="path4269"
+ d="M 142.43151,459.90771 C 141.14867,459.79597 139.08492,460.97504 139.65359,462.93817 C 140.23148,464.93313 141.59315,461.6746 140.4112,485.6666 C 140.2676,488.58138 137.75956,490.40169 137.38075,494.2529 C 137.00195,498.10411 138.58031,494.18977 138.51717,510.41534 C 138.47467,521.33918 138.3909,540.08857 138.3909,540.08857 L 142.93659,540.08857 C 142.93659,540.08857 141.77404,517.23242 141.80016,504.73323 C 141.81446,498.03027 145.08316,494.94738 145.20943,491.47498 C 1 [...]
+ style="fill:#9ad8e8;fill-opacity:1;fill-rule:evenodd;stroke:#63b7de;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#99b2e9;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path4271"
+ sodipodi:cx="162.25575"
+ sodipodi:cy="474.80746"
+ sodipodi:rx="1.3889598"
+ sodipodi:ry="3.2829959"
+ d="m 163.64471,474.80746 c 0,1.81315 -0.62186,3.283 -1.38896,3.283 -0.7671,0 -1.38896,-1.46985 -1.38896,-3.283 0,-1.81314 0.62186,-3.28299 1.38896,-3.28299 0.7671,0 1.38896,1.46985 1.38896,3.28299 z"
+ transform="matrix(0.96429,0.26485,-0.26485,0.96429,110.0814,-7.582614)" />
+ </g>
+ <g
+ id="g4273">
+ <path
+ style="fill:#9ad8e8;fill-opacity:1;fill-rule:evenodd;stroke:#63b7de;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 150.00765,459.40263 C 148.72481,459.29089 146.66106,460.46996 147.22973,462.43309 C 147.80762,464.42805 148.15914,462.43221 147.98734,477.0803 C 147.95426,479.99843 145.3357,481.81539 144.95689,485.6666 C 144.57809,489.51781 146.15645,485.60347 146.09331,501.82904 C 146.05081,512.75288 145.46196,540.08857 145.46196,540.08857 L 150.00765,540.08857 C 150.00765,540.08857 149.35018,508.64612 149.3763,496.14693 C 149.3906,489.44397 152.6593,486.36108 152.78557,482.88868 C [...]
+ id="path4275"
+ sodipodi:nodetypes="cssssccsssss" />
+ <path
+ transform="matrix(0.992538,0.121935,-0.121935,0.992538,45.59577,-6.518967)"
+ d="m 163.64471,474.80746 c 0,1.81315 -0.62186,3.283 -1.38896,3.283 -0.7671,0 -1.38896,-1.46985 -1.38896,-3.283 0,-1.81314 0.62186,-3.28299 1.38896,-3.28299 0.7671,0 1.38896,1.46985 1.38896,3.28299 z"
+ sodipodi:ry="3.2829959"
+ sodipodi:rx="1.3889598"
+ sodipodi:cy="474.80746"
+ sodipodi:cx="162.25575"
+ id="path4277"
+ style="fill:#99b2e9;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:type="arc" />
+ </g>
+ </g>
+ <g
+ id="g4281"
+ transform="translate(112.4848,0)">
+ <path
+ sodipodi:nodetypes="cssssccsssss"
+ id="path4283"
+ d="M 142.43151,459.90771 C 141.14867,459.79597 139.08492,460.97504 139.65359,462.93817 C 140.23148,464.93313 141.59315,461.6746 140.4112,485.6666 C 140.2676,488.58138 137.75956,490.40169 137.38075,494.2529 C 137.00195,498.10411 138.58031,494.18977 138.51717,510.41534 C 138.47467,521.33918 138.3909,540.08857 138.3909,540.08857 L 142.93659,540.08857 C 142.93659,540.08857 141.77404,517.23242 141.80016,504.73323 C 141.81446,498.03027 145.08316,494.94738 145.20943,491.47498 C 145 [...]
+ style="fill:#9ad8e8;fill-opacity:1;fill-rule:evenodd;stroke:#63b7de;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#99b2e9;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path4285"
+ sodipodi:cx="162.25575"
+ sodipodi:cy="474.80746"
+ sodipodi:rx="1.3889598"
+ sodipodi:ry="3.2829959"
+ d="m 163.64471,474.80746 c 0,1.81315 -0.62186,3.283 -1.38896,3.283 -0.7671,0 -1.38896,-1.46985 -1.38896,-3.283 0,-1.81314 0.62186,-3.28299 1.38896,-3.28299 0.7671,0 1.38896,1.46985 1.38896,3.28299 z"
+ transform="matrix(0.96429,0.26485,-0.26485,0.96429,110.0814,-7.582614)" />
+ </g>
+ <g
+ id="g4287"
+ transform="translate(112.4848,0)">
+ <path
+ style="fill:#9ad8e8;fill-opacity:1;fill-rule:evenodd;stroke:#63b7de;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 150.00765,459.40263 C 148.72481,459.29089 146.66106,460.46996 147.22973,462.43309 C 147.80762,464.42805 148.15914,462.43221 147.98734,477.0803 C 147.95426,479.99843 145.3357,481.81539 144.95689,485.6666 C 144.57809,489.51781 146.15645,485.60347 146.09331,501.82904 C 146.05081,512.75288 145.46196,540.08857 145.46196,540.08857 L 150.00765,540.08857 C 150.00765,540.08857 149.35018,508.64612 149.3763,496.14693 C 149.3906,489.44397 152.6593,486.36108 152.78557,482.88868 C 15 [...]
+ id="path4289"
+ sodipodi:nodetypes="cssssccsssss" />
+ <path
+ transform="matrix(0.992538,0.121935,-0.121935,0.992538,45.59577,-6.518967)"
+ d="m 163.64471,474.80746 c 0,1.81315 -0.62186,3.283 -1.38896,3.283 -0.7671,0 -1.38896,-1.46985 -1.38896,-3.283 0,-1.81314 0.62186,-3.28299 1.38896,-3.28299 0.7671,0 1.38896,1.46985 1.38896,3.28299 z"
+ sodipodi:ry="3.2829959"
+ sodipodi:rx="1.3889598"
+ sodipodi:cy="474.80746"
+ sodipodi:cx="162.25575"
+ id="path4291"
+ style="fill:#99b2e9;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:type="arc" />
+ </g>
+ <g
+ id="g4537">
+ <path
+ style="fill:#9ad8e8;fill-opacity:1;fill-rule:evenodd;stroke:#63b7de;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 265.16631,455.97914 C 263.88347,456.76026 264.85543,457.93933 265.4241,459.90246 C 266.00199,461.89742 269.14938,457.21031 267.96743,481.20231 C 267.82383,484.11709 264.24436,490.40169 263.86555,494.2529 C 263.48675,498.10411 265.06511,494.18977 265.00197,510.41534 C 264.95947,521.33918 264.8757,540.08857 264.8757,540.08857 L 269.42139,540.08857 C 269.42139,540.08857 268.25884,517.23242 268.28496,504.73323 C 268.29926,498.03027 271.56796,494.94738 271.69423,491.47498 C [...]
+ id="path4439"
+ sodipodi:nodetypes="cssssccsssss" />
+ <path
+ transform="matrix(0.96429,0.26485,-0.26485,0.96429,236.5662,-7.582614)"
+ d="m 163.64471,474.80746 c 0,1.81315 -0.62186,3.283 -1.38896,3.283 -0.7671,0 -1.38896,-1.46985 -1.38896,-3.283 0,-1.81314 0.62186,-3.28299 1.38896,-3.28299 0.7671,0 1.38896,1.46985 1.38896,3.28299 z"
+ sodipodi:ry="3.2829959"
+ sodipodi:rx="1.3889598"
+ sodipodi:cy="474.80746"
+ sodipodi:cx="162.25575"
+ id="path4441"
+ style="fill:#99b2e9;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:type="arc" />
+ <path
+ sodipodi:nodetypes="cssssccsssss"
+ id="path4445"
+ d="M 267.38531,452.79549 C 265.90734,455.63745 268.14586,454.93425 269.60739,456.18309 C 271.18642,457.53234 280.35823,463.86078 276.79357,475.11601 C 275.91244,477.89813 271.8205,481.81539 271.44169,485.6666 C 271.06289,489.51781 272.64125,485.60347 272.57811,501.82904 C 272.53561,512.75288 271.94676,540.08857 271.94676,540.08857 L 276.49245,540.08857 C 276.49245,540.08857 275.83498,508.64612 275.8611,496.14693 C 275.8754,489.44397 279.1441,486.36108 279.27037,482.88868 C 2 [...]
+ style="fill:#9ad8e8;fill-opacity:1;fill-rule:evenodd;stroke:#63b7de;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#99b2e9;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path4447"
+ sodipodi:cx="162.25575"
+ sodipodi:cy="474.80746"
+ sodipodi:rx="1.3889598"
+ sodipodi:ry="3.2829959"
+ d="m 163.64471,474.80746 c 0,1.81315 -0.62186,3.283 -1.38896,3.283 -0.7671,0 -1.38896,-1.46985 -1.38896,-3.283 0,-1.81314 0.62186,-3.28299 1.38896,-3.28299 0.7671,0 1.38896,1.46985 1.38896,3.28299 z"
+ transform="matrix(0.992538,0.121935,-0.121935,0.992538,172.0806,-6.518967)" />
+ <path
+ sodipodi:nodetypes="cssssccsssss"
+ id="path4453"
+ d="M 271.66631,450.622 C 270.46807,452.42636 272.87329,453.1179 273.70982,453.65246 C 275.45997,454.77084 289.22081,461.13889 281.96743,481.55946 C 280.99063,484.30945 278.24436,490.40169 277.86555,494.2529 C 277.48675,498.10411 279.06511,494.18977 279.00197,510.41534 C 278.95947,521.33918 278.8757,540.08857 278.8757,540.08857 L 283.42139,540.08857 C 283.42139,540.08857 282.25884,517.23242 282.28496,504.73323 C 282.29926,498.03027 285.56796,494.94738 285.69423,491.47498 C 28 [...]
+ style="fill:#9ad8e8;fill-opacity:1;fill-rule:evenodd;stroke:#63b7de;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#99b2e9;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path4455"
+ sodipodi:cx="162.25575"
+ sodipodi:cy="474.80746"
+ sodipodi:rx="1.3889598"
+ sodipodi:ry="3.2829959"
+ d="m 163.64471,474.80746 c 0,1.81315 -0.62186,3.283 -1.38896,3.283 -0.7671,0 -1.38896,-1.46985 -1.38896,-3.283 0,-1.81314 0.62186,-3.28299 1.38896,-3.28299 0.7671,0 1.38896,1.46985 1.38896,3.28299 z"
+ transform="matrix(0.96429,0.26485,-0.26485,0.96429,250.5662,-7.582614)" />
+ <path
+ style="fill:#9ad8e8;fill-opacity:1;fill-rule:evenodd;stroke:#63b7de;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 281.02345,453.83628 C 280.27633,454.6174 283.10556,455.2898 283.51339,455.92924 C 285.81812,459.5429 292.86366,462.38889 294.896,485.6666 C 295.14983,488.57386 292.24436,490.40169 291.86555,494.2529 C 291.48675,498.10411 293.06511,494.18977 293.00197,510.41534 C 292.95947,521.33918 292.8757,540.08857 292.8757,540.08857 L 297.42139,540.08857 C 297.42139,540.08857 296.25884,517.23242 296.28496,504.73323 C 296.29926,498.03027 299.56796,494.94738 299.69423,491.47498 C 299.8 [...]
+ id="path4467"
+ sodipodi:nodetypes="cssssccsssss" />
+ <path
+ transform="matrix(0.96429,0.26485,-0.26485,0.96429,264.5662,-7.582614)"
+ d="m 163.64471,474.80746 c 0,1.81315 -0.62186,3.283 -1.38896,3.283 -0.7671,0 -1.38896,-1.46985 -1.38896,-3.283 0,-1.81314 0.62186,-3.28299 1.38896,-3.28299 0.7671,0 1.38896,1.46985 1.38896,3.28299 z"
+ sodipodi:ry="3.2829959"
+ sodipodi:rx="1.3889598"
+ sodipodi:cy="474.80746"
+ sodipodi:cx="162.25575"
+ id="path4469"
+ style="fill:#99b2e9;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:type="arc" />
+ <path
+ style="fill:#9ad8e8;fill-opacity:1;fill-rule:evenodd;stroke:#63b7de;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 276.92102,449.40263 C 275.71262,450.23469 277.08771,452.14078 278.42882,452.25452 C 280.11987,452.39793 292.92965,460.28935 290.615,475.47316 C 290.1752,478.35815 285.8205,481.81539 285.44169,485.6666 C 285.06289,489.51781 286.64125,485.60347 286.57811,501.82904 C 286.53561,512.75288 285.94676,540.08857 285.94676,540.08857 L 290.49245,540.08857 C 290.49245,540.08857 289.83498,508.64612 289.8611,496.14693 C 289.8754,489.44397 293.1441,486.36108 293.27037,482.88868 C 293. [...]
+ id="path4459"
+ sodipodi:nodetypes="cssssccsssss" />
+ <path
+ transform="matrix(0.992538,0.121935,-0.121935,0.992538,186.0806,-6.518967)"
+ d="m 163.64471,474.80746 c 0,1.81315 -0.62186,3.283 -1.38896,3.283 -0.7671,0 -1.38896,-1.46985 -1.38896,-3.283 0,-1.81314 0.62186,-3.28299 1.38896,-3.28299 0.7671,0 1.38896,1.46985 1.38896,3.28299 z"
+ sodipodi:ry="3.2829959"
+ sodipodi:rx="1.3889598"
+ sodipodi:cy="474.80746"
+ sodipodi:cx="162.25575"
+ id="path4461"
+ style="fill:#99b2e9;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:type="arc" />
+ </g>
+ </g>
+ <g
+ transform="matrix(0.3840236,0,0,0.3840236,103.53412,157.89079)"
+ id="g5830">
+ <g
+ id="g5578"
+ transform="matrix(0.926041,-0.377423,0.377423,0.926041,-92.20903,15.91881)">
+ <path
+ sodipodi:nodetypes="cssssccsssss"
+ id="path5580"
+ d="M 149.20408,491.18834 C 147.92124,491.0766 145.85749,492.25567 146.42616,494.2188 C 147.00405,496.21376 147.802,493.86078 147.6302,508.50887 C 147.59712,511.427 144.97856,513.24396 144.59975,517.09517 C 144.22095,520.94638 146.17783,517.03793 145.73617,533.25761 C 145.63417,537.00353 145.4933,537.99127 145.4933,537.99127 L 150.00765,540.08857 C 150.00765,540.08857 148.94595,537.79103 149.01916,527.5755 C 149.0672,520.8727 152.30216,517.78965 152.42843,514.31725 C 152.5547 [...]
+ style="fill:#e8c69a;fill-opacity:1;fill-rule:evenodd;stroke:#deb463;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#d6a33f;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path5582"
+ sodipodi:cx="162.25575"
+ sodipodi:cy="474.80746"
+ sodipodi:rx="1.3889598"
+ sodipodi:ry="3.2829959"
+ d="m 163.64471,474.80746 c 0,1.81315 -0.62186,3.283 -1.38896,3.283 -0.7671,0 -1.38896,-1.46985 -1.38896,-3.283 0,-1.81314 0.62186,-3.28299 1.38896,-3.28299 0.7671,0 1.38896,1.46985 1.38896,3.28299 z"
+ transform="matrix(0.992538,0.121935,-0.121935,0.992538,45.06006,24.9096)" />
+ </g>
+ <g
+ id="g5464"
+ transform="translate(1.617597,3.078398)">
+ <g
+ transform="translate(-14,-84)"
+ id="g4573">
+ <path
+ sodipodi:nodetypes="cssssccsssss"
+ id="path4575"
+ d="M 142.43151,490.97914 C 141.14867,490.8674 139.08492,492.04647 139.65359,494.0096 C 140.23148,496.00456 140.87886,494.62103 140.4112,501.73803 C 140.21985,504.65006 137.75956,506.47312 137.38075,510.32433 C 137.00195,514.17554 138.58031,510.2612 138.51717,526.48677 C 138.47467,537.41061 138.3909,540.08857 138.3909,540.08857 L 142.93659,540.08857 C 142.93659,540.08857 141.77404,533.30385 141.80016,520.80466 C 141.81446,514.1017 145.08316,511.01881 145.20943,507.54641 C 1 [...]
+ style="fill:#e8c69a;fill-opacity:1;fill-rule:evenodd;stroke:#deb463;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#d6a33f;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path4577"
+ sodipodi:cx="162.25575"
+ sodipodi:cy="474.80746"
+ sodipodi:rx="1.3889598"
+ sodipodi:ry="3.2829959"
+ d="m 163.64471,474.80746 c 0,1.81315 -0.62186,3.283 -1.38896,3.283 -0.7671,0 -1.38896,-1.46985 -1.38896,-3.283 0,-1.81314 0.62186,-3.28299 1.38896,-3.28299 0.7671,0 1.38896,1.46985 1.38896,3.28299 z"
+ transform="matrix(0.96429,0.26485,-0.26485,0.96429,110.26,9.292386)" />
+ </g>
+ <g
+ transform="translate(-14,-84)"
+ id="g4579">
+ <path
+ style="fill:#e8c69a;fill-opacity:1;fill-rule:evenodd;stroke:#deb463;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 149.20408,491.18834 C 147.92124,491.0766 145.85749,492.25567 146.42616,494.2188 C 147.00405,496.21376 147.802,493.86078 147.6302,508.50887 C 147.59712,511.427 144.97856,513.24396 144.59975,517.09517 C 144.22095,520.94638 145.86628,517.03244 145.73617,533.25761 C 145.69367,538.36706 145.46196,540.08857 145.46196,540.08857 L 150.00765,540.08857 C 150.00765,540.08857 148.89772,537.78706 149.01916,527.5755 C 149.09887,520.873 152.30216,517.78965 152.42843,514.31725 C 152. [...]
+ id="path4581"
+ sodipodi:nodetypes="cssssccsssss" />
+ <path
+ transform="matrix(0.992538,0.121935,-0.121935,0.992538,45.06006,24.9096)"
+ d="m 163.64471,474.80746 c 0,1.81315 -0.62186,3.283 -1.38896,3.283 -0.7671,0 -1.38896,-1.46985 -1.38896,-3.283 0,-1.81314 0.62186,-3.28299 1.38896,-3.28299 0.7671,0 1.38896,1.46985 1.38896,3.28299 z"
+ sodipodi:ry="3.2829959"
+ sodipodi:rx="1.3889598"
+ sodipodi:cy="474.80746"
+ sodipodi:cx="162.25575"
+ id="path4583"
+ style="fill:#d6a33f;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:type="arc" />
+ </g>
+ </g>
+ <g
+ transform="translate(16.1568,3.078398)"
+ id="g5472">
+ <g
+ id="g5474"
+ transform="translate(-14,-84)">
+ <path
+ style="fill:#e8c69a;fill-opacity:1;fill-rule:evenodd;stroke:#deb463;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 142.43151,490.97914 C 141.14867,490.8674 139.08492,492.04647 139.65359,494.0096 C 140.23148,496.00456 140.87886,494.62103 140.4112,501.73803 C 140.21985,504.65006 137.75956,506.47312 137.38075,510.32433 C 137.00195,514.17554 138.58031,510.2612 138.51717,526.48677 C 138.47467,537.41061 138.3909,540.08857 138.3909,540.08857 L 142.93659,540.08857 C 142.93659,540.08857 141.77404,533.30385 141.80016,520.80466 C 141.81446,514.1017 145.08316,511.01881 145.20943,507.54641 C 1 [...]
+ id="path5476"
+ sodipodi:nodetypes="cssssccsssss" />
+ <path
+ transform="matrix(0.96429,0.26485,-0.26485,0.96429,110.26,9.292386)"
+ d="m 163.64471,474.80746 c 0,1.81315 -0.62186,3.283 -1.38896,3.283 -0.7671,0 -1.38896,-1.46985 -1.38896,-3.283 0,-1.81314 0.62186,-3.28299 1.38896,-3.28299 0.7671,0 1.38896,1.46985 1.38896,3.28299 z"
+ sodipodi:ry="3.2829959"
+ sodipodi:rx="1.3889598"
+ sodipodi:cy="474.80746"
+ sodipodi:cx="162.25575"
+ id="path5478"
+ style="fill:#d6a33f;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:type="arc" />
+ </g>
+ <g
+ id="g5480"
+ transform="translate(-14,-84)">
+ <path
+ sodipodi:nodetypes="cssssccsssss"
+ id="path5482"
+ d="M 149.20408,491.18834 C 147.92124,491.0766 145.85749,492.25567 146.42616,494.2188 C 147.00405,496.21376 147.802,493.86078 147.6302,508.50887 C 147.59712,511.427 144.97856,513.24396 144.59975,517.09517 C 144.22095,520.94638 145.54731,517.03302 145.73617,533.25761 C 145.78899,537.60452 145.46196,540.08857 145.46196,540.08857 L 150.00765,540.08857 C 150.00765,540.08857 149.18368,538.35897 149.01916,527.5755 C 148.91691,520.8733 152.30216,517.78965 152.42843,514.31725 C 152 [...]
+ style="fill:#e8c69a;fill-opacity:1;fill-rule:evenodd;stroke:#deb463;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#d6a33f;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path5484"
+ sodipodi:cx="162.25575"
+ sodipodi:cy="474.80746"
+ sodipodi:rx="1.3889598"
+ sodipodi:ry="3.2829959"
+ d="m 163.64471,474.80746 c 0,1.81315 -0.62186,3.283 -1.38896,3.283 -0.7671,0 -1.38896,-1.46985 -1.38896,-3.283 0,-1.81314 0.62186,-3.28299 1.38896,-3.28299 0.7671,0 1.38896,1.46985 1.38896,3.28299 z"
+ transform="matrix(0.992538,0.121935,-0.121935,0.992538,45.06006,24.9096)" />
+ </g>
+ </g>
+ <g
+ id="g5486"
+ transform="translate(30.1568,3.078398)">
+ <g
+ transform="translate(-14,-84)"
+ id="g5488">
+ <path
+ sodipodi:nodetypes="cssssccsssss"
+ id="path5490"
+ d="M 142.43151,490.97914 C 141.14867,490.8674 139.08492,492.04647 139.65359,494.0096 C 140.23148,496.00456 140.87886,494.62103 140.4112,501.73803 C 140.21985,504.65006 137.75956,506.47312 137.38075,510.32433 C 137.00195,514.17554 138.58031,510.2612 138.51717,526.48677 C 138.47467,537.41061 138.3909,540.08857 138.3909,540.08857 L 142.93659,540.08857 C 142.93659,540.08857 141.77404,533.30385 141.80016,520.80466 C 141.81446,514.1017 145.08316,511.01881 145.20943,507.54641 C 1 [...]
+ style="fill:#e8c69a;fill-opacity:1;fill-rule:evenodd;stroke:#deb463;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#d6a33f;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path5492"
+ sodipodi:cx="162.25575"
+ sodipodi:cy="474.80746"
+ sodipodi:rx="1.3889598"
+ sodipodi:ry="3.2829959"
+ d="m 163.64471,474.80746 c 0,1.81315 -0.62186,3.283 -1.38896,3.283 -0.7671,0 -1.38896,-1.46985 -1.38896,-3.283 0,-1.81314 0.62186,-3.28299 1.38896,-3.28299 0.7671,0 1.38896,1.46985 1.38896,3.28299 z"
+ transform="matrix(0.96429,0.26485,-0.26485,0.96429,110.26,9.292386)" />
+ </g>
+ <g
+ transform="translate(-14,-84)"
+ id="g5494">
+ <path
+ style="fill:#e8c69a;fill-opacity:1;fill-rule:evenodd;stroke:#deb463;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 149.20408,491.18834 C 147.92124,491.0766 145.85749,492.25567 146.42616,494.2188 C 147.00405,496.21376 147.802,493.86078 147.6302,508.50887 C 147.59712,511.427 144.97856,513.24396 144.59975,517.09517 C 144.22095,520.94638 145.88814,517.03263 145.73617,533.25761 C 145.69367,537.60452 145.46196,540.08857 145.46196,540.08857 L 150.00765,540.08857 C 150.00765,540.08857 149.18368,537.9777 149.01916,527.5755 C 148.91507,520.87333 152.30216,517.78965 152.42843,514.31725 C 152 [...]
+ id="path5496"
+ sodipodi:nodetypes="cssssccsssss" />
+ <path
+ transform="matrix(0.992538,0.121935,-0.121935,0.992538,45.06006,24.9096)"
+ d="m 163.64471,474.80746 c 0,1.81315 -0.62186,3.283 -1.38896,3.283 -0.7671,0 -1.38896,-1.46985 -1.38896,-3.283 0,-1.81314 0.62186,-3.28299 1.38896,-3.28299 0.7671,0 1.38896,1.46985 1.38896,3.28299 z"
+ sodipodi:ry="3.2829959"
+ sodipodi:rx="1.3889598"
+ sodipodi:cy="474.80746"
+ sodipodi:cx="162.25575"
+ id="path5498"
+ style="fill:#d6a33f;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:type="arc" />
+ </g>
+ </g>
+ <g
+ transform="translate(44.1568,3.078398)"
+ id="g5500">
+ <g
+ id="g5502"
+ transform="translate(-14,-84)">
+ <path
+ style="fill:#e8c69a;fill-opacity:1;fill-rule:evenodd;stroke:#deb463;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 142.43151,490.97914 C 141.14867,490.8674 139.08492,492.04647 139.65359,494.0096 C 140.23148,496.00456 140.87886,494.62103 140.4112,501.73803 C 140.21985,504.65006 137.75956,506.47312 137.38075,510.32433 C 137.00195,514.17554 138.58031,510.2612 138.51717,526.48677 C 138.47467,537.41061 138.3909,540.08857 138.3909,540.08857 L 142.93659,540.08857 C 142.93659,540.08857 141.77404,533.30385 141.80016,520.80466 C 141.81446,514.1017 145.08316,511.01881 145.20943,507.54641 C 1 [...]
+ id="path5504"
+ sodipodi:nodetypes="cssssccsssss" />
+ <path
+ transform="matrix(0.96429,0.26485,-0.26485,0.96429,110.26,9.292386)"
+ d="m 163.64471,474.80746 c 0,1.81315 -0.62186,3.283 -1.38896,3.283 -0.7671,0 -1.38896,-1.46985 -1.38896,-3.283 0,-1.81314 0.62186,-3.28299 1.38896,-3.28299 0.7671,0 1.38896,1.46985 1.38896,3.28299 z"
+ sodipodi:ry="3.2829959"
+ sodipodi:rx="1.3889598"
+ sodipodi:cy="474.80746"
+ sodipodi:cx="162.25575"
+ id="path5506"
+ style="fill:#d6a33f;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:type="arc" />
+ </g>
+ <g
+ id="g5508"
+ transform="translate(-14,-84)">
+ <path
+ sodipodi:nodetypes="cssssccsssss"
+ id="path5510"
+ d="M 149.20408,491.18834 C 147.92124,491.0766 145.85749,492.25567 146.42616,494.2188 C 147.00405,496.21376 147.802,493.86078 147.6302,508.50887 C 147.59712,511.427 144.97856,513.24396 144.59975,517.09517 C 144.22095,520.94638 146.26186,517.04044 145.73617,533.25761 C 145.59835,537.5092 145.46196,540.08857 145.46196,540.08857 L 150.00765,540.08857 C 150.00765,540.08857 149.18368,537.21516 149.01916,527.5755 C 148.907,520.87346 152.30216,517.78965 152.42843,514.31725 C 152.5 [...]
+ style="fill:#e8c69a;fill-opacity:1;fill-rule:evenodd;stroke:#deb463;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#d6a33f;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path5512"
+ sodipodi:cx="162.25575"
+ sodipodi:cy="474.80746"
+ sodipodi:rx="1.3889598"
+ sodipodi:ry="3.2829959"
+ d="m 163.64471,474.80746 c 0,1.81315 -0.62186,3.283 -1.38896,3.283 -0.7671,0 -1.38896,-1.46985 -1.38896,-3.283 0,-1.81314 0.62186,-3.28299 1.38896,-3.28299 0.7671,0 1.38896,1.46985 1.38896,3.28299 z"
+ transform="matrix(0.992538,0.121935,-0.121935,0.992538,45.06006,24.9096)" />
+ </g>
+ </g>
+ <g
+ id="g5514"
+ transform="translate(58.1568,3.078398)">
+ <g
+ transform="translate(-14,-84)"
+ id="g5516">
+ <path
+ sodipodi:nodetypes="cssssccsssss"
+ id="path5518"
+ d="M 142.43151,490.97914 C 141.14867,490.8674 139.08492,492.04647 139.65359,494.0096 C 140.23148,496.00456 140.87886,494.62103 140.4112,501.73803 C 140.21985,504.65006 137.75956,506.47312 137.38075,510.32433 C 137.00195,514.17554 138.58031,510.2612 138.51717,526.48677 C 138.47467,537.41061 138.3909,540.08857 138.3909,540.08857 L 142.93659,540.08857 C 142.93659,540.08857 141.77404,533.30385 141.80016,520.80466 C 141.81446,514.1017 145.08316,511.01881 145.20943,507.54641 C 1 [...]
+ style="fill:#e8c69a;fill-opacity:1;fill-rule:evenodd;stroke:#deb463;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#d6a33f;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path5520"
+ sodipodi:cx="162.25575"
+ sodipodi:cy="474.80746"
+ sodipodi:rx="1.3889598"
+ sodipodi:ry="3.2829959"
+ d="m 163.64471,474.80746 c 0,1.81315 -0.62186,3.283 -1.38896,3.283 -0.7671,0 -1.38896,-1.46985 -1.38896,-3.283 0,-1.81314 0.62186,-3.28299 1.38896,-3.28299 0.7671,0 1.38896,1.46985 1.38896,3.28299 z"
+ transform="matrix(0.96429,0.26485,-0.26485,0.96429,110.26,9.292386)" />
+ </g>
+ <g
+ transform="translate(-14,-84)"
+ id="g5522">
+ <path
+ style="fill:#e8c69a;fill-opacity:1;fill-rule:evenodd;stroke:#deb463;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 149.20408,491.18834 C 147.92124,491.0766 145.85749,492.25567 146.42616,494.2188 C 147.00405,496.21376 147.802,493.86078 147.6302,508.50887 C 147.59712,511.427 144.97856,513.24396 144.59975,517.09517 C 144.22095,520.94638 145.73617,517.03192 145.73617,533.25761 C 145.73617,537.3417 145.46196,540.08857 145.46196,540.08857 L 150.00765,540.08857 C 150.00765,540.08857 149.01916,537.41423 149.01916,527.5755 C 149.01916,520.87252 152.30216,517.78965 152.42843,514.31725 C 152 [...]
+ id="path5524"
+ sodipodi:nodetypes="cssssccsssss" />
+ <path
+ transform="matrix(0.992538,0.121935,-0.121935,0.992538,45.06006,24.9096)"
+ d="m 163.64471,474.80746 c 0,1.81315 -0.62186,3.283 -1.38896,3.283 -0.7671,0 -1.38896,-1.46985 -1.38896,-3.283 0,-1.81314 0.62186,-3.28299 1.38896,-3.28299 0.7671,0 1.38896,1.46985 1.38896,3.28299 z"
+ sodipodi:ry="3.2829959"
+ sodipodi:rx="1.3889598"
+ sodipodi:cy="474.80746"
+ sodipodi:cx="162.25575"
+ id="path5526"
+ style="fill:#d6a33f;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:type="arc" />
+ </g>
+ </g>
+ <g
+ transform="translate(72.1568,3.078398)"
+ id="g5528">
+ <g
+ id="g5530"
+ transform="translate(-14,-84)">
+ <path
+ style="fill:#e8c69a;fill-opacity:1;fill-rule:evenodd;stroke:#deb463;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 142.43151,490.97914 C 141.14867,490.8674 139.08492,492.04647 139.65359,494.0096 C 140.23148,496.00456 140.87886,494.62103 140.4112,501.73803 C 140.21985,504.65006 137.75956,506.47312 137.38075,510.32433 C 137.00195,514.17554 138.58031,510.2612 138.51717,526.48677 C 138.47467,537.41061 138.3909,540.08857 138.3909,540.08857 L 142.93659,540.08857 C 142.93659,540.08857 141.77404,533.30385 141.80016,520.80466 C 141.81446,514.1017 145.08316,511.01881 145.20943,507.54641 C 1 [...]
+ id="path5532"
+ sodipodi:nodetypes="cssssccsssss" />
+ <path
+ transform="matrix(0.96429,0.26485,-0.26485,0.96429,110.26,9.292386)"
+ d="m 163.64471,474.80746 c 0,1.81315 -0.62186,3.283 -1.38896,3.283 -0.7671,0 -1.38896,-1.46985 -1.38896,-3.283 0,-1.81314 0.62186,-3.28299 1.38896,-3.28299 0.7671,0 1.38896,1.46985 1.38896,3.28299 z"
+ sodipodi:ry="3.2829959"
+ sodipodi:rx="1.3889598"
+ sodipodi:cy="474.80746"
+ sodipodi:cx="162.25575"
+ id="path5534"
+ style="fill:#d6a33f;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:type="arc" />
+ </g>
+ <g
+ id="g5536"
+ transform="translate(-14,-84)">
+ <path
+ sodipodi:nodetypes="cssssccsssss"
+ id="path5538"
+ d="M 149.20408,491.18834 C 147.92124,491.0766 145.85749,492.25567 146.42616,494.2188 C 147.00405,496.21376 147.802,493.86078 147.6302,508.50887 C 147.59712,511.427 144.97856,513.24396 144.59975,517.09517 C 144.22095,520.94638 145.73617,517.03192 145.73617,533.25761 C 145.73617,538.28318 145.46196,540.08857 145.46196,540.08857 L 150.00765,540.08857 C 150.00765,540.08857 149.01916,537.40583 149.01916,527.5755 C 149.01916,520.87252 152.30216,517.78965 152.42843,514.31725 C 15 [...]
+ style="fill:#e8c69a;fill-opacity:1;fill-rule:evenodd;stroke:#deb463;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#d6a33f;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path5540"
+ sodipodi:cx="162.25575"
+ sodipodi:cy="474.80746"
+ sodipodi:rx="1.3889598"
+ sodipodi:ry="3.2829959"
+ d="m 163.64471,474.80746 c 0,1.81315 -0.62186,3.283 -1.38896,3.283 -0.7671,0 -1.38896,-1.46985 -1.38896,-3.283 0,-1.81314 0.62186,-3.28299 1.38896,-3.28299 0.7671,0 1.38896,1.46985 1.38896,3.28299 z"
+ transform="matrix(0.992538,0.121935,-0.121935,0.992538,45.06006,24.9096)" />
+ </g>
+ </g>
+ <g
+ id="g5544"
+ transform="translate(72.1568,-80.9216)">
+ <path
+ style="fill:#e8c69a;fill-opacity:1;fill-rule:evenodd;stroke:#deb463;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 141.35311,490.97914 C 140.07027,490.8674 138.00652,492.04647 138.57519,494.0096 C 139.15308,496.00456 140.13746,494.58733 140.4112,501.73803 C 140.52284,504.6542 136.81596,505.39472 136.43715,509.24593 C 136.05835,513.09714 138.58031,510.2612 138.51717,526.48677 C 138.47467,537.41061 138.3909,540.08857 138.3909,540.08857 L 142.93659,540.08857 C 142.93659,540.08857 140.83044,532.22545 140.85656,519.72626 C 140.87086,513.0233 144.13956,509.94041 144.26583,506.46801 C 144. [...]
+ id="path5546"
+ sodipodi:nodetypes="cssssccsssss" />
+ <path
+ transform="matrix(0.96429,0.26485,-0.26485,0.96429,109.4512,8.213988)"
+ d="m 163.64471,474.80746 c 0,1.81315 -0.62186,3.283 -1.38896,3.283 -0.7671,0 -1.38896,-1.46985 -1.38896,-3.283 0,-1.81314 0.62186,-3.28299 1.38896,-3.28299 0.7671,0 1.38896,1.46985 1.38896,3.28299 z"
+ sodipodi:ry="3.2829959"
+ sodipodi:rx="1.3889598"
+ sodipodi:cy="474.80746"
+ sodipodi:cx="162.25575"
+ id="path5548"
+ style="fill:#d6a33f;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:type="arc" />
+ </g>
+ <g
+ id="g5550"
+ transform="translate(72.4264,-80.9216)">
+ <path
+ sodipodi:nodetypes="cssssccsssss"
+ id="path5552"
+ d="M 145.18208,491.86234 C 143.89924,491.7506 143.31829,492.25567 143.88696,494.2188 C 144.46485,496.21376 144.2972,493.86078 145.743,508.10447 C 146.03771,511.00787 142.01296,512.56996 141.63415,516.42117 C 141.25535,520.27238 144.38817,516.05671 144.38817,532.31401 C 144.38817,538.3107 145.46196,540.08857 145.46196,540.08857 L 150.00765,540.08857 C 150.00765,540.08857 146.83624,540.20949 146.86236,527.7103 C 146.87666,521.00734 149.33656,517.11565 149.46283,513.64325 C 149 [...]
+ style="fill:#e8c69a;fill-opacity:1;fill-rule:evenodd;stroke:#deb463;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#d6a33f;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path5554"
+ sodipodi:cx="162.25575"
+ sodipodi:cy="474.80746"
+ sodipodi:rx="1.3889598"
+ sodipodi:ry="3.2829959"
+ d="m 163.64471,474.80746 c 0,1.81315 -0.62186,3.283 -1.38896,3.283 -0.7671,0 -1.38896,-1.46985 -1.38896,-3.283 0,-1.81314 0.62186,-3.28299 1.38896,-3.28299 0.7671,0 1.38896,1.46985 1.38896,3.28299 z"
+ transform="matrix(0.992538,0.121935,-0.121935,0.992538,42.36406,25.1792)" />
+ </g>
+ <g
+ transform="matrix(0.926041,-0.377423,0.377423,0.926041,-79.8164,12.54123)"
+ id="g5586">
+ <path
+ sodipodi:nodetypes="cssssccsssss"
+ id="path5588"
+ d="M 139.75083,489.47486 C 138.46799,489.36312 136.40424,490.54219 136.97291,492.50532 C 137.5508,494.50028 140.87886,494.62103 140.4112,501.73803 C 140.21985,504.65006 137.75956,506.47312 137.38075,510.32433 C 137.00195,514.17554 138.58031,510.2612 138.51717,526.48677 C 138.47467,537.41061 138.82093,539.286 138.82093,539.286 L 142.93659,540.08857 C 142.93659,540.08857 141.77404,533.30385 141.80016,520.80466 C 141.81446,514.1017 145.08316,511.01881 145.20943,507.54641 C 145. [...]
+ style="fill:#e8c69a;fill-opacity:1;fill-rule:evenodd;stroke:#deb463;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#d6a33f;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path5590"
+ sodipodi:cx="162.25575"
+ sodipodi:cy="474.80746"
+ sodipodi:rx="1.3889598"
+ sodipodi:ry="3.2829959"
+ d="m 163.64471,474.80746 c 0,1.81315 -0.62186,3.283 -1.38896,3.283 -0.7671,0 -1.38896,-1.46985 -1.38896,-3.283 0,-1.81314 0.62186,-3.28299 1.38896,-3.28299 0.7671,0 1.38896,1.46985 1.38896,3.28299 z"
+ transform="matrix(0.96429,0.26485,-0.26485,0.96429,110.26,9.292386)" />
+ </g>
+ <g
+ transform="translate(86.1568,-80.9216)"
+ id="g5558">
+ <path
+ sodipodi:nodetypes="cssssccsssss"
+ id="path5560"
+ d="M 137.03952,491.11394 C 135.75668,491.0022 133.69293,492.18127 134.2616,494.1444 C 134.83949,496.13936 135.75647,495.16023 136.09761,501.60323 C 136.25191,504.51746 134.25477,503.10313 133.87596,506.95434 C 133.49716,510.80555 135.21032,508.2392 135.14718,524.46477 C 135.10468,535.38861 138.3909,540.08857 138.3909,540.08857 L 142.93659,540.08857 C 142.93659,540.08857 137.73005,533.70825 137.75617,521.20906 C 137.77047,514.5061 141.57837,507.64882 141.70464,504.17642 C 141 [...]
+ style="fill:#e8c69a;fill-opacity:1;fill-rule:evenodd;stroke:#deb463;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#d6a33f;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path5562"
+ sodipodi:cx="162.25575"
+ sodipodi:cy="474.80746"
+ sodipodi:rx="1.3889598"
+ sodipodi:ry="3.2829959"
+ d="m 163.64471,474.80746 c 0,1.81315 -0.62186,3.283 -1.38896,3.283 -0.7671,0 -1.38896,-1.46985 -1.38896,-3.283 0,-1.81314 0.62186,-3.28299 1.38896,-3.28299 0.7671,0 1.38896,1.46985 1.38896,3.28299 z"
+ transform="matrix(0.96429,0.26485,-0.26485,0.96429,107.1596,6.461592)" />
+ </g>
+ <g
+ transform="matrix(0.926041,-0.377423,0.377423,0.926041,-79.2445,14.82885)"
+ id="g5592">
+ <path
+ style="fill:#e8c69a;fill-opacity:1;fill-rule:evenodd;stroke:#deb463;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 144.17131,488.00493 C 142.88847,487.89319 140.82472,489.07226 141.39339,491.03539 C 141.97128,493.03035 145.75551,492.82084 145.58371,507.46893 C 145.55063,510.38706 143.18724,511.07285 142.80843,514.92406 C 142.42963,518.77527 145.66579,517.03207 145.73617,533.25761 C 145.75822,538.34072 145.20049,539.46735 145.20049,539.46735 L 150.00765,540.08857 C 150.00765,540.08857 149.19443,537.68644 149.01916,527.5755 C 148.90298,520.87353 150.75265,516.54054 150.87892,513.06814 [...]
+ id="path5594"
+ sodipodi:nodetypes="cssssccsssss" />
+ <path
+ transform="matrix(0.992538,0.121935,-0.121935,0.992538,44.11211,23.6998)"
+ d="m 163.64471,474.80746 c 0,1.81315 -0.62186,3.283 -1.38896,3.283 -0.7671,0 -1.38896,-1.46985 -1.38896,-3.283 0,-1.81314 0.62186,-3.28299 1.38896,-3.28299 0.7671,0 1.38896,1.46985 1.38896,3.28299 z"
+ sodipodi:ry="3.2829959"
+ sodipodi:rx="1.3889598"
+ sodipodi:cy="474.80746"
+ sodipodi:cx="162.25575"
+ id="path5596"
+ style="fill:#d6a33f;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:type="arc" />
+ </g>
+ <g
+ transform="translate(86.1568,-80.9216)"
+ id="g5564">
+ <path
+ style="fill:#e8c69a;fill-opacity:1;fill-rule:evenodd;stroke:#deb463;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 141.92489,494.01914 C 140.64205,493.9074 138.5783,495.08647 139.14697,497.0496 C 139.72486,499.04456 141.46641,499.11796 141.56421,512.28326 C 141.58589,515.20149 138.69111,516.06819 139.74696,519.79116 C 140.89324,523.83301 141.89778,518.88375 145.54553,531.92316 C 146.68712,536.00391 145.46196,540.08857 145.46196,540.08857 L 150.00765,540.08857 C 150.00765,540.08857 148.06668,530.2167 146.72756,525.6883 C 146.13786,523.69415 147.44937,520.48564 147.57564,517.01324 C 1 [...]
+ id="path5566"
+ sodipodi:nodetypes="cssssccsssss" />
+ <path
+ transform="matrix(0.992538,0.121935,-0.121935,0.992538,40.47687,26.662)"
+ d="m 163.64471,474.80746 c 0,1.81315 -0.62186,3.283 -1.38896,3.283 -0.7671,0 -1.38896,-1.46985 -1.38896,-3.283 0,-1.81314 0.62186,-3.28299 1.38896,-3.28299 0.7671,0 1.38896,1.46985 1.38896,3.28299 z"
+ sodipodi:ry="3.2829959"
+ sodipodi:rx="1.3889598"
+ sodipodi:cy="474.80746"
+ sodipodi:cx="162.25575"
+ id="path5568"
+ style="fill:#d6a33f;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:type="arc" />
+ </g>
+ <g
+ id="g5572"
+ transform="translate(100.1568,-80.9216)">
+ <path
+ style="fill:#e8c69a;fill-opacity:1;fill-rule:evenodd;stroke:#deb463;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 133.06888,491.31135 C 131.78604,491.19961 129.72229,492.37868 130.29096,494.34181 C 130.86885,496.33677 131.3923,494.3271 132.2877,501.40302 C 132.66826,504.41036 131.27795,506.09185 131.85232,510.32433 C 132.3727,514.15898 133.78721,510.60232 135.08573,525.81955 C 135.32652,528.64136 138.3909,540.08857 138.3909,540.08857 L 142.93659,540.08857 C 142.93659,540.08857 137.77069,527.58478 137.51086,522.80633 C 137.14691,516.11325 139.93086,510.48347 138.6325,507.26046 C 137 [...]
+ id="path5574"
+ sodipodi:nodetypes="cssssccsssss" />
+ <path
+ transform="matrix(0.923822,-0.382822,0.382822,0.923822,-196.4126,132.3561)"
+ d="m 163.64471,474.80746 c 0,1.81315 -0.62186,3.283 -1.38896,3.283 -0.7671,0 -1.38896,-1.46985 -1.38896,-3.283 0,-1.81314 0.62186,-3.28299 1.38896,-3.28299 0.7671,0 1.38896,1.46985 1.38896,3.28299 z"
+ sodipodi:ry="3.2829959"
+ sodipodi:rx="1.3889598"
+ sodipodi:cy="474.80746"
+ sodipodi:cx="162.25575"
+ id="path5576"
+ style="fill:#d6a33f;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:type="arc" />
+ </g>
+ <g
+ id="g5806"
+ transform="matrix(0.774683,-0.436512,0.377721,0.895261,-52.92922,37.58278)">
+ <path
+ sodipodi:nodetypes="cssssccsssss"
+ id="path5808"
+ d="M 144.58359,488.44515 C 143.30075,488.33341 139.86857,489.16467 140.43724,491.1278 C 141.01513,493.12276 143.40545,492.38862 144.21528,507.12112 C 144.38102,510.13619 141.82345,511.47258 141.44464,515.32379 C 141.06584,519.175 146.12727,517.06994 145.73617,533.25761 C 145.61465,538.28711 145.01856,538.2221 145.01856,538.2221 L 149.73665,538.62557 C 149.73665,538.62557 149.08631,537.99078 149.01916,527.5755 C 148.97591,520.86666 149.38886,516.94027 149.51513,513.46787 C 14 [...]
+ style="fill:#e8c69a;fill-opacity:1;fill-rule:evenodd;stroke:#deb463;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#d6a33f;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path5810"
+ sodipodi:cx="162.25575"
+ sodipodi:cy="474.80746"
+ sodipodi:rx="1.3889598"
+ sodipodi:ry="3.2829959"
+ d="m 163.64471,474.80746 c 0,1.81315 -0.62186,3.283 -1.38896,3.283 -0.7671,0 -1.38896,-1.46985 -1.38896,-3.283 0,-1.81314 0.62186,-3.28299 1.38896,-3.28299 0.7671,0 1.38896,1.46985 1.38896,3.28299 z"
+ transform="matrix(0.992538,0.121935,-0.121935,0.992538,44.11211,23.6998)" />
+ </g>
+ <g
+ transform="matrix(0.730447,-0.507073,0.459821,0.855997,-86.27254,67.44803)"
+ id="g5812">
+ <path
+ style="fill:#e8c69a;fill-opacity:1;fill-rule:evenodd;stroke:#deb463;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 146.94842,489.65143 C 145.66558,489.53969 143.60183,490.71876 144.1705,492.68189 C 144.74839,494.67685 145.75551,492.82084 145.58371,507.46893 C 145.55063,510.38706 143.18724,511.07285 142.80843,514.92406 C 142.42963,518.77527 148.21611,517.4406 145.73617,533.25761 C 145.39546,535.43062 144.39126,536.50346 144.39126,536.50346 L 148.53122,536.31879 C 148.53122,536.31879 149.8347,538.01215 149.01916,527.5755 C 148.49232,520.83339 150.75265,516.54054 150.87892,513.06814 C [...]
+ id="path5814"
+ sodipodi:nodetypes="cssssccsssss" />
+ <path
+ transform="matrix(0.992538,0.121935,-0.121935,0.992538,44.11211,23.6998)"
+ d="m 163.64471,474.80746 c 0,1.81315 -0.62186,3.283 -1.38896,3.283 -0.7671,0 -1.38896,-1.46985 -1.38896,-3.283 0,-1.81314 0.62186,-3.28299 1.38896,-3.28299 0.7671,0 1.38896,1.46985 1.38896,3.28299 z"
+ sodipodi:ry="3.2829959"
+ sodipodi:rx="1.3889598"
+ sodipodi:cy="474.80746"
+ sodipodi:cx="162.25575"
+ id="path5816"
+ style="fill:#d6a33f;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:type="arc" />
+ </g>
+ <g
+ id="g5824"
+ transform="matrix(0.893805,-0.448457,0.448457,0.893805,-100.226,34.6487)">
+ <path
+ sodipodi:nodetypes="cssssccsssss"
+ id="path5826"
+ d="M 141.92489,494.01914 C 140.64205,493.9074 138.5783,495.08647 139.14697,497.0496 C 139.72486,499.04456 141.46641,499.11796 141.56421,512.28326 C 141.58589,515.20149 140.12577,515.93995 139.74696,519.79116 C 139.36816,523.64237 142.72352,520.057 145.73617,533.25761 C 146.70153,537.48755 148.02138,538.81331 148.02138,538.81331 L 152.56707,538.81331 C 152.56707,538.81331 148.06668,530.2167 146.72756,525.6883 C 146.13786,523.69415 147.44937,520.48564 147.57564,517.01324 C 147 [...]
+ style="fill:#e8c69a;fill-opacity:1;fill-rule:evenodd;stroke:#deb463;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#d6a33f;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path5828"
+ sodipodi:cx="162.25575"
+ sodipodi:cy="474.80746"
+ sodipodi:rx="1.3889598"
+ sodipodi:ry="3.2829959"
+ d="m 163.64471,474.80746 c 0,1.81315 -0.62186,3.283 -1.38896,3.283 -0.7671,0 -1.38896,-1.46985 -1.38896,-3.283 0,-1.81314 0.62186,-3.28299 1.38896,-3.28299 0.7671,0 1.38896,1.46985 1.38896,3.28299 z"
+ transform="matrix(0.992538,0.121935,-0.121935,0.992538,40.47687,26.662)" />
+ </g>
+ <g
+ id="g5818"
+ transform="matrix(0.638842,-0.618512,0.592183,0.77038,-140.2824,125.7023)">
+ <path
+ sodipodi:nodetypes="cssssccsssss"
+ id="path5820"
+ d="M 148.77098,490.74351 C 147.48814,490.63177 145.42439,491.81084 145.99306,493.77397 C 146.57095,495.76893 145.75551,492.82084 145.58371,507.46893 C 145.55063,510.38706 143.18724,511.07285 142.80843,514.92406 C 142.42963,518.77527 146.85083,517.17294 145.73617,533.25761 C 145.50009,536.6642 144.41537,538.32034 144.41537,538.32034 L 147.61918,538.48027 C 147.61918,538.48027 149.35089,538.32048 149.01916,527.5755 C 148.81159,520.85211 150.75265,516.54054 150.87892,513.06814 [...]
+ style="fill:#e8c69a;fill-opacity:1;fill-rule:evenodd;stroke:#deb463;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#d6a33f;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path5822"
+ sodipodi:cx="162.25575"
+ sodipodi:cy="474.80746"
+ sodipodi:rx="1.3889598"
+ sodipodi:ry="3.2829959"
+ d="m 163.64471,474.80746 c 0,1.81315 -0.62186,3.283 -1.38896,3.283 -0.7671,0 -1.38896,-1.46985 -1.38896,-3.283 0,-1.81314 0.62186,-3.28299 1.38896,-3.28299 0.7671,0 1.38896,1.46985 1.38896,3.28299 z"
+ transform="matrix(0.992538,0.121935,-0.121935,0.992538,44.11211,23.6998)" />
+ </g>
+ </g>
+ <g
+ id="g2608"
+ transform="translate(0,0.539199)">
+ <g
+ id="g1551"
+ transform="matrix(0.3840236,0,0,0.3840236,136.7512,106.97234)">
+ <path
+ sodipodi:nodetypes="csssssccsssss"
+ id="path1553"
+ d="M 115.00993,485.82485 C 114.36851,485.76898 112.67066,488.41755 115.35725,488.4207 C 118.74278,488.42468 122.05455,487.59081 125.91011,488.37872 C 128.08444,488.82306 135.48841,493.32158 138.5518,508.23927 C 139.13884,511.09794 137.16018,512.36776 138.33156,515.74717 C 139.59895,519.40354 145.86628,517.03244 145.73617,533.25761 C 145.69367,538.36706 145.46196,540.08857 145.46196,540.08857 L 150.00765,540.08857 C 150.00765,540.08857 148.89772,537.78706 149.01916,527.5755 C [...]
+ style="fill:#dece6c;fill-opacity:1;fill-rule:evenodd;stroke:#d0ac32;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#ccb43c;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path1555"
+ sodipodi:cx="162.25575"
+ sodipodi:cy="474.80746"
+ sodipodi:rx="1.5227536"
+ sodipodi:ry="3.2829959"
+ d="m 163.77851,474.80746 c 0,1.81315 -0.68176,3.283 -1.52276,3.283 -0.84099,0 -1.52275,-1.46985 -1.52275,-3.283 0,-1.81314 0.68176,-3.28299 1.52275,-3.28299 0.841,0 1.52276,1.46985 1.52276,3.28299 z"
+ transform="matrix(0.962661,-0.27071,0.27071,0.962661,-142.9933,100.4454)" />
+ </g>
+ <g
+ id="g7274"
+ transform="matrix(0.3840236,0,0,0.3840236,129.07073,106.97234)">
+ <path
+ style="fill:#dece6c;fill-opacity:1;fill-rule:evenodd;stroke:#d0ac32;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 132.32782,488.97914 C 127.12429,488.74028 129.43901,492.80016 131.38836,491.81329 C 133.738,490.62376 134.09577,493.29494 137.74723,493.05642 C 140.8919,492.85101 140.87886,494.62103 140.4112,501.73803 C 140.21985,504.65006 137.75956,506.47312 137.38075,510.32433 C 137.00195,514.17554 138.58031,510.2612 138.51717,526.48677 C 138.47467,537.41061 138.3909,540.08857 138.3909,540.08857 L 142.93659,540.08857 C 142.93659,540.08857 141.77404,533.30385 141.80016,520.80466 C 141 [...]
+ id="path7276"
+ sodipodi:nodetypes="csssssccsssss" />
+ <path
+ transform="matrix(0.96429,0.26485,-0.26485,0.96429,110.26,9.292386)"
+ d="m 163.64471,474.80746 c 0,1.81315 -0.62186,3.283 -1.38896,3.283 -0.7671,0 -1.38896,-1.46985 -1.38896,-3.283 0,-1.81314 0.62186,-3.28299 1.38896,-3.28299 0.7671,0 1.38896,1.46985 1.38896,3.28299 z"
+ sodipodi:ry="3.2829959"
+ sodipodi:rx="1.3889598"
+ sodipodi:cy="474.80746"
+ sodipodi:cx="162.25575"
+ id="path7278"
+ style="fill:#ccb43c;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:type="arc" />
+ </g>
+ <g
+ id="g7280"
+ transform="matrix(0.3840236,0,0,0.3840236,129.07073,106.97234)">
+ <path
+ sodipodi:nodetypes="csssssccsssss"
+ id="path7282"
+ d="M 121.52431,489.32964 C 120.88289,489.27377 119.18504,491.92234 121.87163,491.92549 C 125.25716,491.92947 128.49123,492.00859 132.42449,491.88351 C 135.40762,491.78865 143.802,493.86078 143.6302,508.50887 C 143.59712,511.427 142.97856,513.24396 142.59975,517.09517 C 142.22095,520.94638 145.86628,517.03244 145.73617,533.25761 C 145.69367,538.36706 145.46196,540.08857 145.46196,540.08857 L 150.00765,540.08857 C 150.00765,540.08857 148.89772,537.78706 149.01916,527.5755 C 14 [...]
+ style="fill:#dece6c;fill-opacity:1;fill-rule:evenodd;stroke:#d0ac32;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#ccb43c;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path7284"
+ sodipodi:cx="162.25575"
+ sodipodi:cy="474.80746"
+ sodipodi:rx="1.3889598"
+ sodipodi:ry="3.2829959"
+ d="m 163.64471,474.80746 c 0,1.81315 -0.62186,3.283 -1.38896,3.283 -0.7671,0 -1.38896,-1.46985 -1.38896,-3.283 0,-1.81314 0.62186,-3.28299 1.38896,-3.28299 0.7671,0 1.38896,1.46985 1.38896,3.28299 z"
+ transform="matrix(0.992538,0.121935,-0.121935,0.992538,43.06006,24.9096)" />
+ </g>
+ <use
+ height="1052.3622"
+ width="744.09448"
+ transform="translate(18.433131,4.2749997e-6)"
+ id="use1547"
+ xlink:href="#g6322"
+ y="0"
+ x="0" />
+ <use
+ x="0"
+ y="0"
+ xlink:href="#g6322"
+ id="use1549"
+ transform="translate(24.577508,4.2749997e-6)"
+ width="744.09448"
+ height="1052.3622" />
+ <use
+ x="0"
+ y="0"
+ xlink:href="#g6322"
+ id="use1545"
+ transform="translate(18.433131,4.2749997e-6)"
+ width="744.09448"
+ height="1052.3622" />
+ <use
+ height="1052.3622"
+ width="744.09448"
+ transform="translate(12.288754,4.2749997e-6)"
+ id="use1543"
+ xlink:href="#g6322"
+ y="0"
+ x="0" />
+ <use
+ x="0"
+ y="0"
+ xlink:href="#g6322"
+ id="use1541"
+ transform="translate(6.1443771,4.2749997e-6)"
+ width="744.09448"
+ height="1052.3622" />
+ <g
+ style="display:inline"
+ transform="matrix(0.3840236,0,0,0.3840236,104.49323,139.23033)"
+ id="g6322">
+ <g
+ id="g6330"
+ transform="translate(-14,-84)">
+ <path
+ sodipodi:nodetypes="csssssccsssss"
+ id="path6332"
+ d="M 137.39931,488.45464 C 136.75789,488.39877 135.06004,491.04734 137.74663,491.05049 C 141.13216,491.05447 140.99123,489.88359 145.17449,491.25851 C 148.00991,492.19043 147.802,493.86078 147.6302,508.50887 C 147.59712,511.427 144.97856,513.24396 144.59975,517.09517 C 144.22095,520.94638 145.86628,517.03244 145.73617,533.25761 C 145.69367,538.36706 145.46196,540.08857 145.46196,540.08857 L 150.00765,540.08857 C 150.00765,540.08857 148.89772,537.78706 149.01916,527.5755 C [...]
+ style="fill:#dece6c;fill-opacity:1;fill-rule:evenodd;stroke:#d0ac32;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#ccb43c;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path6334"
+ sodipodi:cx="162.25575"
+ sodipodi:cy="474.80746"
+ sodipodi:rx="1.3889598"
+ sodipodi:ry="3.2829959"
+ d="m 163.64471,474.80746 c 0,1.81315 -0.62186,3.283 -1.38896,3.283 -0.7671,0 -1.38896,-1.46985 -1.38896,-3.283 0,-1.81314 0.62186,-3.28299 1.38896,-3.28299 0.7671,0 1.38896,1.46985 1.38896,3.28299 z"
+ transform="matrix(0.992538,0.121935,-0.121935,0.992538,45.06006,24.9096)" />
+ </g>
+ <g
+ id="g6324"
+ transform="translate(-14,-84)">
+ <path
+ style="fill:#dece6c;fill-opacity:1;fill-rule:evenodd;stroke:#d0ac32;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 139.72862,488.13234 L 138.30996,490.01429 C 142.62233,490.02799 140.02107,489.65653 140.36243,493.56622 C 140.63599,496.69942 140.87886,494.62103 140.4112,501.73803 C 140.21985,504.65006 137.75956,506.47312 137.38075,510.32433 C 137.00195,514.17554 138.58031,510.2612 138.51717,526.48677 C 138.47467,537.41061 138.3909,540.08857 138.3909,540.08857 L 142.93659,540.08857 C 142.93659,540.08857 141.77404,533.30385 141.80016,520.80466 C 141.81446,514.1017 145.08316,511.01881 [...]
+ id="path6326"
+ sodipodi:nodetypes="ccssssccssssc" />
+ <path
+ transform="matrix(0.96429,0.26485,-0.26485,0.96429,110.26,9.292386)"
+ d="m 163.64471,474.80746 c 0,1.81315 -0.62186,3.283 -1.38896,3.283 -0.7671,0 -1.38896,-1.46985 -1.38896,-3.283 0,-1.81314 0.62186,-3.28299 1.38896,-3.28299 0.7671,0 1.38896,1.46985 1.38896,3.28299 z"
+ sodipodi:ry="3.2829959"
+ sodipodi:rx="1.3889598"
+ sodipodi:cy="474.80746"
+ sodipodi:cx="162.25575"
+ id="path6328"
+ style="fill:#ccb43c;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:type="arc" />
+ </g>
+ </g>
+ <g
+ id="g1537"
+ transform="matrix(0.3840236,0,0,0.3840236,94.317552,157.55865)">
+ <path
+ style="fill:#dece6c;fill-opacity:1;fill-rule:evenodd;stroke:#d0ac32;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 234.82532,359.25204 C 229.62179,359.01318 231.93651,363.07306 233.88586,362.08619 C 236.2355,360.89666 236.59327,361.56784 240.24473,361.32932 C 243.3894,361.12391 246.74635,375.5651 246.27869,382.6821 C 246.08734,385.59413 244.09885,387.21499 245.20284,389.853 C 246.69677,393.4228 250.49133,392.33889 250.74507,399.00586 C 250.98534,405.31875 250.8884,408.36147 250.8884,408.36147 L 255.43409,408.36147 C 255.43409,408.36147 254.40634,402.65515 254.43246,399.59194 C 254.4 [...]
+ id="path7322"
+ sodipodi:nodetypes="csssssccsssss" />
+ <path
+ transform="matrix(0.99974,-2.285026e-2,2.285026e-2,0.99974,74.87678,-83.21669)"
+ d="m 163.64471,474.80746 c 0,1.81315 -0.62186,3.283 -1.38896,3.283 -0.7671,0 -1.38896,-1.46985 -1.38896,-3.283 0,-1.81314 0.62186,-3.28299 1.38896,-3.28299 0.7671,0 1.38896,1.46985 1.38896,3.28299 z"
+ sodipodi:ry="3.2829959"
+ sodipodi:rx="1.3889598"
+ sodipodi:cy="474.80746"
+ sodipodi:cx="162.25575"
+ id="path7324"
+ style="fill:#ccb43c;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:type="arc" />
+ </g>
+ <g
+ transform="matrix(0.3840236,0,0,0.3840236,132.91096,106.97234)"
+ id="g7294">
+ <path
+ style="fill:#dece6c;fill-opacity:1;fill-rule:evenodd;stroke:#d0ac32;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 129.52431,487.32964 C 128.88289,487.27377 127.18504,489.92234 129.87163,489.92549 C 133.25716,489.92947 132.49123,492.00859 136.42449,491.88351 C 139.40762,491.78865 139.802,493.86078 139.6302,508.50887 C 139.59712,511.427 137.16018,512.36776 138.33156,515.74717 C 139.59895,519.40354 145.86628,517.03244 145.73617,533.25761 C 145.69367,538.36706 145.46196,540.08857 145.46196,540.08857 L 150.00765,540.08857 C 150.00765,540.08857 148.89772,537.78706 149.01916,527.5755 C 14 [...]
+ id="path7296"
+ sodipodi:nodetypes="csssssccsssss" />
+ <path
+ transform="matrix(0.962661,-0.27071,0.27071,0.962661,-142.9933,100.4454)"
+ d="m 163.77851,474.80746 c 0,1.81315 -0.68176,3.283 -1.52276,3.283 -0.84099,0 -1.52275,-1.46985 -1.52275,-3.283 0,-1.81314 0.68176,-3.28299 1.52275,-3.28299 0.841,0 1.52276,1.46985 1.52276,3.28299 z"
+ sodipodi:ry="3.2829959"
+ sodipodi:rx="1.5227536"
+ sodipodi:cy="474.80746"
+ sodipodi:cx="162.25575"
+ id="path7298"
+ style="fill:#ccb43c;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:type="arc" />
+ </g>
+ <g
+ id="g1565"
+ transform="matrix(0.3840236,0,0,0.3840236,138.28729,106.97234)">
+ <path
+ sodipodi:nodetypes="csssssccsssss"
+ id="path1567"
+ d="M 111.00993,485.82485 C 110.36851,485.76898 108.67066,488.41755 111.35725,488.4207 C 114.74278,488.42468 118.05455,487.59081 121.91011,488.37872 C 124.08444,488.82306 131.48841,493.32158 134.5518,508.23927 C 135.13884,511.09794 133.16018,512.36776 134.33156,515.74717 C 135.59895,519.40354 146.27068,517.84124 149.37576,533.52721 C 150.36797,538.53957 149.10155,540.35817 149.10155,540.35817 L 153.64724,540.35817 C 153.64724,540.35817 151.97939,529.27169 151.31075,527.7103 C [...]
+ style="fill:#dece6c;fill-opacity:1;fill-rule:evenodd;stroke:#d0ac32;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#ccb43c;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path1569"
+ sodipodi:cx="162.25575"
+ sodipodi:cy="474.80746"
+ sodipodi:rx="1.5227536"
+ sodipodi:ry="3.2829959"
+ d="m 163.77851,474.80746 c 0,1.81315 -0.68176,3.283 -1.52276,3.283 -0.84099,0 -1.52275,-1.46985 -1.52275,-3.283 0,-1.81314 0.68176,-3.28299 1.52275,-3.28299 0.841,0 1.52276,1.46985 1.52276,3.28299 z"
+ transform="matrix(0.962661,-0.27071,0.27071,0.962661,-146.4981,100.8498)" />
+ </g>
+ <g
+ transform="matrix(0.3840236,0,0,0.3840236,133.67901,106.97234)"
+ id="g7288">
+ <path
+ sodipodi:nodetypes="ccssssccssscc"
+ id="path7290"
+ d="M 47.281861,487.60915 L 46.342401,490.4433 C 50.501351,490.38193 123.78218,490.46415 127.43364,490.22563 C 130.57831,490.02022 134.87886,494.62103 134.4112,501.73803 C 134.21985,504.65006 131.75956,506.94492 132.45915,509.44813 C 133.50075,513.17511 138.58031,510.2612 138.51717,526.48677 C 138.47467,537.41061 138.3909,540.08857 138.3909,540.08857 L 142.93659,540.08857 C 142.93659,540.08857 141.77404,533.30385 141.80016,520.80466 C 141.81446,514.1017 138.20696,511.35581 13 [...]
+ style="fill:#dece6c;fill-opacity:1;fill-rule:evenodd;stroke:#d0ac32;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#ccb43c;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path7292"
+ sodipodi:cx="162.25575"
+ sodipodi:cy="474.80746"
+ sodipodi:rx="1.3889598"
+ sodipodi:ry="3.2829959"
+ d="m 163.64471,474.80746 c 0,1.81315 -0.62186,3.283 -1.38896,3.283 -0.7671,0 -1.38896,-1.46985 -1.38896,-3.283 0,-1.81314 0.62186,-3.28299 1.38896,-3.28299 0.7671,0 1.38896,1.46985 1.38896,3.28299 z"
+ transform="matrix(0.995477,9.500641e-2,-9.500641e-2,0.995477,19.02852,19.63825)" />
+ </g>
+ <g
+ transform="matrix(0.3840236,0,0,0.3840236,138.28729,106.97234)"
+ id="g1559">
+ <path
+ style="fill:#dece6c;fill-opacity:1;fill-rule:evenodd;stroke:#d0ac32;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 34.669336,485.55525 L 35.016656,488.1511 C 38.835626,487.86714 118.05455,487.59081 121.91011,488.37872 C 124.08444,488.82306 131.48841,493.32158 134.5518,508.23927 C 135.13884,511.09794 133.16018,512.36776 134.33156,515.74717 C 135.59895,519.40354 142.63109,517.57164 145.73617,533.25761 C 146.72838,538.26997 145.46196,540.08857 145.46196,540.08857 L 150.00765,540.08857 C 150.00765,540.08857 148.3398,529.00209 147.67116,527.4407 C 144.79849,520.73253 142.30357,516.37425 [...]
+ id="path1561"
+ sodipodi:nodetypes="ccssssccssssc" />
+ <path
+ transform="matrix(0.962661,-0.27071,0.27071,0.962661,-146.4981,100.8498)"
+ d="m 163.77851,474.80746 c 0,1.81315 -0.68176,3.283 -1.52276,3.283 -0.84099,0 -1.52275,-1.46985 -1.52275,-3.283 0,-1.81314 0.68176,-3.28299 1.52275,-3.28299 0.841,0 1.52276,1.46985 1.52276,3.28299 z"
+ sodipodi:ry="3.2829959"
+ sodipodi:rx="1.5227536"
+ sodipodi:cy="474.80746"
+ sodipodi:cx="162.25575"
+ id="path1563"
+ style="fill:#ccb43c;fill-opacity:1;stroke:none;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:type="arc" />
+ </g>
+ </g>
+ </g>
+ <use
+ x="0"
+ y="0"
+ xlink:href="#g2647"
+ id="use2849"
+ transform="matrix(-1,0,0,1,227.72776,4.3793762e-8)"
+ width="744.09448"
+ height="1052.3622" />
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer5"
+ inkscape:label="Tip"
+ transform="translate(-45.700655,-119.62301)">
+ <g
+ style="display:inline"
+ id="g5555"
+ transform="matrix(0.3840236,0,0,0.3840236,13.749846,185.22329)">
+ <g
+ style="display:inline"
+ id="g5557"
+ inkscape:label="Calque 1" />
+ <g
+ style="display:inline"
+ inkscape:label="Oeuil"
+ id="layer2">
+ <g
+ transform="translate(268.3572,-229.3095)"
+ id="g3158"
+ style="display:inline">
+ <path
+ style="fill:#e3a200;fill-opacity:1;fill-rule:evenodd;stroke:#b87a0a;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M -56.194377,290.66148 L -5.4384401,375.80048 L 48.59209,290.66148 C 109.65915,274.67937 130.59352,265.73378 440.49026,140.67771 L 423.76701,106.15763 C 154.58912,217.0441 94.88535,263.31492 -74.204551,257.91569 L -74.204551,290.66148 L -56.194377,290.66148 z"
+ id="path1327"
+ sodipodi:nodetypes="cccccccc" />
+ </g>
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/openfovea/Icon/Export_curve.svg b/openfovea/Icon/Export_curve.svg
new file mode 100644
index 0000000..65989d7
--- /dev/null
+++ b/openfovea/Icon/Export_curve.svg
@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="24"
+ height="24"
+ id="svg3613"
+ version="1.1"
+ inkscape:version="0.47 r22583"
+ inkscape:export-filename="export_curve.png"
+ inkscape:export-xdpi="52.5"
+ inkscape:export-ydpi="52.5"
+ sodipodi:docname="Export_curve.svg">
+ <defs
+ id="defs3615">
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 526.18109 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="744.09448 : 526.18109 : 1"
+ inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+ id="perspective3621" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="7.7083333"
+ inkscape:cx="16.94543"
+ inkscape:cy="15.450684"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:window-width="832"
+ inkscape:window-height="625"
+ inkscape:window-x="51"
+ inkscape:window-y="103"
+ inkscape:window-maximized="0" />
+ <metadata
+ id="metadata3618">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Calque 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-1028.3622)">
+ <path
+ style="fill:none;stroke:#0000ff;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ d="m 5.8378379,1023.3677 3.0486486,18.227 17.7081075,0"
+ id="path3623" />
+ <path
+ style="fill:none;stroke:#ff0000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ d="m 2.1405405,1027.8433 2.1405405,19.3297 4.7513514,-0.016 4.3621626,2.3189 0.08108,-2.3514 9.810811,-0.016"
+ id="path4135"
+ sodipodi:nodetypes="cccccc" />
+ <rect
+ style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+ id="rect4137"
+ width="12.972973"
+ height="14.27027"
+ x="25.945944"
+ y="1023.6919" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:2, 2;stroke-dashoffset:0"
+ d="m 26.464865,1028.4921 10.118918,0"
+ id="path4139"
+ sodipodi:nodetypes="cc" />
+ </g>
+</svg>
diff --git a/openfovea/Icon/NoData.png b/openfovea/Icon/NoData.png
new file mode 100644
index 0000000..b857156
Binary files /dev/null and b/openfovea/Icon/NoData.png differ
diff --git a/openfovea/Icon/NoData.svg b/openfovea/Icon/NoData.svg
new file mode 100644
index 0000000..6851c7d
--- /dev/null
+++ b/openfovea/Icon/NoData.svg
@@ -0,0 +1,335 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="161.52025"
+ height="143.48293"
+ id="svg2"
+ sodipodi:version="0.32"
+ inkscape:version="0.46"
+ version="1.0"
+ sodipodi:docbase="/home/charles/Projet/AFM/OpenFovea/Icon"
+ sodipodi:docname="NoData.svg"
+ inkscape:export-filename="/home/charles/Bazaar/OpenFovea/mosaic/src/Icon/NoData.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape">
+ <defs
+ id="defs4">
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 32 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="64 : 32 : 1"
+ inkscape:persp3d-origin="32 : 21.333333 : 1"
+ id="perspective3169" />
+ <linearGradient
+ id="linearGradient2832">
+ <stop
+ id="stop2834"
+ offset="0"
+ style="stop-color:#f2ffff;stop-opacity:0;" />
+ <stop
+ id="stop2838"
+ offset="1"
+ style="stop-color:#f2ffff;stop-opacity:0.7037037;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2772">
+ <stop
+ style="stop-color:#f2ffff;stop-opacity:0.88148147;"
+ offset="0"
+ id="stop2774" />
+ <stop
+ id="stop2780"
+ offset="0.82051283"
+ style="stop-color:#f2ffff;stop-opacity:0.38518518;" />
+ <stop
+ style="stop-color:#f2ffff;stop-opacity:0;"
+ offset="1"
+ id="stop2776" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2772"
+ id="radialGradient2778"
+ cx="387.5"
+ cy="438.73355"
+ fx="387.5"
+ fy="438.73355"
+ r="81.502647"
+ gradientTransform="matrix(1,0,0,1.04908,0,-23.40425)"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2832"
+ id="radialGradient2830"
+ cx="387.14285"
+ cy="486.64789"
+ fx="387.14285"
+ fy="486.64789"
+ r="102.86049"
+ gradientTransform="translate(0,3.116813e-5)"
+ gradientUnits="userSpaceOnUse" />
+ <filter
+ inkscape:collect="always"
+ id="filter4000">
+ <feGaussianBlur
+ inkscape:collect="always"
+ stdDeviation="0.87375745"
+ id="feGaussianBlur4002" />
+ </filter>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="2"
+ inkscape:cx="75.750097"
+ inkscape:cy="97.202502"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ width="64px"
+ height="64px"
+ inkscape:window-width="1280"
+ inkscape:window-height="949"
+ inkscape:window-x="390"
+ inkscape:window-y="47"
+ showgrid="false" />
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:groupmode="layer"
+ id="layer2"
+ inkscape:label="Iris"
+ style="display:inline"
+ transform="translate(46.260125,50.03102)">
+ <path
+ sodipodi:type="arc"
+ style="fill:#00ff00;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.70000005;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path1872"
+ sodipodi:cx="387.14285"
+ sodipodi:cy="486.64789"
+ sodipodi:rx="102.85715"
+ sodipodi:ry="102.85715"
+ d="M 475.44604,539.39388 A 102.85715,102.85715 0 1 1 475.44644,539.39321"
+ sodipodi:start="0.53845272"
+ sodipodi:end="6.8216304"
+ sodipodi:open="true"
+ transform="matrix(0.5777973,0,0,0.5777973,-186.97978,-247.16444)" />
+ <path
+ transform="matrix(0.5777973,0,0,0.5777973,-186.97978,-247.16444)"
+ sodipodi:open="true"
+ sodipodi:end="6.8216304"
+ sodipodi:start="0.53845272"
+ d="M 475.44604,539.39388 A 102.85715,102.85715 0 1 1 475.44644,539.39321"
+ sodipodi:ry="102.85715"
+ sodipodi:rx="102.85715"
+ sodipodi:cy="486.64789"
+ sodipodi:cx="387.14285"
+ id="path2822"
+ style="fill:url(#radialGradient2830);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.70000005;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:type="arc" />
+ </g>
+ <g
+ inkscape:label="Calque 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ style="display:inline"
+ transform="translate(46.260125,50.03102)">
+ <g
+ id="g2808"
+ transform="matrix(0.5777973,0,0,0.5777973,-187.18619,-247.28843)">
+ <path
+ sodipodi:type="arc"
+ style="fill:#00cd00;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.70000005;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path2788"
+ sodipodi:cx="387.14285"
+ sodipodi:cy="486.64789"
+ sodipodi:rx="102.85715"
+ sodipodi:ry="102.85715"
+ d="M 288.18709,458.58814 A 102.85715,102.85715 0 0 1 292.71022,445.8794 L 387.14285,486.64789 z"
+ sodipodi:start="3.4178981"
+ sodipodi:end="3.5491417" />
+ <path
+ sodipodi:end="4.5812194"
+ sodipodi:start="4.3329848"
+ d="M 349.04795,391.10538 A 102.85715,102.85715 0 0 1 373.68978,384.67433 L 387.14285,486.64789 z"
+ sodipodi:ry="102.85715"
+ sodipodi:rx="102.85715"
+ sodipodi:cy="486.64789"
+ sodipodi:cx="387.14285"
+ id="path2790"
+ style="fill:#00e000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.70000005;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:type="arc" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#00cd00;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.70000005;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path2792"
+ sodipodi:cx="387.14285"
+ sodipodi:cy="486.64789"
+ sodipodi:rx="102.85715"
+ sodipodi:ry="102.85715"
+ d="M 314.15905,414.1706 A 102.85715,102.85715 0 0 1 329.91339,401.18221 L 387.14285,486.64789 z"
+ sodipodi:start="3.9235087"
+ sodipodi:end="4.1223451" />
+ <path
+ sodipodi:end="5.3665573"
+ sodipodi:start="5.1452281"
+ d="M 430.28626,393.27638 A 102.85715,102.85715 0 0 1 449.73137,405.02516 L 387.14285,486.64789 z"
+ sodipodi:ry="102.85715"
+ sodipodi:rx="102.85715"
+ sodipodi:cy="486.64789"
+ sodipodi:cx="387.14285"
+ id="path2794"
+ style="fill:#63cd00;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.70000005;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:type="arc" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#00cd6d;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.70000005;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path2796"
+ sodipodi:cx="387.14285"
+ sodipodi:cy="486.64789"
+ sodipodi:rx="102.85715"
+ sodipodi:ry="102.85715"
+ d="M 489.40234,475.57586 A 102.85715,102.85715 0 0 1 489.93906,490.18795 L 387.14285,486.64789 z"
+ sodipodi:start="6.1753316"
+ sodipodi:end="6.3176094" />
+ <path
+ sodipodi:end="0.46670819"
+ sodipodi:start="0.270173"
+ d="M 486.26883,514.10027 A 102.85715,102.85715 0 0 1 478.99987,532.92835 L 387.14285,486.64789 z"
+ sodipodi:ry="102.85715"
+ sodipodi:rx="102.85715"
+ sodipodi:cy="486.64789"
+ sodipodi:cx="387.14285"
+ id="path2798"
+ style="fill:#00b600;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.70000005;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:type="arc" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#00cd28;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.70000005;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path2800"
+ sodipodi:cx="387.14285"
+ sodipodi:cy="486.64789"
+ sodipodi:rx="102.85715"
+ sodipodi:ry="102.85715"
+ d="M 402.60529,588.33617 A 102.85715,102.85715 0 0 1 387.01799,589.50496 L 387.14285,486.64789 z"
+ sodipodi:start="1.419895"
+ sodipodi:end="1.5720103" />
+ <path
+ sodipodi:end="1.1096195"
+ sodipodi:start="0.95694208"
+ d="M 446.39087,570.72681 A 102.85715,102.85715 0 0 1 432.91452,578.75949 L 387.14285,486.64789 z"
+ sodipodi:ry="102.85715"
+ sodipodi:rx="102.85715"
+ sodipodi:cy="486.64789"
+ sodipodi:cx="387.14285"
+ id="path2802"
+ style="fill:#30cd00;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.70000005;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:type="arc" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#00e400;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.70000005;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path2804"
+ sodipodi:cx="387.14285"
+ sodipodi:cy="486.64789"
+ sodipodi:rx="102.85715"
+ sodipodi:ry="102.85715"
+ d="M 341.16962,578.65905 A 102.85715,102.85715 0 0 1 324.52646,568.24924 L 387.14285,486.64789 z"
+ sodipodi:start="2.0341626"
+ sodipodi:end="2.2253062" />
+ <path
+ sodipodi:end="2.790794"
+ sodipodi:start="2.5992668"
+ d="M 299.04462,539.7355 A 102.85715,102.85715 0 0 1 290.54985,521.99453 L 387.14285,486.64789 z"
+ sodipodi:ry="102.85715"
+ sodipodi:rx="102.85715"
+ sodipodi:cy="486.64789"
+ sodipodi:cx="387.14285"
+ id="path2806"
+ style="fill:#00cd40;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.70000005;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:type="arc" />
+ </g>
+ <path
+ sodipodi:type="arc"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.70000005;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path1874"
+ sodipodi:cx="166"
+ sodipodi:cy="166.36218"
+ sodipodi:rx="51"
+ sodipodi:ry="50"
+ d="M 209.78366,192.0026 A 51,50 0 1 1 209.78386,192.00227"
+ sodipodi:start="0.53845272"
+ sodipodi:end="6.8216304"
+ sodipodi:open="true"
+ transform="matrix(0.5777973,0,0,0.5777973,-58.626305,-61.237609)" />
+ <path
+ sodipodi:type="arc"
+ style="fill:url(#radialGradient2778);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.70000005;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path2762"
+ sodipodi:cx="409"
+ sodipodi:cy="476.86218"
+ sodipodi:rx="106"
+ sodipodi:ry="91"
+ d="M 500.00134,523.52773 A 106,91 0 1 1 500.00175,523.52714"
+ sodipodi:start="0.53845272"
+ sodipodi:end="6.8216304"
+ sodipodi:open="true"
+ transform="matrix(0.5777973,0,0,0.5777973,-199.89771,-247.86605)" />
+ <g
+ id="g4004"
+ transform="translate(98.8816,0)">
+ <text
+ xml:space="preserve"
+ style="font-size:24px;font-style:normal;font-weight:normal;opacity:0.94100294;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;filter:url(#filter4000);font-family:Bitstream Vera Sans"
+ id="text3950"
+ transform="translate(-98.287844,0.3535534)"><textPath
+ xlink:href="#path3175"
+ id="textPath3952"><tspan
+ id="tspan3954"
+ style="font-size:24px">No Data to show</tspan></textPath></text>
+ <text
+ transform="translate(-98.99495,-0.3535534)"
+ id="text3171"
+ style="font-size:24px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Bitstream Vera Sans"
+ xml:space="preserve"><textPath
+ id="textPath3947"
+ xlink:href="#path3175"><tspan
+ style="font-size:24px"
+ id="tspan3173">No Data to show</tspan></textPath></text>
+ </g>
+ <path
+ sodipodi:type="arc"
+ style="opacity:0.5;fill:none;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path3175"
+ sodipodi:cx="136.11806"
+ sodipodi:cy="33.59441"
+ sodipodi:rx="65.053825"
+ sodipodi:ry="65.053825"
+ d="M 71.06568,33.160371 A 65.053825,65.053825 0 0 1 201.17151,33.813888"
+ sodipodi:start="3.1482647"
+ sodipodi:end="6.2865591"
+ sodipodi:open="true"
+ transform="translate(-99.61879,-1.5)" />
+ </g>
+</svg>
diff --git a/openfovea/Icon/event.ico b/openfovea/Icon/event.ico
new file mode 100644
index 0000000..8113b4b
Binary files /dev/null and b/openfovea/Icon/event.ico differ
diff --git a/openfovea/Icon/event.png b/openfovea/Icon/event.png
new file mode 100644
index 0000000..5c81385
Binary files /dev/null and b/openfovea/Icon/event.png differ
diff --git a/openfovea/Icon/event.svg b/openfovea/Icon/event.svg
new file mode 100644
index 0000000..bdc6399
--- /dev/null
+++ b/openfovea/Icon/event.svg
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="24"
+ height="24"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.47 r22583"
+ sodipodi:docname="event.svg"
+ inkscape:export-filename="event.png"
+ inkscape:export-xdpi="28.33"
+ inkscape:export-ydpi="28.33">
+ <defs
+ id="defs4">
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 526.18109 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="744.09448 : 526.18109 : 1"
+ inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+ id="perspective10" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="4.3671875"
+ inkscape:cx="-18.626592"
+ inkscape:cy="29.168491"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:window-width="1101"
+ inkscape:window-height="814"
+ inkscape:window-x="95"
+ inkscape:window-y="64"
+ inkscape:window-maximized="0" />
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Calque 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-1028.3622)">
+ <path
+ style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ d="m 0.51520576,1030.141 3.55436914,0 c 10.3961131,-0.3272 14.0666961,19.4769 14.0666961,19.4769 l 0.177096,-19.5206 5.26861,0"
+ id="path2816"
+ sodipodi:nodetypes="ccccc" />
+ </g>
+</svg>
diff --git a/openfovea/Icon/export.ico b/openfovea/Icon/export.ico
new file mode 100644
index 0000000..dbb63b2
Binary files /dev/null and b/openfovea/Icon/export.ico differ
diff --git a/openfovea/Icon/export.png b/openfovea/Icon/export.png
new file mode 100644
index 0000000..d6bea17
Binary files /dev/null and b/openfovea/Icon/export.png differ
diff --git a/openfovea/Icon/export.svg b/openfovea/Icon/export.svg
new file mode 100644
index 0000000..8ac19fc
--- /dev/null
+++ b/openfovea/Icon/export.svg
@@ -0,0 +1,375 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="64"
+ height="64"
+ id="svg2"
+ sodipodi:version="0.32"
+ inkscape:version="0.47 r22583"
+ version="1.0"
+ sodipodi:docname="export.svg"
+ inkscape:export-filename="export.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape">
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="2.8284271"
+ inkscape:cx="74.547595"
+ inkscape:cy="85.267813"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ width="64px"
+ height="64px"
+ inkscape:window-width="1280"
+ inkscape:window-height="949"
+ inkscape:window-x="0"
+ inkscape:window-y="25"
+ showgrid="false"
+ inkscape:window-maximized="0" />
+ <defs
+ id="defs4">
+ <marker
+ style="overflow:visible"
+ id="TriangleOutS"
+ refX="0.0"
+ refY="0.0"
+ orient="auto"
+ inkscape:stockid="TriangleOutS">
+ <path
+ transform="scale(0.2)"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none"
+ d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
+ id="path3966" />
+ </marker>
+ <marker
+ style="overflow:visible;"
+ id="Arrow2Send"
+ refX="0.0"
+ refY="0.0"
+ orient="auto"
+ inkscape:stockid="Arrow2Send">
+ <path
+ transform="scale(0.3) rotate(180) translate(-2.3,0)"
+ d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
+ style="font-size:12.0;fill-rule:evenodd;stroke-width:0.62500000;stroke-linejoin:round;"
+ id="path3850" />
+ </marker>
+ <marker
+ style="overflow:visible;"
+ id="Arrow1Send"
+ refX="0.0"
+ refY="0.0"
+ orient="auto"
+ inkscape:stockid="Arrow1Send">
+ <path
+ transform="scale(0.2) rotate(180) translate(6,0)"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none;"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ id="path3832" />
+ </marker>
+ <marker
+ style="overflow:visible;"
+ id="Arrow2Lend"
+ refX="0.0"
+ refY="0.0"
+ orient="auto"
+ inkscape:stockid="Arrow2Lend">
+ <path
+ transform="scale(1.1) rotate(180) translate(1,0)"
+ d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
+ style="font-size:12.0;fill-rule:evenodd;stroke-width:0.62500000;stroke-linejoin:round;"
+ id="path3838" />
+ </marker>
+ <marker
+ style="overflow:visible;"
+ id="Arrow1Lend"
+ refX="0.0"
+ refY="0.0"
+ orient="auto"
+ inkscape:stockid="Arrow1Lend">
+ <path
+ transform="scale(0.8) rotate(180) translate(12.5,0)"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none;"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ id="path3820" />
+ </marker>
+ <inkscape:perspective
+ id="perspective32"
+ inkscape:persp3d-origin="32 : 21.333333 : 1"
+ inkscape:vp_z="64 : 32 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 32 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <linearGradient
+ id="linearGradient2832">
+ <stop
+ style="stop-color:#f2ffff;stop-opacity:0;"
+ offset="0"
+ id="stop2834" />
+ <stop
+ style="stop-color:#f2ffff;stop-opacity:0.7037037;"
+ offset="1"
+ id="stop2838" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2772">
+ <stop
+ id="stop2774"
+ offset="0"
+ style="stop-color:#f2ffff;stop-opacity:0.88148147;" />
+ <stop
+ style="stop-color:#f2ffff;stop-opacity:0.38518518;"
+ offset="0.82051283"
+ id="stop2780" />
+ <stop
+ id="stop2776"
+ offset="1"
+ style="stop-color:#f2ffff;stop-opacity:0;" />
+ </linearGradient>
+ <radialGradient
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,1.04908,0,-23.40425)"
+ r="81.502647"
+ fy="438.73355"
+ fx="387.5"
+ cy="438.73355"
+ cx="387.5"
+ id="radialGradient2778"
+ xlink:href="#linearGradient2772"
+ inkscape:collect="always" />
+ <radialGradient
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(0,3.116813e-5)"
+ r="102.86049"
+ fy="486.64789"
+ fx="387.14285"
+ cy="486.64789"
+ cx="387.14285"
+ id="radialGradient2830"
+ xlink:href="#linearGradient2832"
+ inkscape:collect="always" />
+ <marker
+ style="overflow:visible"
+ id="TriangleOutSp"
+ refX="0.0"
+ refY="0.0"
+ orient="auto"
+ inkscape:stockid="TriangleOutSp">
+ <path
+ transform="scale(0.2)"
+ style="marker-start:none;stroke:#0000ff;stroke-width:1.0pt;fill:#0000ff;fill-rule:evenodd"
+ d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
+ id="path5423" />
+ </marker>
+ </defs>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ style="display:inline"
+ inkscape:label="Iris"
+ id="layer2"
+ inkscape:groupmode="layer">
+ <path
+ transform="matrix(0.29079,0,0,0.29079,-80.27129,-109.4375)"
+ sodipodi:open="true"
+ sodipodi:end="6.8216304"
+ sodipodi:start="0.53845272"
+ d="M 475.44604,539.39388 A 102.85715,102.85715 0 1 1 475.44644,539.39321"
+ sodipodi:ry="102.85715"
+ sodipodi:rx="102.85715"
+ sodipodi:cy="486.64789"
+ sodipodi:cx="387.14285"
+ id="path1872"
+ style="fill:lime;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.70000005;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:type="arc" />
+ <path
+ sodipodi:type="arc"
+ style="fill:url(#radialGradient2830);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.70000005;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path2822"
+ sodipodi:cx="387.14285"
+ sodipodi:cy="486.64789"
+ sodipodi:rx="102.85715"
+ sodipodi:ry="102.85715"
+ d="M 475.44604,539.39388 A 102.85715,102.85715 0 1 1 475.44644,539.39321"
+ sodipodi:start="0.53845272"
+ sodipodi:end="6.8216304"
+ sodipodi:open="true"
+ transform="matrix(0.29079,0,0,0.29079,-80.27129,-109.4375)" />
+ </g>
+ <g
+ style="display:inline"
+ id="layer1"
+ inkscape:groupmode="layer"
+ inkscape:label="Calque 1">
+ <g
+ transform="matrix(0.29079,0,0,0.29079,-80.37517,-109.4999)"
+ id="g2808">
+ <path
+ sodipodi:end="3.5491417"
+ sodipodi:start="3.4178981"
+ d="m 288.18709,458.58814 c 1.22799,-4.33066 2.73895,-8.57603 4.52313,-12.70874 l 94.43263,40.76849 z"
+ sodipodi:ry="102.85715"
+ sodipodi:rx="102.85715"
+ sodipodi:cy="486.64789"
+ sodipodi:cx="387.14285"
+ id="path2788"
+ style="fill:#00cd00;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.70000005;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:type="arc" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#00e000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.70000005;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path2790"
+ sodipodi:cx="387.14285"
+ sodipodi:cy="486.64789"
+ sodipodi:rx="102.85715"
+ sodipodi:ry="102.85715"
+ d="m 349.04795,391.10538 c 7.91582,-3.15621 16.19319,-5.31645 24.64183,-6.43105 l 13.45307,101.97356 z"
+ sodipodi:start="4.3329848"
+ sodipodi:end="4.5812194" />
+ <path
+ sodipodi:end="4.1223451"
+ sodipodi:start="3.9235087"
+ d="m 314.15905,414.1706 c 4.80767,-4.84127 10.08511,-9.19216 15.75434,-12.98839 l 57.22946,85.46568 z"
+ sodipodi:ry="102.85715"
+ sodipodi:rx="102.85715"
+ sodipodi:cy="486.64789"
+ sodipodi:cx="387.14285"
+ id="path2792"
+ style="fill:#00cd00;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.70000005;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:type="arc" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#63cd00;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.70000005;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path2794"
+ sodipodi:cx="387.14285"
+ sodipodi:cy="486.64789"
+ sodipodi:rx="102.85715"
+ sodipodi:ry="102.85715"
+ d="m 430.28626,393.27638 c 6.89565,3.18621 13.41713,7.1265 19.44511,11.74878 l -62.58852,81.62273 z"
+ sodipodi:start="5.1452281"
+ sodipodi:end="5.3665573" />
+ <path
+ sodipodi:end="6.3176094"
+ sodipodi:start="6.1753316"
+ d="m 489.40234,475.57586 c 0.52532,4.8518 0.70468,9.73483 0.53672,14.61209 l -102.79621,-3.54006 z"
+ sodipodi:ry="102.85715"
+ sodipodi:rx="102.85715"
+ sodipodi:cy="486.64789"
+ sodipodi:cx="387.14285"
+ id="path2796"
+ style="fill:#00cd6d;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.70000005;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:type="arc" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#00b600;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.70000005;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path2798"
+ sodipodi:cx="387.14285"
+ sodipodi:cy="486.64789"
+ sodipodi:rx="102.85715"
+ sodipodi:ry="102.85715"
+ d="m 486.26883,514.10027 c -1.7999,6.49915 -4.23461,12.80553 -7.26896,18.82808 l -91.85702,-46.28046 z"
+ sodipodi:start="0.270173"
+ sodipodi:end="0.46670819" />
+ <path
+ sodipodi:end="1.5720103"
+ sodipodi:start="1.419895"
+ d="m 402.60529,588.33617 c -5.1586,0.7844 -10.36941,1.17512 -15.5873,1.16879 l 0.12486,-102.85707 z"
+ sodipodi:ry="102.85715"
+ sodipodi:rx="102.85715"
+ sodipodi:cy="486.64789"
+ sodipodi:cx="387.14285"
+ id="path2800"
+ style="fill:#00cd28;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.70000005;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:type="arc" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#30cd00;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.70000005;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path2802"
+ sodipodi:cx="387.14285"
+ sodipodi:cy="486.64789"
+ sodipodi:rx="102.85715"
+ sodipodi:ry="102.85715"
+ d="m 446.39087,570.72681 c -4.28107,3.01675 -8.78629,5.70212 -13.47635,8.03268 l -45.77167,-92.1116 z"
+ sodipodi:start="0.95694208"
+ sodipodi:end="1.1096195" />
+ <path
+ sodipodi:end="2.2253062"
+ sodipodi:start="2.0341626"
+ d="m 341.16962,578.65905 c -5.86691,-2.93139 -11.44001,-6.4172 -16.64316,-10.40981 l 62.61639,-81.60135 z"
+ sodipodi:ry="102.85715"
+ sodipodi:rx="102.85715"
+ sodipodi:cy="486.64789"
+ sodipodi:cx="387.14285"
+ id="path2804"
+ style="fill:#00e400;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.70000005;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:type="arc" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#00cd40;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.70000005;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path2806"
+ sodipodi:cx="387.14285"
+ sodipodi:cy="486.64789"
+ sodipodi:rx="102.85715"
+ sodipodi:ry="102.85715"
+ d="m 299.04462,539.7355 c -3.39183,-5.62871 -6.23642,-11.56952 -8.49477,-17.74097 l 96.593,-35.34664 z"
+ sodipodi:start="2.5992668"
+ sodipodi:end="2.790794" />
+ </g>
+ <path
+ transform="matrix(0.29079,0,0,0.29079,-15.6744,-15.86547)"
+ sodipodi:open="true"
+ sodipodi:end="6.8216304"
+ sodipodi:start="0.53845272"
+ d="M 209.78366,192.0026 A 51,50 0 1 1 209.78386,192.00227"
+ sodipodi:ry="50"
+ sodipodi:rx="51"
+ sodipodi:cy="166.36218"
+ sodipodi:cx="166"
+ id="path1874"
+ style="fill:black;fill-opacity:1;fill-rule:nonzero;stroke:black;stroke-width:1.70000005;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:type="arc" />
+ <path
+ transform="matrix(0.29079,0,0,0.29079,-86.77254,-109.7906)"
+ sodipodi:open="true"
+ sodipodi:end="6.8216304"
+ sodipodi:start="0.53845272"
+ d="M 500.00134,523.52773 A 106,91 0 1 1 500.00175,523.52714"
+ sodipodi:ry="91"
+ sodipodi:rx="106"
+ sodipodi:cy="476.86218"
+ sodipodi:cx="409"
+ id="path2762"
+ style="fill:url(#radialGradient2778);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.70000005;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:type="arc" />
+ <path
+ style="fill:#0000ff;fill-opacity:0.57499999;stroke:none"
+ d="M 27.223611,20.866486 19.091883,9.1992245 65.760931,2.8352625 48.436814,47.736543 39.59798,36.069281 13.965359,55.42633 c 0,0 -7.4350825,4.161152 -12.6395335,-3.093593 -5.204451,-7.254745 1.414214,-12.904698 1.414214,-12.904698 L 27.223611,20.866486 z"
+ id="path5508"
+ sodipodi:nodetypes="cccccczcc" />
+ </g>
+</svg>
diff --git a/openfovea/Icon/export_curve.ico b/openfovea/Icon/export_curve.ico
new file mode 100644
index 0000000..c7fb448
Binary files /dev/null and b/openfovea/Icon/export_curve.ico differ
diff --git a/openfovea/Icon/export_curve.png b/openfovea/Icon/export_curve.png
new file mode 100644
index 0000000..f53ec19
Binary files /dev/null and b/openfovea/Icon/export_curve.png differ
diff --git a/openfovea/Icon/icon64x64.svg b/openfovea/Icon/icon64x64.svg
new file mode 100644
index 0000000..332de46
--- /dev/null
+++ b/openfovea/Icon/icon64x64.svg
@@ -0,0 +1,290 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="64"
+ height="64"
+ id="svg2"
+ sodipodi:version="0.32"
+ inkscape:version="0.46"
+ version="1.0"
+ sodipodi:docbase="/home/charles/Projet/AFM/OpenFovea/Icon"
+ sodipodi:docname="icon64x64.svg"
+ inkscape:export-filename="/home/charles/Bazaar/OpenFovea/packaging/openfovea/Icon/openfovea.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape">
+ <defs
+ id="defs4">
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 32 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="64 : 32 : 1"
+ inkscape:persp3d-origin="32 : 21.333333 : 1"
+ id="perspective32" />
+ <linearGradient
+ id="linearGradient2832">
+ <stop
+ id="stop2834"
+ offset="0"
+ style="stop-color:#f2ffff;stop-opacity:0;" />
+ <stop
+ id="stop2838"
+ offset="1"
+ style="stop-color:#f2ffff;stop-opacity:0.7037037;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2772">
+ <stop
+ style="stop-color:#f2ffff;stop-opacity:0.88148147;"
+ offset="0"
+ id="stop2774" />
+ <stop
+ id="stop2780"
+ offset="0.82051283"
+ style="stop-color:#f2ffff;stop-opacity:0.38518518;" />
+ <stop
+ style="stop-color:#f2ffff;stop-opacity:0;"
+ offset="1"
+ id="stop2776" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2772"
+ id="radialGradient2778"
+ cx="387.5"
+ cy="438.73355"
+ fx="387.5"
+ fy="438.73355"
+ r="81.502647"
+ gradientTransform="matrix(1,0,0,1.04908,0,-23.40425)"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2832"
+ id="radialGradient2830"
+ cx="387.14285"
+ cy="486.64789"
+ fx="387.14285"
+ fy="486.64789"
+ r="102.86049"
+ gradientTransform="translate(0,3.116813e-5)"
+ gradientUnits="userSpaceOnUse" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="2.8284271"
+ inkscape:cx="149.35582"
+ inkscape:cy="100.06507"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ width="64px"
+ height="64px"
+ inkscape:window-width="1280"
+ inkscape:window-height="949"
+ inkscape:window-x="0"
+ inkscape:window-y="25"
+ showgrid="false" />
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:groupmode="layer"
+ id="layer2"
+ inkscape:label="Iris"
+ style="display:inline">
+ <path
+ sodipodi:type="arc"
+ style="fill:lime;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.70000005;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path1872"
+ sodipodi:cx="387.14285"
+ sodipodi:cy="486.64789"
+ sodipodi:rx="102.85715"
+ sodipodi:ry="102.85715"
+ d="M 475.44604,539.39388 A 102.85715,102.85715 0 1 1 475.44644,539.39321"
+ sodipodi:start="0.53845272"
+ sodipodi:end="6.8216304"
+ sodipodi:open="true"
+ transform="matrix(0.29079,0,0,0.29079,-80.27129,-109.4375)" />
+ <path
+ transform="matrix(0.29079,0,0,0.29079,-80.27129,-109.4375)"
+ sodipodi:open="true"
+ sodipodi:end="6.8216304"
+ sodipodi:start="0.53845272"
+ d="M 475.44604,539.39388 A 102.85715,102.85715 0 1 1 475.44644,539.39321"
+ sodipodi:ry="102.85715"
+ sodipodi:rx="102.85715"
+ sodipodi:cy="486.64789"
+ sodipodi:cx="387.14285"
+ id="path2822"
+ style="fill:url(#radialGradient2830);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.70000005;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:type="arc" />
+ </g>
+ <g
+ inkscape:label="Calque 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ style="display:inline">
+ <g
+ id="g2808"
+ transform="matrix(0.29079,0,0,0.29079,-80.37517,-109.4999)">
+ <path
+ sodipodi:type="arc"
+ style="fill:#00cd00;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.70000005;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path2788"
+ sodipodi:cx="387.14285"
+ sodipodi:cy="486.64789"
+ sodipodi:rx="102.85715"
+ sodipodi:ry="102.85715"
+ d="M 288.18709,458.58814 A 102.85715,102.85715 0 0 1 292.71022,445.8794 L 387.14285,486.64789 z"
+ sodipodi:start="3.4178981"
+ sodipodi:end="3.5491417" />
+ <path
+ sodipodi:end="4.5812194"
+ sodipodi:start="4.3329848"
+ d="M 349.04795,391.10538 A 102.85715,102.85715 0 0 1 373.68978,384.67433 L 387.14285,486.64789 z"
+ sodipodi:ry="102.85715"
+ sodipodi:rx="102.85715"
+ sodipodi:cy="486.64789"
+ sodipodi:cx="387.14285"
+ id="path2790"
+ style="fill:#00e000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.70000005;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:type="arc" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#00cd00;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.70000005;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path2792"
+ sodipodi:cx="387.14285"
+ sodipodi:cy="486.64789"
+ sodipodi:rx="102.85715"
+ sodipodi:ry="102.85715"
+ d="M 314.15905,414.1706 A 102.85715,102.85715 0 0 1 329.91339,401.18221 L 387.14285,486.64789 z"
+ sodipodi:start="3.9235087"
+ sodipodi:end="4.1223451" />
+ <path
+ sodipodi:end="5.3665573"
+ sodipodi:start="5.1452281"
+ d="M 430.28626,393.27638 A 102.85715,102.85715 0 0 1 449.73137,405.02516 L 387.14285,486.64789 z"
+ sodipodi:ry="102.85715"
+ sodipodi:rx="102.85715"
+ sodipodi:cy="486.64789"
+ sodipodi:cx="387.14285"
+ id="path2794"
+ style="fill:#63cd00;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.70000005;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:type="arc" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#00cd6d;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.70000005;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path2796"
+ sodipodi:cx="387.14285"
+ sodipodi:cy="486.64789"
+ sodipodi:rx="102.85715"
+ sodipodi:ry="102.85715"
+ d="M 489.40234,475.57586 A 102.85715,102.85715 0 0 1 489.93906,490.18795 L 387.14285,486.64789 z"
+ sodipodi:start="6.1753316"
+ sodipodi:end="6.3176094" />
+ <path
+ sodipodi:end="0.46670819"
+ sodipodi:start="0.270173"
+ d="M 486.26883,514.10027 A 102.85715,102.85715 0 0 1 478.99987,532.92835 L 387.14285,486.64789 z"
+ sodipodi:ry="102.85715"
+ sodipodi:rx="102.85715"
+ sodipodi:cy="486.64789"
+ sodipodi:cx="387.14285"
+ id="path2798"
+ style="fill:#00b600;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.70000005;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:type="arc" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#00cd28;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.70000005;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path2800"
+ sodipodi:cx="387.14285"
+ sodipodi:cy="486.64789"
+ sodipodi:rx="102.85715"
+ sodipodi:ry="102.85715"
+ d="M 402.60529,588.33617 A 102.85715,102.85715 0 0 1 387.01799,589.50496 L 387.14285,486.64789 z"
+ sodipodi:start="1.419895"
+ sodipodi:end="1.5720103" />
+ <path
+ sodipodi:end="1.1096195"
+ sodipodi:start="0.95694208"
+ d="M 446.39087,570.72681 A 102.85715,102.85715 0 0 1 432.91452,578.75949 L 387.14285,486.64789 z"
+ sodipodi:ry="102.85715"
+ sodipodi:rx="102.85715"
+ sodipodi:cy="486.64789"
+ sodipodi:cx="387.14285"
+ id="path2802"
+ style="fill:#30cd00;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.70000005;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:type="arc" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#00e400;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.70000005;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path2804"
+ sodipodi:cx="387.14285"
+ sodipodi:cy="486.64789"
+ sodipodi:rx="102.85715"
+ sodipodi:ry="102.85715"
+ d="M 341.16962,578.65905 A 102.85715,102.85715 0 0 1 324.52646,568.24924 L 387.14285,486.64789 z"
+ sodipodi:start="2.0341626"
+ sodipodi:end="2.2253062" />
+ <path
+ sodipodi:end="2.790794"
+ sodipodi:start="2.5992668"
+ d="M 299.04462,539.7355 A 102.85715,102.85715 0 0 1 290.54985,521.99453 L 387.14285,486.64789 z"
+ sodipodi:ry="102.85715"
+ sodipodi:rx="102.85715"
+ sodipodi:cy="486.64789"
+ sodipodi:cx="387.14285"
+ id="path2806"
+ style="fill:#00cd40;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.70000005;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:type="arc" />
+ </g>
+ <path
+ sodipodi:type="arc"
+ style="fill:black;fill-opacity:1;fill-rule:nonzero;stroke:black;stroke-width:1.70000005;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path1874"
+ sodipodi:cx="166"
+ sodipodi:cy="166.36218"
+ sodipodi:rx="51"
+ sodipodi:ry="50"
+ d="M 209.78366,192.0026 A 51,50 0 1 1 209.78386,192.00227"
+ sodipodi:start="0.53845272"
+ sodipodi:end="6.8216304"
+ sodipodi:open="true"
+ transform="matrix(0.29079,0,0,0.29079,-15.6744,-15.86547)" />
+ <path
+ sodipodi:type="arc"
+ style="fill:url(#radialGradient2778);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.70000005;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path2762"
+ sodipodi:cx="409"
+ sodipodi:cy="476.86218"
+ sodipodi:rx="106"
+ sodipodi:ry="91"
+ d="M 500.00134,523.52773 A 106,91 0 1 1 500.00175,523.52714"
+ sodipodi:start="0.53845272"
+ sodipodi:end="6.8216304"
+ sodipodi:open="true"
+ transform="matrix(0.29079,0,0,0.29079,-86.77254,-109.7906)" />
+ </g>
+</svg>
diff --git a/openfovea/Icon/openfovea.ico b/openfovea/Icon/openfovea.ico
new file mode 100644
index 0000000..3ae0fc0
Binary files /dev/null and b/openfovea/Icon/openfovea.ico differ
diff --git a/openfovea/Icon/openfovea.png b/openfovea/Icon/openfovea.png
new file mode 100644
index 0000000..df4e9b0
Binary files /dev/null and b/openfovea/Icon/openfovea.png differ
diff --git a/openfovea/Icon/tomo_x_square.svg b/openfovea/Icon/tomo_x_square.svg
new file mode 100644
index 0000000..adbb15d
--- /dev/null
+++ b/openfovea/Icon/tomo_x_square.svg
@@ -0,0 +1,187 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="29.899384"
+ height="24.995012"
+ id="svg3280"
+ sodipodi:version="0.32"
+ inkscape:version="0.46"
+ version="1.0"
+ sodipodi:docname="tomo_x_square.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape">
+ <defs
+ id="defs3282">
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="-6.5123916 : 6.2257691 : 0"
+ inkscape:vp_y="1.4135022e-15 : 23.085005 : 0"
+ inkscape:vp_z="8.9844554 : -0.10633669 : 0"
+ inkscape:persp3d-origin="-74.254878 : 13.403064 : 1"
+ id="perspective2430" />
+ <inkscape:perspective
+ id="perspective2420"
+ inkscape:persp3d-origin="-87.44631 : 3.2456637 : 1"
+ inkscape:vp_z="8.9844554 : -0.10633669 : 0"
+ inkscape:vp_y="1.4135022e-15 : 23.085005 : 0"
+ inkscape:vp_x="-6.5123916 : 6.2257691 : 0"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ id="perspective3306"
+ inkscape:persp3d-origin="-63.004878 : 15.188778 : 1"
+ inkscape:vp_z="8.9844554 : -0.10633669 : 0"
+ inkscape:vp_y="1.4135022e-15 : 23.085005 : 0"
+ inkscape:vp_x="-6.5123916 : 6.2257691 : 0"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="-6.5123916 : 6.2257691 : 0"
+ inkscape:vp_y="1.4135022e-15 : 23.085005 : 0"
+ inkscape:vp_z="8.9844554 : -0.10633669 : 0"
+ inkscape:persp3d-origin="-76.19631 : 5.031378 : 1"
+ id="perspective10" />
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 526.18109 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="744.09448 : 526.18109 : 1"
+ inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+ id="perspective3288" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="5.6"
+ inkscape:cx="52.4945"
+ inkscape:cy="28.807076"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:window-width="1131"
+ inkscape:window-height="726"
+ inkscape:window-x="309"
+ inkscape:window-y="147" />
+ <metadata
+ id="metadata3285">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Calque 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(43.261608,-742.32513)">
+ <g
+ sodipodi:type="inkscape:box3d"
+ style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.34055018;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:0.34055018, 0.34055018;stroke-dashoffset:0;stroke-opacity:1"
+ id="g3169"
+ inkscape:perspectiveID="#perspective3306"
+ inkscape:corner0="1.549884 : -0.23976184 : 0 : 1"
+ inkscape:corner7="0.81015214 : -0.64656034 : 1.3234556 : 1">
+ <path
+ sodipodi:type="inkscape:box3dside"
+ id="path3181"
+ style="fill:#e9e9ff;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
+ inkscape:box3dsidetype="11"
+ d="M -17.946194,748.15778 L -13.12877,752.76318 L -13.12877,762.15412 L -17.946194,757.54872 L -17.946194,748.15778 z" />
+ <path
+ sodipodi:type="inkscape:box3dside"
+ id="path3171"
+ style="fill:#353564;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
+ inkscape:box3dsidetype="6"
+ d="M -29.836722,748.01705 L -29.836722,757.40799 L -17.946194,757.54872 L -17.946194,748.15778 L -29.836722,748.01705 z" />
+ <path
+ sodipodi:type="inkscape:box3dside"
+ id="path3179"
+ style="fill:#afafde;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
+ inkscape:box3dsidetype="13"
+ d="M -29.836722,757.40799 L -25.019298,762.01339 L -13.12877,762.15412 L -17.946194,757.54872 L -29.836722,757.40799 z" />
+ <path
+ sodipodi:type="inkscape:box3dside"
+ id="path3173"
+ style="fill:#4d4d9f;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
+ inkscape:box3dsidetype="5"
+ d="M -29.836722,748.01705 L -25.019298,752.62245 L -13.12877,752.76318 L -17.946194,748.15778 L -29.836722,748.01705 z" />
+ <path
+ sodipodi:type="inkscape:box3dside"
+ id="path3177"
+ style="fill:#d7d7ff;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
+ inkscape:box3dsidetype="14"
+ d="M -25.019298,752.62245 L -25.019298,762.01339 L -13.12877,762.15412 L -13.12877,752.76318 L -25.019298,752.62245 z" />
+ <path
+ sodipodi:type="inkscape:box3dside"
+ id="path3175"
+ style="fill:#8686bf;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
+ inkscape:box3dsidetype="3"
+ d="M -29.836722,748.01705 L -25.019298,752.62245 L -25.019298,762.01339 L -29.836722,757.40799 L -29.836722,748.01705 z" />
+ </g>
+ <rect
+ style="fill:#474747;fill-opacity:0.58895706;stroke:#000000;stroke-width:0.00597004;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:0.00597003, 0.00597003;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect3278"
+ width="17.792322"
+ height="10.059039"
+ x="774.87329"
+ y="-45.206345"
+ transform="matrix(0,1,0.6980577,0.7160415,0,0)" />
+ <g
+ inkscape:corner7="0.81015214 : -0.2043281 : 1.3781759 : 1"
+ inkscape:corner0="1.549884 : 0.2024704 : 0 : 1"
+ inkscape:perspectiveID="#perspective10"
+ id="g3233"
+ style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.34055018;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:0.34055018, 0.34055018;stroke-dashoffset:0;stroke-opacity:1"
+ sodipodi:type="inkscape:box3d">
+ <path
+ d="M -30.645994,748.11206 L -25.82857,752.71746 L -25.82857,762.10841 L -30.645994,757.50301 L -30.645994,748.11206 z"
+ inkscape:box3dsidetype="11"
+ style="fill:#e9e9ff;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
+ id="path3239"
+ sodipodi:type="inkscape:box3dside" />
+ <path
+ d="M -43.028154,747.96551 L -43.028154,757.35646 L -30.645994,757.50301 L -30.645994,748.11206 L -43.028154,747.96551 z"
+ inkscape:box3dsidetype="6"
+ style="fill:#353564;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
+ id="path3237"
+ sodipodi:type="inkscape:box3dside" />
+ <path
+ d="M -43.028154,757.35646 L -38.21073,761.96186 L -25.82857,762.10841 L -30.645994,757.50301 L -43.028154,757.35646 z"
+ inkscape:box3dsidetype="13"
+ style="fill:#afafde;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
+ id="path3235"
+ sodipodi:type="inkscape:box3dside" />
+ <path
+ d="M -43.028154,747.96551 L -38.21073,752.57091 L -25.82857,752.71746 L -30.645994,748.11206 L -43.028154,747.96551 z"
+ inkscape:box3dsidetype="5"
+ style="fill:#4d4d9f;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
+ id="path3241"
+ sodipodi:type="inkscape:box3dside" />
+ <path
+ d="M -38.21073,752.57091 L -38.21073,761.96186 L -25.82857,762.10841 L -25.82857,752.71746 L -38.21073,752.57091 z"
+ inkscape:box3dsidetype="14"
+ style="fill:#d7d7ff;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
+ id="path3243"
+ sodipodi:type="inkscape:box3dside" />
+ <path
+ d="M -43.028154,747.96551 L -38.21073,752.57091 L -38.21073,761.96186 L -43.028154,757.35646 L -43.028154,747.96551 z"
+ inkscape:box3dsidetype="3"
+ style="fill:#8686bf;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
+ id="path3245"
+ sodipodi:type="inkscape:box3dside" />
+ </g>
+ </g>
+</svg>
diff --git a/openfovea/Icon/tomo_y_square.svg b/openfovea/Icon/tomo_y_square.svg
new file mode 100644
index 0000000..e610bd5
--- /dev/null
+++ b/openfovea/Icon/tomo_y_square.svg
@@ -0,0 +1,173 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="29.468185"
+ height="25.000004"
+ id="svg3308"
+ sodipodi:version="0.32"
+ inkscape:version="0.46"
+ version="1.0"
+ sodipodi:docname="tomo_y_square.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape">
+ <defs
+ id="defs3310">
+ <inkscape:perspective
+ id="perspective3334"
+ inkscape:persp3d-origin="-364.97991 : 19.498083 : 1"
+ inkscape:vp_z="11.676602 : -0.13819994 : 0"
+ inkscape:vp_y="1.8370509e-15 : 30.002309 : 0"
+ inkscape:vp_x="-8.4637967 : 8.0912892 : 0"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="-8.4637967 : 8.0912892 : 0"
+ inkscape:vp_y="1.8370509e-15 : 30.002309 : 0"
+ inkscape:vp_z="11.676602 : -0.13819994 : 0"
+ inkscape:persp3d-origin="-357.86507 : -0.38916702 : 1"
+ id="perspective10" />
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 526.18109 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="744.09448 : 526.18109 : 1"
+ inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+ id="perspective3316" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="11.684154"
+ inkscape:cx="25.34874"
+ inkscape:cy="11.889522"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:window-width="1001"
+ inkscape:window-height="755"
+ inkscape:window-x="577"
+ inkscape:window-y="95" />
+ <metadata
+ id="metadata3313">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Calque 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(189.09959,296.32348)">
+ <g
+ sodipodi:type="inkscape:box3d"
+ style="opacity:1;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.34055018;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:0.34055018, 0.34055018;stroke-dashoffset:0;stroke-opacity:1"
+ id="g3169"
+ inkscape:perspectiveID="#perspective3334"
+ inkscape:corner0="1.549884 : -0.23976184 : 0 : 1"
+ inkscape:corner7="0.81015214 : -0.64656034 : 1.3234556 : 1">
+ <path
+ sodipodi:type="inkscape:box3dside"
+ id="path3181"
+ style="fill:#e9e9ff;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
+ inkscape:box3dsidetype="11"
+ d="M -173.54476,-295.98581 L -167.28382,-290.00042 L -167.28382,-277.79553 L -173.54476,-283.78091 L -173.54476,-295.98581 z" />
+ <path
+ sodipodi:type="inkscape:box3dside"
+ id="path3171"
+ style="fill:#353564;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
+ inkscape:box3dsidetype="6"
+ d="M -188.99822,-296.16871 L -188.99822,-283.96382 L -173.54476,-283.78091 L -173.54476,-295.98581 L -188.99822,-296.16871 z" />
+ <path
+ sodipodi:type="inkscape:box3dside"
+ id="path3179"
+ style="fill:#afafde;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
+ inkscape:box3dsidetype="13"
+ d="M -188.99822,-283.96382 L -182.73728,-277.97843 L -167.28382,-277.79553 L -173.54476,-283.78091 L -188.99822,-283.96382 z" />
+ <path
+ sodipodi:type="inkscape:box3dside"
+ id="path3173"
+ style="fill:#4d4d9f;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
+ inkscape:box3dsidetype="5"
+ d="M -188.99822,-296.16871 L -182.73728,-290.18333 L -167.28382,-290.00042 L -173.54476,-295.98581 L -188.99822,-296.16871 z" />
+ <path
+ sodipodi:type="inkscape:box3dside"
+ id="path3177"
+ style="fill:#d7d7ff;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
+ inkscape:box3dsidetype="14"
+ d="M -182.73728,-290.18333 L -182.73728,-277.97843 L -167.28382,-277.79553 L -167.28382,-290.00042 L -182.73728,-290.18333 z" />
+ <path
+ sodipodi:type="inkscape:box3dside"
+ id="path3175"
+ style="fill:#8686bf;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
+ inkscape:box3dsidetype="3"
+ d="M -188.99822,-296.16871 L -182.73728,-290.18333 L -182.73728,-277.97843 L -188.99822,-283.96382 L -188.99822,-296.16871 z" />
+ </g>
+ <rect
+ style="opacity:1;fill:#474747;fill-opacity:0.58895706;stroke:#000000;stroke-width:0.00943256;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:0.00943256, 0.00943256;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect3278"
+ width="23.120758"
+ height="19.323767"
+ x="-188.452"
+ y="-294.07654"
+ transform="matrix(1,0,-3.190733e-3,0.9999949,0,0)" />
+ <g
+ inkscape:corner7="0.81015214 : -0.2043281 : 1.3781759 : 1"
+ inkscape:corner0="1.549884 : 0.2024704 : 0 : 1"
+ inkscape:perspectiveID="#perspective10"
+ id="g3233"
+ style="opacity:1;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.34055018;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:0.34055018, 0.34055018;stroke-dashoffset:0;stroke-opacity:1"
+ sodipodi:type="inkscape:box3d">
+ <path
+ d="M -165.79097,-289.35898 L -159.53003,-283.3736 L -159.53003,-271.16871 L -165.79097,-277.15409 L -165.79097,-289.35898 z"
+ inkscape:box3dsidetype="11"
+ style="fill:#e9e9ff;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
+ id="path3239"
+ sodipodi:type="inkscape:box3dside" />
+ <path
+ d="M -181.88338,-289.54945 L -181.88338,-277.34455 L -165.79097,-277.15409 L -165.79097,-289.35898 L -181.88338,-289.54945 z"
+ inkscape:box3dsidetype="6"
+ style="fill:#353564;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
+ id="path3237"
+ sodipodi:type="inkscape:box3dside" />
+ <path
+ d="M -181.88338,-277.34455 L -175.62244,-271.35917 L -159.53003,-271.16871 L -165.79097,-277.15409 L -181.88338,-277.34455 z"
+ inkscape:box3dsidetype="13"
+ style="fill:#afafde;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
+ id="path3235"
+ sodipodi:type="inkscape:box3dside" />
+ <path
+ d="M -181.88338,-289.54945 L -175.62244,-283.56406 L -159.53003,-283.3736 L -165.79097,-289.35898 L -181.88338,-289.54945 z"
+ inkscape:box3dsidetype="5"
+ style="fill:#4d4d9f;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
+ id="path3241"
+ sodipodi:type="inkscape:box3dside" />
+ <path
+ d="M -175.62244,-283.56406 L -175.62244,-271.35917 L -159.53003,-271.16871 L -159.53003,-283.3736 L -175.62244,-283.56406 z"
+ inkscape:box3dsidetype="14"
+ style="fill:#d7d7ff;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
+ id="path3243"
+ sodipodi:type="inkscape:box3dside" />
+ <path
+ d="M -181.88338,-289.54945 L -175.62244,-283.56406 L -175.62244,-271.35917 L -181.88338,-277.34455 L -181.88338,-289.54945 z"
+ inkscape:box3dsidetype="3"
+ style="fill:#8686bf;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
+ id="path3245"
+ sodipodi:type="inkscape:box3dside" />
+ </g>
+ </g>
+</svg>
diff --git a/openfovea/Icon/tomo_z_square.svg b/openfovea/Icon/tomo_z_square.svg
new file mode 100644
index 0000000..5cfbcd0
--- /dev/null
+++ b/openfovea/Icon/tomo_z_square.svg
@@ -0,0 +1,183 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="25.666435"
+ height="25"
+ id="svg2"
+ sodipodi:version="0.32"
+ inkscape:version="0.46"
+ version="1.0"
+ sodipodi:docname="tomo_z_square.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape">
+ <defs
+ id="defs4">
+ <inkscape:perspective
+ id="perspective2446"
+ inkscape:persp3d-origin="-364.09235 : -8.3537513 : 1"
+ inkscape:vp_z="9.2257984 : -0.10919314 : 0"
+ inkscape:vp_y="1.4514721e-15 : 23.705121 : 0"
+ inkscape:vp_x="-6.6873294 : 6.3930075 : 0"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ id="perspective2436"
+ inkscape:persp3d-origin="-364.09235 : -8.3537513 : 1"
+ inkscape:vp_z="9.2257984 : -0.10919314 : 0"
+ inkscape:vp_y="1.4514721e-15 : 23.705121 : 0"
+ inkscape:vp_x="-6.6873294 : 6.3930075 : 0"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ id="perspective3231"
+ inkscape:persp3d-origin="377.76153 : 585.0731 : 1"
+ inkscape:vp_z="389.1901 : -4.60631 : 0"
+ inkscape:vp_y="6.1230318e-14 : 1000 : 0"
+ inkscape:vp_x="-17.142856 : 646.18109 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="-6.6873294 : 6.3930075 : 0"
+ inkscape:vp_y="1.4514721e-15 : 23.705121 : 0"
+ inkscape:vp_z="9.2257984 : -0.10919314 : 0"
+ inkscape:persp3d-origin="-363.19949 : 10.217677 : 1"
+ id="perspective10" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="5.6"
+ inkscape:cx="-5.2881722"
+ inkscape:cy="-15.065071"
+ inkscape:document-units="px"
+ inkscape:current-layer="g2395"
+ showgrid="false"
+ inkscape:window-width="1286"
+ inkscape:window-height="816"
+ inkscape:window-x="0"
+ inkscape:window-y="180" />
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Calque 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(189.04492,-1064.7383)">
+ <g
+ id="g2395">
+ <g
+ inkscape:corner7="0.81015214 : -0.64656034 : 1.3234556 : 1"
+ inkscape:corner0="1.549884 : -0.23976184 : 0 : 1"
+ inkscape:perspectiveID="#perspective10"
+ id="g3169"
+ style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.34055018;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:0.34055018, 0.34055018;stroke-dashoffset:0;stroke-opacity:1"
+ sodipodi:type="inkscape:box3d">
+ <path
+ d="M -172.30922,1075.4403 L -167.36239,1080.1694 L -167.36239,1089.8126 L -172.30922,1085.0835 L -172.30922,1075.4403 z"
+ inkscape:box3dsidetype="11"
+ style="fill:#e9e9ff;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
+ id="path3181"
+ sodipodi:type="inkscape:box3dside" />
+ <path
+ d="M -184.51915,1075.2958 L -184.51915,1084.939 L -172.30922,1085.0835 L -172.30922,1075.4403 L -184.51915,1075.2958 z"
+ inkscape:box3dsidetype="6"
+ style="fill:#353564;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
+ id="path3171"
+ sodipodi:type="inkscape:box3dside" />
+ <path
+ d="M -184.51915,1084.939 L -179.57232,1089.6681 L -167.36239,1089.8126 L -172.30922,1085.0835 L -184.51915,1084.939 z"
+ inkscape:box3dsidetype="13"
+ style="fill:#afafde;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
+ id="path3179"
+ sodipodi:type="inkscape:box3dside" />
+ <path
+ d="M -184.51915,1075.2958 L -179.57232,1080.0249 L -167.36239,1080.1694 L -172.30922,1075.4403 L -184.51915,1075.2958 z"
+ inkscape:box3dsidetype="5"
+ style="fill:#4d4d9f;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
+ id="path3173"
+ sodipodi:type="inkscape:box3dside" />
+ <path
+ d="M -179.57232,1080.0249 L -179.57232,1089.6681 L -167.36239,1089.8126 L -167.36239,1080.1694 L -179.57232,1080.0249 z"
+ inkscape:box3dsidetype="14"
+ style="fill:#d7d7ff;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
+ id="path3177"
+ sodipodi:type="inkscape:box3dside" />
+ <path
+ d="M -184.51915,1075.2958 L -179.57232,1080.0249 L -179.57232,1089.6681 L -184.51915,1084.939 L -184.51915,1075.2958 z"
+ inkscape:box3dsidetype="3"
+ style="fill:#8686bf;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
+ id="path3175"
+ sodipodi:type="inkscape:box3dside" />
+ </g>
+ <rect
+ transform="matrix(1,0,0.7160415,0.6980577,0,0)"
+ y="1537.6874"
+ x="-1290.0929"
+ height="10.329248"
+ width="18.270266"
+ id="rect3278"
+ style="fill:#474747;fill-opacity:0.58895706;stroke:#000000;stroke-width:0.00613041;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:0.00613041, 0.00613041;stroke-dashoffset:0;stroke-opacity:1" />
+ <g
+ sodipodi:type="inkscape:box3d"
+ style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.34055018;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:0.34055018, 0.34055018;stroke-dashoffset:0;stroke-opacity:1"
+ id="g3233"
+ inkscape:perspectiveID="#perspective10"
+ inkscape:corner0="1.549884 : 0.2024704 : 0 : 1"
+ inkscape:corner7="0.81015214 : -0.2043281 : 1.3781759 : 1">
+ <path
+ sodipodi:type="inkscape:box3dside"
+ id="path3239"
+ style="fill:#e9e9ff;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
+ inkscape:box3dsidetype="11"
+ d="M -171.80438,1064.9631 L -166.85755,1069.6922 L -166.85755,1079.3354 L -171.80438,1074.6063 L -171.80438,1064.9631 z" />
+ <path
+ sodipodi:type="inkscape:box3dside"
+ id="path3237"
+ style="fill:#353564;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
+ inkscape:box3dsidetype="6"
+ d="M -184.51915,1064.8126 L -184.51915,1074.4558 L -171.80438,1074.6063 L -171.80438,1064.9631 L -184.51915,1064.8126 z" />
+ <path
+ sodipodi:type="inkscape:box3dside"
+ id="path3235"
+ style="fill:#afafde;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
+ inkscape:box3dsidetype="13"
+ d="M -184.51915,1074.4558 L -179.57232,1079.1849 L -166.85755,1079.3354 L -171.80438,1074.6063 L -184.51915,1074.4558 z" />
+ <path
+ sodipodi:type="inkscape:box3dside"
+ id="path3241"
+ style="fill:#4d4d9f;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
+ inkscape:box3dsidetype="5"
+ d="M -184.51915,1064.8126 L -179.57232,1069.5417 L -166.85755,1069.6922 L -171.80438,1064.9631 L -184.51915,1064.8126 z" />
+ <path
+ sodipodi:type="inkscape:box3dside"
+ id="path3243"
+ style="fill:#d7d7ff;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
+ inkscape:box3dsidetype="14"
+ d="M -179.57232,1069.5417 L -179.57232,1079.1849 L -166.85755,1079.3354 L -166.85755,1069.6922 L -179.57232,1069.5417 z" />
+ <path
+ sodipodi:type="inkscape:box3dside"
+ id="path3245"
+ style="fill:#8686bf;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
+ inkscape:box3dsidetype="3"
+ d="M -184.51915,1064.8126 L -179.57232,1069.5417 L -179.57232,1079.1849 L -184.51915,1074.4558 L -184.51915,1064.8126 z" />
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/openfovea/Icon/two_evt.ico b/openfovea/Icon/two_evt.ico
new file mode 100644
index 0000000..126be2d
Binary files /dev/null and b/openfovea/Icon/two_evt.ico differ
diff --git a/openfovea/Icon/two_evt.png b/openfovea/Icon/two_evt.png
new file mode 100644
index 0000000..89cea4b
Binary files /dev/null and b/openfovea/Icon/two_evt.png differ
diff --git a/openfovea/Icon/two_evt.svg b/openfovea/Icon/two_evt.svg
new file mode 100644
index 0000000..ead8448
--- /dev/null
+++ b/openfovea/Icon/two_evt.svg
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="24"
+ height="24"
+ id="svg4192"
+ version="1.1"
+ inkscape:version="0.47 r22583"
+ sodipodi:docname="Nouveau document 7">
+ <defs
+ id="defs4194">
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 526.18109 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="744.09448 : 526.18109 : 1"
+ inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+ id="perspective4200" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="18.666667"
+ inkscape:cx="1.3392857"
+ inkscape:cy="12"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:window-width="1115"
+ inkscape:window-height="703"
+ inkscape:window-x="656"
+ inkscape:window-y="313"
+ inkscape:window-maximized="0" />
+ <metadata
+ id="metadata4197">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Calque 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-1028.3622)">
+ <path
+ style="fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
+ d="M 0.80357143,3.1071429 2.25,3.1607143 7.6071429,22.232143 7.125,3.2678571 l 5.196429,0 L 17.892857,21.75 17.839286,3.4285714 l 5.25,0.053571"
+ id="path4202"
+ transform="translate(0,1028.3622)" />
+ </g>
+</svg>
diff --git a/openfovea/__init__.py b/openfovea/__init__.py
new file mode 100644
index 0000000..01678e9
--- /dev/null
+++ b/openfovea/__init__.py
@@ -0,0 +1 @@
+__all__ = ['curve', 'file']
\ No newline at end of file
diff --git a/openfovea/classes.py b/openfovea/classes.py
new file mode 100644
index 0000000..2c9649d
--- /dev/null
+++ b/openfovea/classes.py
@@ -0,0 +1,2738 @@
+#! /usr/bin/python
+# -*- coding: utf-8 -*-
+
+'''
+This module contains the classes that are usefull for OpenFovea. Currently,
+there is only the class ForceVolume, but we will soon implement an experiment
+class
+'''
+
+import os
+import csv
+from datetime import datetime
+# import pdb
+
+import numpy as num
+import math
+
+from fovea_toolbox import curve
+from fovea_toolbox.file_util import afm_file, aex
+from fovea_toolbox.post_proc import rel_values, rand_dot, \
+ add_stiffness_to_event, resize_array
+from fovea_toolbox import post_proc, misc
+from plot_generic import PlotData
+import plot_generic as plot
+
+__author__ = "Charles Roduit <charles.roduit at gmail.com>"
+__date__ = "25 juin 2005"
+__version__ = "0.1.alpha.1"
+__credits__ = "Rewrite of the 'Succellus' software developed during my thesis\
+ at the EPFL under the direction of Sandor Kasas."
+
+
+class ForceVolume(object):
+ """
+ This class contains all attributes and methods used to post-process force
+ curve. The needed input is the name of the file to open.
+
+ Attributes :
+
+ * name: the original file name
+ * indent: indentation vector of the force indentation curve
+ * force: force vector of the force indentation curve
+ * curve_nb: indice of the current curve
+ * pox_x: x coordinate of the current curve
+ * pos_y: y coordinate of the current curve
+ * stiffness: dictionnary that contains the parameters to compute the
+ stiffness
+ - hertz_model: the hertz model used
+ - poisson_ratio: Poisson ratio of the material.
+ - point_carac: caracteristic of the tip (radius or semi-opening
+ angle)
+ - nb_parts: the number of segment to compute
+ - size_parts: the size of each segments
+ - glass: the slope of the "non indenting part" of the curve
+ - claibSens: ???
+ - relative_max_dist: the maximum distance to compute relative
+ stiffness
+ - poc_threshold: The threshold to detect the point of contact
+ - poc_method: Stores the method to find the point of contact. (see
+ curve.find_poc for the available methods.)
+ - limit_slide: Wether to limit or not the sliding of the slice when
+ finding point of contact.
+ - recompute_poc: Wether to recompute poc or not.
+ - poc_len_slice: Length of the fit curve portion to find point of
+ contact
+ * array: dictionnary that contains the arrays computed on the scan.
+ - 'Piezo': The height of the piezo at the end of the indentation.
+ - 'PoCIndice': The indice of the point of contact.
+ - 'Topography': The zero force image.
+ - 'Stiffness': The computed stiffness.
+ - 'Stiffness tomo': Same as "Stiffness", but in an array corrected
+ with the zero force image.
+ - 'Event': The number of events.
+ - 'Event force': The force of events (max if several).
+ - 'Event random': The position of random selected pixels.
+ - 'Relative pos': ??
+ - 'Relative rand pos': ??
+ - 'Path': This defines a path to display info.
+ - 'Masked': True | False (default: False) To apply or not a mask to
+ the array.
+ * event: dictionnary that possess information on the computation and
+ display of the events.
+ - nbr_random: the number of random event that has to be generated.
+ - rel_stiff and rel_stiff_ctl: the result of the relative stiffness.
+ * header: TO COMPLETE
+ - ev_dist_thresh: the distance from which we have to display the
+ events.
+ * plot_who: curve to plot
+ * exist: to check the existence of computed things
+ * array: the 2D arrays computed
+ * header: the header of the file
+ * deflection_av
+ * deflection_re: advance and retraction curves
+ * plotx: the scanner extention part for all the deflection curves
+ * event_list: list of all events
+ """
+ # TODO: change self.event_list to self.event['list']
+ def __init__(self, fv_file):
+ self.indent = [] # The indentation and force curve of the
+ self.force = [] # trace curve.
+ self.r_indent = [] # The indentation and force curve of the
+ self.r_force = [] # retrace curve.
+ self.curve_nb = 0
+ self.pos_x = 0
+ self.pos_y = 0
+ self.counter = {'num': 0,
+ 'increment': 0,
+ 'text': 'Please wait...'}
+ self.stiffness = {'hertz_model': 'Sphere', # Hertz model used.
+ 'fit_method': 'Raw', # The fit method used.
+ 'poisson_ratio': 0.3, # Poisson ratio of the material.
+ 'point_carac': 40, # Caracteristic of the tip (depend
+ # on the Hertz model).
+ 'nb_parts': 4, # Number of FI segment.
+ 'size_parts': 50, # The size of each FI segment.
+ 'glass': -1., # Correction factor for the sensitivity
+ # calibration.
+ 'calib_sens': 1, # ?? Dead code ??
+ 'relative_max_dist': 3, # When computing the relative
+ # stiffness, what is the maximum distance to compute.
+ 'poc_threshold': 2, # The dependency on noise.
+ 'poc_method': 'curve_fit', # Stores the method to find
+ # the point of contact. (see curve.find_poc for the
+ # available methods.)
+ 'limit_slide': False, # Wether to limit or not the
+ # sliding of the slice when finding point of contact.
+ 'recompute_poc': True, # Wether to recompute poc or
+ # not.
+ 'poc_len_slice': 0.3, # Length of the fit curve
+ # portion to find point of contact
+ }
+ self.event = {'nbr_random': 15,
+ 'rel_stiff': None,
+ 'rel_stiff_ctl': None}
+ self.event_list = []
+ self.event_stat = {'force': None,
+ 'lr': None,
+ 'dist': None,
+ 'stiff': None,
+ 'rel_stiff': None}
+ self.event_list_original = None
+ self.plot_who = {'Av': 1, 'Re': 0, 'Indent': 0,
+ 'PoC': 0, 'Event': 0}
+ self.exist = {'PoC': 0, 'Stiffness': 0}
+ ######################
+ # Define the arrays :
+ self.__array = dict()
+ for item in ARRAY_TYPE:
+ self.__array[item] = None
+ # special case :
+ #self.__array['Masked'] = False
+ #######################
+ self.__fdcurves = {
+ 'trace_x': None,
+ 'trace_y': None,
+ 'retrace_x': None,
+ 'retrace_y': None,
+ }
+ if type(fv_file) in [str, unicode]:
+ #if type(fv_file) == str:
+ self.name = os.path.split(fv_file)[1]
+ _data = afm_file.load(fv_file)
+ self.header = _data['header']
+ self.__array['Piezo'] = _data['piezo']
+ if self.header['data_location'] == 'local':
+ # we are in the case where the data are loaded at once
+ self.__fdcurves['trace_x'] = _data['trace_x']
+ self.__fdcurves['retrace_x'] = _data['retrace_x']
+ self.__fdcurves['trace_y'] = _data['trace_y']
+ self.__fdcurves['retrace_y'] = _data['retrace_y']
+ else:
+ self.__data_fid = _data['data_fid']
+ if self.header['Microscope'] == 'csem':
+ self.stiffness['glass'] = -0.01
+ elif type(fv_file) == dict:
+ # we are in the presence of a xml tree readed from aex file
+ self.name = fv_file['name']
+ self.header = fv_file['header']
+ self.__fdcurves['trace_y'] = fv_file['trace_array']
+ self.__fdcurves['retrace_y'] = fv_file['retrace_array']
+ self.__fdcurves['trace_x'] = fv_file['trace_x_array']
+ self.__fdcurves['retrace_x'] = fv_file['retrace_x_array']
+ self.__array['Piezo'] = fv_file['piezo_array']
+ if self.header['data_location'] not in ['local', u'local']:
+ self.__data_fid = fv_file['data_fid']
+ if len(fv_file['stiffness_header']):
+ self.stiffness = fv_file['stiffness_header']
+ # Load stiffness results
+ if fv_file['stiffness'] is not None:
+ # recover attributes...
+ self.__array['Stiffness'] = fv_file['stiffness']
+ # Load events results
+ if 'event' in fv_file and 'array' in fv_file['event']:
+ self.__array['Event'] = fv_file['event']['array']
+ self.event_list = fv_file['event']['detail']
+ if 'event_rand_array' in fv_file:
+ self.__array['Event random'] = fv_file['event_rand_array']
+ if 'poc_array' in fv_file and fv_file['poc_array'] is not None:
+ self.__array['PoCIndice'] = fv_file['poc_array']
+ self.exist['PoC'] = 1
+ if fv_file['pocnoise_array'] is not None:
+ self.__array['PoCNoise'] = fv_file['pocnoise_array']
+ if 'topo_array' in fv_file:
+ self.__array['Topography'] = fv_file['topo_array']
+ if 'mask_array' in fv_file:
+ self.__array['Mask'] = fv_file['mask_array']
+ self.set_switch('mask', eval(fv_file['switch']['mask']))
+ if self.header['pixel_size'] is None:
+ try:
+ self.header['pixel_size'] = \
+ tuple([float(size) / nb_pix
+ for size, nb_pix in zip(self.header['scan_size'],
+ self.header['size'])])
+ except (ZeroDivisionError, TypeError):
+ self.header['pixel_size'] = (None, None)
+
+ def __iter__(self):
+ self.curve_nb = -1
+ return self
+
+ def __cmp__(self, other):
+ if isinstance(other, ForceVolume):
+ return cmp(self.header['date'], other.header['date'])
+
+ def __getattribute__(self, name, **toto):
+ """
+ Control the attribute given form the object.
+ """
+ if name == 'event_list':
+ event_list = object.__getattribute__(self, name)
+ if self.get_switch('mask'):
+ # If data are masked, get only the events that are on the mask.
+ event_list = misc.remove_from_mask(event_list,
+ self.__array['Mask'])
+ if self.get_switch('ev_dist'):
+ # If there is an threshold on the event distance.
+ event_list = misc.remove_from_threshold(
+ event_list,
+ 'dist',
+ self.get_switch('ev_dist'))
+ if self.get_switch('ev_fit_length'):
+ event_list = misc.remove_from_threshold(
+ event_list,
+ 'fit_length',
+ self.get_switch('ev_fit_length'))
+ if self.get_switch('ev_fit_plength'):
+ event_list = misc.remove_from_threshold(
+ event_list,
+ 'fit_plength',
+ self.get_switch('ev_fit_plength'))
+ return event_list
+ elif name == 'has_stiffness':
+ if self.get_array('Stiffness') is not None:
+ return True
+ else:
+ return False
+ elif name == 'spring_constant':
+ if type(self.header['spring_constant']) is list:
+ return self.header['spring_constant'][self.pos_x]
+ else:
+ return self.header['spring_constant']
+ elif name == 'trace_x':
+ if self.header['data_location'] == 'local':
+ if self.__fdcurves['trace_x'].ndim == 1:
+ return self.__fdcurves['trace_x']
+ else:
+ return self.__fdcurves['trace_x'][
+ self.pos_x, self.pos_y, :]
+ else:
+ # the data are loaded on the fly.
+ return afm_file.load_curve(self.__data_fid,
+ pos=[self.pos_x, self.pos_y],
+ dtype='trace_x',
+ header=self.header)
+ elif name == 'trace_y':
+ if self.header['data_location'] == 'local':
+ return self.__fdcurves['trace_y'][self.pos_x, self.pos_y, :]
+ else:
+ # the data are loaded on the fly.
+ return afm_file.load_curve(self.__data_fid,
+ pos=[self.pos_x, self.pos_y],
+ dtype='trace_y',
+ header=self.header)
+ elif name == 'retrace_x':
+ if self.header['data_location'] == 'local':
+ if self.__fdcurves['retrace_x'].ndim == 1:
+ return self.__fdcurves['retrace_x']
+ else:
+ return self.__fdcurves['retrace_x'][
+ self.pos_x, self.pos_y, :]
+ else:
+ # the data are loaded on the fly.
+ return afm_file.load_curve(self.__data_fid,
+ pos=[self.pos_x, self.pos_y],
+ dtype='retrace_x',
+ header=self.header)
+ elif name == 'retrace_y':
+ if self.header['data_location'] == 'local':
+ return self.__fdcurves['retrace_y'][self.pos_x, self.pos_y, :]
+ else:
+ # the data are loaded on the fly.
+ return afm_file.load_curve(self.__data_fid,
+ pos=[self.pos_x, self.pos_y],
+ dtype='retrace_y',
+ header=self.header)
+ else:
+ return object.__getattribute__(self, name)
+
+ def next(self):
+ '''
+ Go to the next pixel
+ '''
+ self.go_next()
+ return [self.pos_x, self.pos_y]
+
+ def current_curve(self, what, parameters=None):
+ if what == 'Event':
+ if parameters is not None:
+ event_detect_weight = parameters[0]
+ event_fit_model = parameters[1]
+ else:
+ event_detect_weight = self.header['event_detect_weight']
+ event_fit_model = self.header['event_fit_model']
+ # event_fit_from_poc = self.header['event_fit_from_poc']
+ if event_fit_model is not None:
+ # we compute the point of contact from the current curve
+ poc = self.current_curve('poc')
+ poc['Poly Fit'][1] += (self.retrace_y[0] - self.trace_y[0])
+ else:
+ poc = {'PoC': 0, 'Poly Fit': None}
+ return curve.event_find(self.retrace_x,
+ self.retrace_y,
+ self.stiffness['glass'],
+ self.spring_constant,
+ weight=event_detect_weight,
+ fit_model=event_fit_model,
+ baseline=poc['Poly Fit'],
+ poc=poc['PoC'])
+ elif what == 'poc':
+ poc = curve.find_poc(self.trace_x, self.trace_y,
+ threshold=self.stiffness['poc_threshold'],
+ method=self.stiffness['poc_method'],
+ limit_slide=self.stiffness['limit_slide'],
+ len_slice=self.stiffness['poc_len_slice'])
+ return poc
+
+ ##### Get stuffs from the object.
+ def get_parameter(self, which):
+ # TODO Put here all parameters that can be get. These parameters are
+ # not switches. Meaning that they have influences in the computation,
+ # but not in the display.
+ if which == 'poisson_ratio':
+ return self.stiffness['poisson_ratio']
+
+ def get_current_curve(self, direction='trace'):
+ """
+ Get the current trace curve.
+
+ * Parameters :
+
+ direction: str
+ 'trace' or 'retrace'
+
+ * Returns :
+
+ plotx: numpy.array
+ The x part of the deflection curve
+ deflection: numpy.array
+ The deflection part of the deflection curve
+ """
+ if direction == 'trace':
+ return [self.trace_x, self.trace_y]
+ elif direction == 'retrace':
+ return [self.retrace_x, self.retrace_y]
+
+ def get_switch(self, stype):
+ """
+ Get the value of the switch
+ """
+ if stype == 'list':
+ return ['mask', 'ev_dist']
+ elif stype == 'mask':
+ return self.header['Masked']
+ elif stype == 'ev_dist':
+ return self.header['event_dist_thresh']
+ elif stype == 'ev_thresh':
+ # This is a generic switch for all event thresholds.
+ return self.header['event_dist_thresh']
+ elif stype == 'ev_fit_length':
+ return self.header['thresh_event_fit_length']
+ elif stype == 'ev_fit_plength':
+ return self.header['thresh_event_fit_plength']
+ elif stype == 'restore':
+ return [self.get_switch('mask'), self.get_switch('ev_dist')]
+
+ def set_switch(self, stype, state=None):
+ """
+ Set the value of the switch.
+ """
+ if stype == 'mask':
+ if state == True and (self.__array['Mask'] is not None):
+ # Control if we have a mask
+ self.header['Masked'] = state
+ else:
+ self.header['Masked'] = False
+ elif stype == 'ev_dist':
+ if type(state) in [float, int]:
+ self.header['event_dist_thresh'] = float(state)
+ else:
+ raise AttributeError('Expecting numerical value.')
+ elif stype == 'ev_fit_length':
+ if type(state) == list and len(state) == 2:
+ if state[0] == 0:
+ state[0] = None
+ if state[1] == 0:
+ state[1] = None
+ self.header['thresh_event_fit_length'] = state
+ else:
+ raise AttributeError('Expecting list of length 2.')
+ elif stype == 'reset':
+ self.set_switch('mask', None)
+ self.set_switch('ev_dist', 0.0)
+ elif stype == 'restore':
+ self.set_switch('mask', state[0])
+ self.set_switch('ev_dist', state[1])
+
+ def get_array(self, atype, raw=False):
+ """
+ Get array from the forcevolume object.
+ """
+ if atype == 'keys':
+ # Return the valid keys for array.
+ return ARRAY_TYPE.keys()
+ if raw:
+ return self.__array[atype]
+ if ARRAY_TYPE[atype] == 'event':
+ #if atype in ['Event', 'Event force', 'Event length'] :
+ # A threshold is applyed on events.
+ ev_lst = self.event_list
+ #ev_lst = object.__getattribute__(self, 'event_list')
+ #ev_lst = misc.remove_from_threshold(ev_lst, 'dist',
+ # self.get_switch('ev_dist'))
+
+ # Get the name in the event dictionnary that correspond to the one
+ # in array.
+ if atype == 'Event':
+ _gen_type = 'count'
+ elif atype == 'Event force':
+ _gen_type = 'force'
+ elif atype == 'Event length':
+ _gen_type = 'fit_length'
+ elif atype == 'Event distance':
+ _gen_type = 'dist'
+ array = misc.generate_array(ev_lst, _gen_type, self.header['size'])
+ elif atype == 'Piezo':
+ array = self.__array[atype]
+ if array is None: #num.array_equal(array, num.zeros(array.shape)):
+ raise NotReadyError('Topography array is not loaded')
+ else:
+ array = self.__array[atype]
+ if array is None:
+ return array
+ if self.get_switch('mask'):
+ # Return a masked array corresponding to the generated mask.
+ mask = self.__array['Mask'].copy()
+ mask = mask.astype(bool)
+ if type(array) == num.ma.core.MaskedArray:
+ if array.ndim == 3:
+ mask = num.array([mask for i in range(array.shape[2])])
+ mask = mask.transpose(1, 2, 0)
+ mask = ((array.mask - 1) * (mask - 1)) - 1
+ array = array.data
+ array = num.ma.masked_array(array, mask=mask)
+ return array
+
+ def force_piezo_loading(self):
+ """
+ Force the loading of the piezo array. Used in the case of AFM files
+ with no piezo arrays saved. The method is to get the coordinate of
+ the end of indentation of each curves. As this can take time, we do
+ not this automatically, but only if the user think its important.
+ """
+ arrays = afm_file.force_load_array(self.__data_fid,
+ header=self.header)
+ self.__array['Piezo'] = arrays['Piezo']
+
+ def set_array(self, atype, array):
+ """
+ Set the array with the new value.
+ """
+ if atype in self.__array.keys():
+ self.__array[atype] = array
+
+ def get_current_indent_curve(self, direction='trace'):
+ """
+ Get the current indentation curve.
+
+ * Parameters :
+ direction: str
+ 'trace' or 'retrace'
+
+ * Returns :
+ indent: numpy.array
+ The indentation part of the indentation curve
+ force: numpy.array
+ The force part of the indentation curve
+ """
+ if direction == 'trace':
+ self.compute_indentation()
+ return [self.indent, self.force]
+ elif direction == 'retrace':
+ self.compute_indentation('retrace')
+ return [self.r_indent, self.r_force]
+
+ def get_poc_display(self):
+ """
+ Get all parmeters to display the point of contact.
+
+ [poc, plotx, ploty, error, slice, deriv] = fv.get_poc_display()
+
+ * Parameters :
+ None
+
+ * Returns :
+ PoC: int
+ The index of the point of contact.
+ plotx: numpy.array
+ The x part of the curve.
+ ploty: numpy.array
+ The y part of the curve.
+ error: float
+ The standard error detected when finding the poc.
+ slice: slice
+ The curve part that was used for the fit.
+ deriv: ??
+ Have to find what's this...
+ """
+ ## build the curves...
+ _result = curve.find_poc(self.trace_x,
+ self.trace_y,
+ self.stiffness['poc_threshold'],
+ method=self.stiffness['poc_method'],
+ limit_slide=self.stiffness['limit_slide'],
+ len_slice=self.stiffness['poc_len_slice'])
+ vect_y = _result['Poly Fit'][0] * self.trace_x + _result['Poly Fit'][1]
+ return[_result['PoC'], self.trace_x, vect_y, _result['Error'],
+ _result['Slice'], _result['deriv']]
+
+ def get_segments_indent(self):
+ """
+ get the segment of the indentation curve that are used to compute
+ the stiffness or the stiffness tomography.
+ """
+ if not self.exist['PoC']:
+ _result = curve.find_poc(self.trace_x,
+ self.trace_y,
+ self.stiffness['poc_threshold'],
+ method=self.stiffness['poc_method'],
+ limit_slide=self.stiffness['limit_slide'],
+ len_slice=self.stiffness['poc_len_slice'])
+ poc_indice = _result['PoC']
+ else:
+ poc_indice = self.__array['PoCIndice'][self.pos_x, self.pos_y]
+ [x_parts, y_parts] = curve.segment_curve(self.indent[0:poc_indice],
+ self.force[0:poc_indice],
+ self.stiffness['nb_parts'],
+ self.stiffness['size_parts'])
+ return [x_parts, y_parts]
+
+ def get_path(self, atype, path):
+ """
+ Get the path from arrays.
+
+ * Parameters :
+ atype: str
+ This is the array type to slice. This has to be an item
+ from fv.get_array('keys')
+ If atype is "all", it will return all possible values,
+ with the corresponding label
+ pos_a: array
+ correspond to the first point of the path[x, y]
+ pos_b: array
+ correspond to the last point of the path [x,y]
+
+ * Returns :
+ x_slice: 1D numpy.array
+ The x value of the path
+ y_slice: 1D numpy.array or list of numpy.array
+ * If atype is an array type, this is the y value of the
+ path
+ * If atype is "all" this is a list of y value of the path
+ label: str
+ If atype is "all", this is a list of label that desing each
+ y_slices.
+ Else, this is the atype as a string
+
+ >>> fv = ForceVolume(BIOSCOPE_FILE)
+ >>> #piezo_slice = fv.get_path('Piezo', [0, 14], [15, 14])
+ """
+ array_list = ['Piezo', 'PoCIndice', 'Topography']
+
+ if atype == 'all':
+ atype = array_list
+ elif atype in array_list:
+ atype = [atype]
+ else:
+ raise ValueError('Array type ' + atype + ' not known.')
+
+ self.__array['Path'] = path
+
+ y_slice = [
+ post_proc.generate_slice(self.__array['Path'],
+ self.__array[item])
+ for item in atype]
+ x_slice = list(num.arange(len(y_slice[0])) * self.header['pixel_size'])
+
+ return x_slice, y_slice, atype
+ #
+ #### Navigation dans le scan ####
+
+ def curve2xy(self):
+ '''
+ Conversion of the current curveNb into the x and y coordinates.
+ '''
+ if self.header['size'][1] - 1:
+ self.pos_x = (self.curve_nb) % (self.header['size'][0])
+ self.pos_y = (self.curve_nb) / (self.header['size'][0])
+ else:
+ self.pos_x = self.curve_nb
+ self.pos_y = 0
+
+ def curve2nb(self):
+ '''
+ Conversion of the current x and y coordinate into the curveNb.
+ '''
+ self.curve_nb = self.pos_x + \
+ self.pos_y * self.header['size'][0]
+
+ def go_next(self):
+ '''
+ Go to the next pixel.
+ '''
+ if self.curve_nb == self.header['number_curves'] - 1:
+ raise StopIteration
+ self.curve_nb = self.curve_nb + 1
+ self.curve2xy()
+
+ def go_prev(self):
+ '''
+ Go to the previous pixel.
+ '''
+ if self.curve_nb == 0:
+ return
+ self.curve_nb = self.curve_nb - 1
+ self.curve2xy()
+
+ def go_up(self):
+ '''
+ Go tho the upper pixel.
+ '''
+ if self.pos_y == self.header['size'][1] - 1:
+ return
+ self.pos_y = self.pos_y + 1
+ self.curve2nb()
+
+ def go_down(self):
+ '''
+ Go to the bellow pixel.
+ '''
+ if self.pos_y <= 0:
+ return
+ self.pos_y = self.pos_y - 1
+ self.curve2nb()
+
+ def go_first(self):
+ '''
+ Go the the first pixel.
+ '''
+ self.pos_y = 0
+ self.pos_x = 0
+ self.curve_nb = 0
+
+ def go_to(self, pos_x, pos_y):
+ '''
+ Go to a defined pixel
+ '''
+ self.pos_x = int(pos_x)
+ self.pos_y = int(pos_y)
+ self.curve2nb()
+
+ # TODO: All these change should be in a single set_param function
+ # Look if it's better or not...
+ # e.g. set_param('hertz_model', 'sphere', carac=None)
+ # set_param('segm_depth', 50, carac='nm')
+ # set_param('segm_nbr', 10, carac='begin')
+ # set_param('rel_dist', 4)
+ def set_parameters(self, param, value):
+ """
+ Change the caracteristic of a parameter.
+
+ param: str
+ Which parameter to change. Possible values are
+ * 'tip_carac' to change the tip caracteristic (i.e. the
+ semi-opening angle if the cone model is used, or the
+ radius of the sphere if the sphere model is used)
+ In this case, value is a float.
+
+ * 'evt_fit' to change the event fit model.
+ In this case, value is a string. 'wlc' and 'fjc' are
+ possible strings. For more detailed information, see
+ documentation of the event_fit function in
+ fovea_toolbox/curve documentation.
+
+ * 'stiff_method' to change the stiffness computation
+ method.
+ 'Raw' indicates the use of the raw data to fit with the
+ chosen model.
+ 'Extrema' uses the points at the extremities of the
+ segments in order to make a simple fit. This
+ method is faster, but less precise. This is
+ historically the first method introduced.
+
+ value: float or str (depending on the parameters.)
+ """
+
+ if param == 'tip_carac':
+ print "Changing carac of %s from %f to %f" % (
+ self.name,
+ self.stiffness['point_carac'],
+ value)
+ self.stiffness['point_carac'] = value
+ elif param == 'evt_fit':
+ self.header['event_fit_model'] = value
+ elif param == 'stiffness_fit_method':
+ if value in ['Raw', 'Linear', 'Extrema', 'Median']:
+ self.stiffness['fit_method'] = value
+ else:
+ raise AttributeError(
+ 'Method %s not recognized as a stiffness method') % value
+
+ def change_poc_seg_size(self, size):
+ """
+ Change the size of the curve portion used to find the point of
+ contact. The size is expressed as a fraction of the total force
+ curve.
+ """
+ if size == 'default':
+ self.stiffness['poc_len_slice'] = 0.3
+ elif 0 < size < 1:
+ self.stiffness['poc_len_slice'] = size
+ else:
+ raise ValueError('Value must be between 0 and 1.')
+
+ def change_model(self, model, carac=None):
+ '''
+ change_model changes the model used for the stiffness computation.
+
+ model: 'Sphere' or 'Cone'.
+
+ carac: sphere radius or cone semi opening angle.
+ '''
+ if model == 'Sphere':
+ if carac == None:
+ self.stiffness['point_carac'] = 40
+ else:
+ self.stiffness['point_carac'] = carac
+ self.stiffness['hertz_model'] = 'Sphere'
+ elif model == 'Cone':
+ if carac == None:
+ self.stiffness['point_carac'] = 20 * math.pi / 180
+ else:
+ self.stiffness['point_carac'] = carac
+ self.stiffness['hertz_model'] = 'Cone'
+
+ def change_segment_depth(self, value):
+ '''
+ Changes the depth (in nm) of the force curve segmentation used to
+ compute the stiffness
+ '''
+ try:
+ self.stiffness['size_parts'] = float(value)
+ except ValueError:
+ pass
+
+ def change_segment_number(self, value):
+ '''
+ Changes the number of segment used to compute the stiffness
+ '''
+ try:
+ self.stiffness['nb_parts'] = int(value)
+ except ValueError:
+ pass
+
+ def change_relative_distance(self, value):
+ '''
+ Changes the maximum relative stiffness distance to compute.
+ '''
+ if type(value) == int or (int(value) - value == 0):
+ self.stiffness['relative_max_dist'] = int(value)
+
+ def change_poisson_ratio(self, value):
+ """
+ Changes the poisson ratio of the material.
+
+ * Parameters :
+ value: float
+ The new poisson ratio.
+ """
+ if type(value) == float:
+ self.stiffness['poisson_ratio'] = value
+ else:
+ raise TypeError('Float value expected.')
+
+ def compute_poc(self):
+ '''
+ Computes the point of contact between the tip and the sample in the
+ whole array.
+ '''
+ self.counter['text'] = "Computing point of contact"
+ self.__array['Topography'] = num.empty(self.header['size'])
+ self.__array['PoCIndice'] = num.empty(self.header['size'])
+ self.__array['PoCNoise'] = num.empty(self.header['size'])
+
+ if self.__array['Piezo'] is None:
+ regenerate_piezo = True
+ self.__array['Piezo'] = num.empty(self.header['size'])
+ else:
+ regenerate_piezo = False
+
+ for pixels in self:
+ trace_x, trace_y = self.trace_x, self.trace_y
+ if regenerate_piezo:
+ self.__array['Piezo'][self.pos_x, self.pos_y] = trace_x[-1]
+ _result = curve.find_poc(trace_x, trace_y,
+ threshold=self.stiffness['poc_threshold'],
+ method=self.stiffness['poc_method'],
+ limit_slide=self.stiffness['limit_slide'],
+ len_slice=self.stiffness['poc_len_slice'])
+ if _result is not None:
+ self.__array['PoCIndice'][
+ self.pos_x, self.pos_y] = _result['PoC']
+ self.__array['Topography'][self.pos_x, self.pos_y] = (
+ self.__array['Piezo'][self.pos_x, self.pos_y] +
+ trace_x[_result['PoC']])
+ self.__array['PoCNoise'][
+ self.pos_x, self.pos_y] = _result['Error']
+ else:
+ self.__array['PoCIndice'][self.pos_x, self.pos_y] = 0
+ self.__array['Topography'][self.pos_x, self.pos_y] = 0
+ self.__array['PoCNoise'][self.pos_x, self.pos_y] = 0
+ self.counter['num'] += self.counter['increment']
+ self.go_first()
+ self.exist['PoC'] = 1
+
+ def compute_indentation(self, direction='trace'):
+ '''
+ Computes the indentation curves on the current position
+ '''
+ if direction == 'trace':
+ [self.indent, self.force] = curve.compute_indentation(
+ self.trace_x, self.trace_y,
+ self.stiffness['glass'],
+ self.spring_constant)
+ elif direction == 'retrace':
+ [self.r_indent, self.r_force] = curve.compute_indentation(
+ self.retrace_x, self.retrace_y,
+ self.stiffness['glass'],
+ self.spring_constant)
+
+ def compute_stiffness(self):
+ '''
+ Compute the stiffness array on the scan
+ '''
+ ## Defines the variables
+ ##
+
+ self.counter['num'] = 0
+ self.counter['text'] = "Creating matrices"
+ if self.stiffness['nb_parts'] < 0:
+ # case where we compute stiffness on whole curve instead of segments.
+ __size_array = 1
+ else:
+ __size_array = self.stiffness['nb_parts']
+ self.__array['Stiffness'] = num.zeros([
+ self.header['size'][0],
+ self.header['size'][1],
+ __size_array
+ ], num.float)
+ _number_of_curves = self.header['size'][0] * \
+ self.header['size'][1] * 2 + 1
+ self.counter['increment'] = 1 / float(_number_of_curves - 1)
+
+ if (not self.exist['PoC']) or self.stiffness['recompute_poc']:
+ self.compute_poc()
+
+ self.counter['text'] = "Computing stiffness"
+ ## Computation begins by navigation in the array
+ for pixel in self:
+ self.compute_indentation()
+ if self.indent is None:
+ # There is no data here. We increment the counter and go to the
+ # next pixel.
+ self.counter['num'] += self.counter['increment']
+ continue
+ poc_indice = self.__array['PoCIndice'][self.pos_x, self.pos_y]
+ [x_parts, y_parts] = curve.segment_curve(
+ self.indent[0:poc_indice],
+ self.force[0:poc_indice],
+ self.stiffness['nb_parts'],
+ self.stiffness['size_parts'],
+ )
+ tmp = curve.compute_stiffness(
+ x_parts,
+ y_parts,
+ model=self.stiffness['hertz_model'],
+ tip_carac=self.stiffness['point_carac'],
+ poisson_ratio=self.stiffness['poisson_ratio'],
+ method=self.stiffness['fit_method'])
+ ## We complement the array in the case the newly computed stiffness
+ ## contains more depth. That can happend when performing stiffness
+ ## tomography.
+ if self.__array['Stiffness'].shape[2] < len(tmp):
+ array_shape = self.__array['Stiffness'].shape
+ _to_fill = num.empty((array_shape[0], array_shape[1],
+ len(tmp) - array_shape[2]))
+ _to_fill[:, :, :] = num.nan
+ self.__array['Stiffness'] = num.append(
+ self.__array['Stiffness'],
+ _to_fill, 2)
+ elif self.__array['Stiffness'].shape[2] > len(tmp):
+ array_shape = self.__array['Stiffness'].shape
+ _to_fill = num.empty((array_shape[2] - tmp.shape[0]))
+ _to_fill[:] = num.nan
+ tmp = num.append(tmp, _to_fill, 0)
+ ## Curves are in nm and nN. The result in tmp is then in nN/nm^2
+ ## To convert the result in Pa, we multiply by 1e9
+ self.__array['Stiffness'][self.pos_x, self.pos_y, ] = tmp * 1e9
+ self.counter['num'] += self.counter['increment']
+ ##
+ ## Stores the result in a matrix that supports the mask, to get rid
+ ## of the nan values
+ ##
+ self.counter['text'] = 'Store results'
+ self.__array['Stiffness'] = num.ma.array(
+ self.__array['Stiffness'],
+ mask=num.isnan(self.__array['Stiffness']))
+ self.exist['Stiffness'] = 1
+ self.exist['PoC'] = 1
+ self.go_first()
+ self.counter['num'] = 1
+
+ def compute_events(self):
+ '''
+ Computes the event of all the curves in the object. The event_list and
+ event matrix (in array['Event']) are refreshed.
+ '''
+ # Set the switches to 0 in order to get all event
+ _init_mask = self.get_switch('mask')
+ _init_ev_dist = self.get_switch('ev_dist')
+ self.set_switch('mask', False)
+ self.set_switch('ev_dist', 0.0)
+ self.counter['num'] = 0
+ self.counter['text'] = "Detecting events"
+ _number_of_curves = self.header['size'][0] * \
+ self.header['size'][1] + 1
+ self.event_list = []
+ self.__array['Event'] = num.zeros((self.header['size'][0],
+ self.header['size'][0]))
+ self.__array['Event force'] = num.zeros((self.header['size'][0],
+ self.header['size'][0]))
+ for pixels in self:
+ det_event = self.current_curve('Event')
+ if det_event:
+ for event in det_event:
+ event['pos_x'] = self.pos_x
+ event['pos_y'] = self.pos_y
+ self.event_list.append(event)
+ self.__array['Event'][self.pos_x, self.pos_y] += 1
+ self.counter['num'] = float(self.curve_nb) / _number_of_curves
+ self.go_first()
+ # Generate the random matrix.
+ self.generate_random_event()
+ self.counter['num'] = 1
+
+ # Set back the original switches...
+ self.set_switch('mask', _init_mask)
+ self.set_switch('ev_dist', _init_ev_dist)
+
+ def generate_random_event(self, nbr_event=None):
+ if nbr_event is not None:
+ self.event['nbr_random'] = nbr_event
+ self.__array['Event random'] = rand_dot(self.__array['Event'],
+ self.event['nbr_random'])
+
+ def generate_mask(self, mtype, mdata, depth=0):
+ """
+ Generate a mask for the arrays.
+
+ * Parameters :
+ mtype: string
+ 'Piezo': generate mask based to the piezo image array.
+ 'Topography': generate mask based on the topography array.
+ 'Stiffness': generate mask based on the stiffness array.
+ 'Array': generate mask based on the mdata array.
+ mdata: float | 2D numpy.array
+ float if mtype is 'piezo', 'topography', 'stiffness'
+ 2D numpy.array if mtype is 'array'
+ depht: int
+ In the case the array is 3d. It generates the mask from the
+ specified depth, array[:,:,depth].
+ Default value: 0
+ """
+ if mtype == 'Array':
+ self.__array['Mask'] = mdata
+ else:
+ if self.__array[mtype].ndim == 3:
+ self.__array['Mask'] = self.__array[mtype][:, :, depth] < mdata
+ else:
+ self.__array['Mask'] = self.__array[mtype] < mdata
+ # Creating a mask automatically switch it on.
+ self.set_switch('mask', True)
+
+ def compute_relative_stiffness(self, exclude=0):
+ '''
+ Computes the relative stiffness of the events
+ '''
+ if self.__array['Event'] is None:
+ raise NotReadyError('Event is not yet computed.')
+ if self.__array['Stiffness'] is None:
+ raise NotReadyError('Stiffness is not yet computed.')
+ # event and stiffness have to be computed
+ # TODO: change self.event_list to self.event['list']
+ [self.event['rel_stiff'], coordinate] = rel_values(
+ self.__array['Stiffness'][:, :, 0],
+ self.__array['Event'],
+ self.stiffness['relative_max_dist'],
+ coord=True)
+ self.event['rel_stiff_ctl'] = rel_values(
+ self.__array['Stiffness'][:, :, 0],
+ self.__array['Event random'],
+ self.stiffness['relative_max_dist'])
+ # Is it really necessary ??
+ self.event_list = add_stiffness_to_event(self.event_list,
+ self.event['rel_stiff'],
+ coordinate)
+
+ def compute_average_stiffness(self):
+ """
+ Computes the average stiffness in function of the depth.
+
+ returns a dictionary with the deep, mean, std and count.
+
+ >>> fv = ForceVolume(BIOSCOPE_FILE)
+ >>> fv.compute_stiffness()
+ >>> result = fv.compute_average_stiffness()
+ >>> result.keys()
+ ['std', 'count', 'deep', 'mean']
+ """
+ if self.__array['Stiffness'] is None:
+ start = 0
+ data_count = 0
+ else:
+ step = self.stiffness['size_parts']
+ start = (self.__array['Stiffness'].shape[2] - 1) * step
+ #raise NotReadyError, 'Stiffness is not yet computed'
+ #start = (self.__array['Stiffness'].shape[2] -1) * step
+ deep = range(start, -step, -step)
+ stiffness = self.get_array('Stiffness')
+ data_count = stiffness.count()
+ if data_count:
+ _result = [[deep.pop(), i.mean(), i.std(), i.count()] for i in
+ self.get_array('Stiffness').transpose(2, 0, 1)]
+ else:
+ _result = [[0, num.nan, num.nan, 0]]
+ _result = num.ma.asarray(_result)
+ return dict([(key, value) for key, value in
+ zip(['deep', 'mean', 'std', 'count'],
+ _result.transpose(1, 0))])
+
+ def event_force_list(self, dtype='rel'):
+ '''
+ Returns the list of the event force
+ '''
+ force_list = []
+ if dtype == 'rel':
+ prop = 'force'
+ elif dtype == 'baseline':
+ prop = 'force_base'
+ else:
+ raise TypeError('dtype ' + prop + ' is not supported.')
+ for event in self.event_list:
+ force_list.append(event[prop])
+ return force_list
+
+ def number_event(self):
+ return len(self.event_list)
+
+ def event_lr_list(self):
+ '''
+ Returns the list of the loading rates
+ '''
+ lr_list = []
+ for event in self.event_list:
+ lr_list.append(event['loading_rate'])
+ return lr_list
+
+ def event_dist_list(self):
+ '''
+ Returns the list of the distance of the events
+ '''
+ dist_list = []
+ for event in self.event_list:
+ dist_list.append(event['dist'])
+ return dist_list
+
+ def event_stiff_list(self):
+ '''
+ Returns the list of the stiffness of the events
+ '''
+ stiff_list = []
+ if self.__array['Stiffness'] is None:
+ return [num.nan for i in range(self.number_event())]
+ for event in self.event_list:
+ if not self.__array['Stiffness'].mask[event['pos_x'],
+ event['pos_y'], 0]:
+ stiff_list.append(
+ self.__array['Stiffness'].data[event['pos_x'],
+ event['pos_y'],
+ 0])
+ else:
+ stiff_list.append(num.nan)
+ return stiff_list
+
+ def event_rel_stiff_list(self, dist=1):
+ '''
+ Returns the list of the relative stiffness of the events
+ '''
+ stiff_list = []
+ if self.event_list is None:
+ return stiff_list
+ elif not 'Stiffness' in self.event_list[0]:
+ return [num.nan for i in range(self.number_event())]
+ for event in self.event_list:
+ if event['Stiffness'].size and not event['Stiffness'].mask[dist]:
+ stiff_list.append(event['Stiffness'].data[dist])
+ else:
+ stiff_list.append(num.nan)
+ return stiff_list
+
+ def event_rand_rel_stiff_list(self, dist=1):
+ '''
+ Returns the list of the relative stiffness of the events.
+ '''
+ _list = []
+ for item in self.event['rel_stiff_ctl'][dist]:
+ if isinstance(item, num.ma.core.MaskedArray):
+ _list.append(num.nan)
+ else:
+
+ _list.append(item)
+ return _list
+
+ def event_pos_list(self, dim=None):
+ """
+ Returns the position list of the events.
+ """
+ if dim in ['x', 'X']:
+ pos_list = [event['pos_x'] for event in self.event_list]
+ elif dim in ['y', 'Y']:
+ pos_list = [event['pos_y'] for event in self.event_list]
+ else:
+ pos_list = [self.event_pos_list('x'), self.event_pos_list('y')]
+ return pos_list
+
+ def event_nbr_per_curve(self):
+ """
+ Returns the list of number of event per curves.
+
+ >>> fv = ForceVolume(BIOSCOPE_FILE)
+ >>> fv.compute_events()
+ >>> nbr_event = fv.event_nbr_per_curve()
+ >>> print nbr_event
+ [79, 86, 55, 26, 10]
+
+ This means that 79 pixels (or force curves) have 0 events, 85 have
+ 1, 56 have 2 26 have 3 and 10 force curves contains 4 events.
+
+ * Parameters: None
+
+ * Returns :
+ nbr_events_list: list
+ The number of force curves that contains 0, 1, ... events.
+ """
+
+ _list = []
+ for nbr_ev in range(int(self.__array['Event'].max()) + 1):
+ with_current = num.nonzero(self.get_array('Event') == nbr_ev)
+ _list.append(len(with_current[0]))
+ return _list
+
+ def get_event_prop(self, data_type, dist=1):
+ """
+ Get the specified properties of the event.
+
+ * Parameters :
+ data_type: str
+ Describe the event property to get.
+ 'force': return the list of event forces.
+ 'force_base': return the list of event forces, computed
+ relative to the baseline.
+ 'lr': the loading rate.
+ 'dist': the distance from the end of the indentation.
+ 'stiff': the stiffness, computed from the corresponding
+ trace curve.
+ 'stiff_ev': same as stiff, but not ordered, and one per
+ pixel. If a pixel contain two event, it will be
+ reported once.
+ 'stiff_notev': list of stiffness on pixels with no event.
+ 'relstiff': the list of relative stiffness on event.
+ 'pos_x': the list of the x position in the array.
+ 'pos_y': the list of the y position in the array.
+ 'nbr_ev_per_curve': the list of the number of events per
+ force curves. From 0 event to the maximum detected.
+
+ 'all': A dictionnary of list with all the parameters
+ related to events. This will not return list from
+ "stiff_ev" and "stiff_notev" as they are different.
+ 'label': the label corresponding to "all". Usefull to have
+ an ordered label.
+ """
+ if data_type is 'force':
+ return self.event_force_list()
+ elif data_type is 'force_base':
+ return self.event_force_list('baseline')
+ elif data_type is 'lr':
+ return self.event_lr_list()
+ elif data_type is 'dist':
+ return self.event_dist_list()
+ elif data_type is 'stiff':
+ return self.event_stiff_list()
+ elif data_type is 'stiff_ev':
+ return self.stiff_list('event')
+ elif data_type is 'stiff_notev':
+ return self.stiff_list('not event')
+ elif data_type is 'relstiff':
+ return [self.event_rel_stiff_list(dist) \
+ for dist in range(self.stiffness['relative_max_dist'] + 1)]
+ elif data_type is 'nbr_ev_per_curve':
+ return self.event_nbr_per_curve()
+ elif data_type is 'length':
+ return [event['fit_length'][0] for event in self.event_list]
+ elif data_type is 'plength':
+ return [event['fit_plength'][0] for event in self.event_list]
+ elif data_type is 'label':
+ return ['pos_x', 'pos_y', 'force', 'lr',
+ 'dist', 'stiff', 'relstiff']
+ elif data_type is 'all':
+ return {'force': self.event_force_list(),
+ 'lr': self.event_lr_list(),
+ 'dist': self.event_dist_list(),
+ 'stiff': self.event_stiff_list(),
+ 'relstiff': self.event_rel_stiff_list(),
+ 'pos_x': self.event_pos_list('x'),
+ 'pos_y': self.event_pos_list('y')}
+
+ def stiff_list(self, dtype, depth=0):
+ """
+ Get the list of the stiffness at the given depth.
+
+ dtype = string
+ 'all'
+ to have stiffness of all pixels
+ 'event'
+ to have stiffness of event pixels
+ 'not event'
+ to have stiffness of pixels with no event
+ 'random'
+ to have stiffness of randomly selected no event pixels
+
+ The difference between event_stiff_list() and stiff_list('event')
+ is that for the first, there is the list of stiffness for each
+ events. Which means there can be two stiffness that belongs to the
+ same pixel. For the second (stiff_list('event')), the list does not
+ contain twice the same pixel value.
+
+ For example :
+
+ >>> fv = ForceVolume(BIOSCOPE_FILE)
+ >>> fv.compute_events()
+ >>> fv.compute_stiffness()
+
+ To get stiffness of pixels where no event were detected :
+
+ >>> stiffness_list = fv.stiff_list('not event')
+
+ To get stiffness of pixels with event :
+
+ >>> event_stiffness_list = fv.stiff_list('event')
+
+ To get stiffness of all pixels :
+
+ >>> all_stiffness_list = fv.stiff_list('all')
+
+ Just to verify that the sum of list with and without event give the
+ correct length :
+
+ >>> len(event_stiffness_list) + len(stiffness_list) == \
+ len(all_stiffness_list)
+ True
+
+ ... and so on.
+ """
+ array = self.get_array('Stiffness')
+
+ depth = int(depth)
+ #print('Line 1165 class.py Depth = %f, type')%depth
+ if array is None:
+ return []
+ else:
+ try:
+ array = array[:, :, depth]
+ except IndexError:
+ array = num.ma.array(num.zeros(array.shape[:2]),
+ mask=num.ones(array.shape[:2]))
+ array[:] = num.nan
+ if dtype == 'all':
+ cache = num.ones(array.shape)
+ elif dtype == 'event':
+ cache = self.get_array('Event') > 0
+ elif dtype == 'random':
+ cache = self.get_array('Event random') > 0
+ elif dtype == 'not event':
+ cache = self.get_array('Event') == 0
+ else:
+ raise TypeError(dtype + ' is not recognized.')
+ # Want's negative mask (initially, 1 is masked and 0 is visible)
+ mask = array.mask == False
+ # * is the same as 'and' but 'and' operator does not work
+ mask = cache * mask
+ pix_indice = num.nonzero(mask)
+ stiffness_list = [array[x, y] for x, y in zip(pix_indice[0],
+ pix_indice[1])]
+
+ return stiffness_list
+
+ def event_gauss_fit(self, data_type, min_val, max_val, dist=1):
+ '''
+ Automatically finds the best gaussian fit of the event properties.
+
+ data_type can be :
+
+ * 'force' to considere the event forces.
+ * 'lr' to considere the event loading rates.
+ * 'distance' to considere the event distance in the curve
+ * 'stiff' to considere the stiffness in the trace curve where event
+ was detected.
+ * 'relstiff' to considere the relative stiffness of the pixel where
+ event was detected.
+
+ Results are stored in event_stat[data_type] attribute.
+
+ For example :
+
+ >>> fv = ForceVolume(BIOSCOPE_FILE)
+ >>> fv.compute_events()
+ >>> fv.event_gauss_fit('force', min_val=0, max_val=1)
+ >>> fv.event_stat['force'].keys()
+ ['std', 'kde', 'maxima', 'count', 'mean']
+
+ * kde is the kernel density estimation. This is an array composed of
+ two vector, respectively the x and y coordinate.
+ * 'mean' is the list of detected gaussian mean
+ * 'std' is the corresponding standard deviation.
+ * 'count' is the corresponding number of elements for the mean and std.
+ * 'maxima' is the detected maxima from the kde.
+ '''
+
+ fit, mean, std, count = None, None, None, None
+ if data_type == 'force':
+ values = num.asanyarray(self.event_force_list())
+ elif data_type == 'lr':
+ values = num.asanyarray(self.event_lr_list())
+ elif data_type == 'distance':
+ values = num.asanyarray(self.event_dist_list())
+ elif data_type == 'stiff':
+ values = num.asanyarray(self.event_stiff_list())
+ elif data_type == 'relstiff':
+ values = num.asanyarray(self.event_rel_stiff_list(dist))
+ else:
+ raise StandardError(data_type + ' is not recognize.')
+ index = (min_val < values) * (values < max_val)
+ self.event_stat[data_type] = post_proc.find_gauss_fit(values[index])
+
+ def to_plot(self, curve_type):
+ '''
+ Returns the curves to plot
+ '''
+ if curve_type in self.plot_who:
+ return self.plot_who[curve_type]
+ else:
+ raise KeyError
+
+ def set_to_plot(self, curve_type, value):
+ '''
+ Sets the plotting status of the curve type
+ '''
+ if curve_type in self.plot_who:
+ self.plot_who[curve_type] = value
+ else:
+ raise KeyError
+
+ def stiffness_tomography_array(self):
+ '''
+ Generates the stiffness tomography array
+ '''
+ floor = num.floor(self.__array['Piezo'] / self.stiffness['size_parts'])
+
+ self.__array['Stiffness tomo'] = post_proc.tomography_array(
+ self.__array['Stiffness'],
+ floor=floor)
+
+ def add_point(self, fdcurves, piezo):
+ '''
+ Add a force distance curve to the file.
+ '''
+ # Calculate the shape of the new array, after we add a new point.
+ nbr_line = num.round(num.sqrt(self.header['number_curves'] + 1))
+ nbr_col = num.ceil(num.sqrt(self.header['number_curves'] + 1))
+ if (nbr_line != self.header['size_x']) or (nbr_col !=
+ self.header['size_y']):
+ self.__fdcurves['trace_y'] = resize_array(
+ self.__fdcurves['trace_y'],
+ (nbr_line, nbr_col,
+ self.header['force_samples_per_curve']))
+ self.__fdcurves['retrace_y'] = resize_array(
+ self.__fdcurves['retrace_y'],
+ (nbr_line, nbr_col,
+ self.header['force_samples_per_curve']))
+ if self.__fdcurves['trace_x'].ndim == 3:
+ self.__fdcurves['trace_x'] = resize_array(
+ self.__fdcurves['trace_x'],
+ (nbr_line, nbr_col,
+ self.header['force_samples_per_curve']))
+ self.__fdcurves['retrace_x'] = resize_array(
+ self.__fdcurves['retrace_x'],
+ (nbr_line, nbr_col,
+ self.header['force_samples_per_curve']))
+ self.__array['Piezo'] = resize_array(self.__array['Piezo'],
+ (nbr_line, nbr_col))
+
+ # The coordinate of the added point.
+ pos_x = (self.header['number_curves']) % nbr_line
+ pos_y = (self.header['number_curves']) / nbr_line
+ # Add the new point.
+ self.__fdcurves['trace_y'][pos_x, pos_y] = fdcurves['trace_y'][0, 0]
+ self.__fdcurves['retrace_y'][pos_x, pos_y] = \
+ fdcurves['retrace_y'][0, 0]
+ if self.__fdcurves['trace_x'].ndim == 3:
+ self.__fdcurves['trace_x'][pos_x, pos_y] = \
+ fdcurves['trace_x'][0, 0]
+ self.__fdcurves['retrace_x'][pos_x, pos_y] = \
+ fdcurves['retrace_x'][0, 0]
+ self.__array['Piezo'][pos_x, pos_y] = piezo
+ # Update the data.
+ self.header['number_curves'] += 1
+ self.header['matrix_length'] = int(nbr_line)
+ self.header['size_x'] = int(nbr_line)
+ self.header['size_y'] = int(nbr_col)
+
+ def export_map_stiffness(self, file_name, depth=None):
+ title = 'Stiffness File ' + self.name
+ plot_array = plot.Array(title=title)
+ if depth == None:
+ #export all depth
+ plot_array.color_scale['min'] = 0
+ plot_array.color_scale['max'] = self.__array['Stiffness'].max()
+ depth = 0
+ for item in self.__array['Stiffness'].transpose(2, 0, 1):
+ plot_array.plot(item, scan_size=self.header['scan_size'])
+ plot_array.title = '%s, depth %03f nm' % (title,
+ self.stiffness['size_parts'])
+ plot_array.save('%s%03i.png' % (file_name, depth))
+ depth += 1
+
+ def export_curve(self, file_name, direction='trace'):
+ """
+ Export the current curve to a csv file.
+
+ * Parameters :
+ file_name: str
+ The complete path and file name to save the curve in.
+ direction: str
+ The direction of the curve. This can be: 'trace',
+ 'retrace', 'both'. Default is 'trace'
+
+ * Returns :
+ None
+
+ >>> file_name = EXPORT_FOLDER + '/export_curve.csv'
+ >>> fv_file = ForceVolume(BIOSCOPE_FILE)
+ >>> fv_file.export_curve(file_name, 'trace')
+ >>> file_name = EXPORT_FOLDER + '/export_curve_both.csv'
+ >>> fv_file.export_curve(file_name, 'both')
+ """
+ legend = []
+ curves = []
+ if direction in ['trace', 'both']:
+ legend.append('trace x')
+ curves.append(self.trace_x)
+ legend.append('trace y')
+ curves.append(self.trace_y)
+ if direction in ['retrace', 'both']:
+ legend.append('retrace x')
+ curves.append(self.retrace_x)
+ legend.append('retrace y')
+ curves.append(self.retrace_y)
+
+ preambule = ['Force distance curve of file :' + self.name,
+ 'Curve [' + str(self.pos_x) + ': ' + str(self.pos_y) + ']']
+ curves = misc.export_list(file_name, curves,
+ legend, preambule, transpose=True)
+
+ def export_events(self, to_file, firstfile=False):
+ """
+ Export the event properties to a csv file.
+
+ * Parameters :
+ to_file: str or csv.writer object
+ If str, it describes the file pathname.
+ If csv.writer object, the function just adds events
+ properties of this ForceVolume object at the end of the
+ file.
+ firstfile: bool (default: False)
+ If True, will add labels to the first line. This has
+ incidence only whith csv.writer object as to_file.
+
+ * Retruns :
+ None.
+
+ >>> file_name = EXPORT_FOLDER + '/export_event.csv'
+ >>> fv_file = ForceVolume(BIOSCOPE_FILE)
+ >>> fv_file.compute_events()
+ >>> fv_file.export_events(file_name)
+ """
+ nbr_evt = self.number_event()
+ evt_list = self.get_event_prop('all')
+ legend = self.get_event_prop('label')
+ if type(to_file) == str:
+ # Create the file.
+ fid = open(to_file, 'wb')
+ fcsv = csv.writer(fid)
+ elif str(type(to_file)) == "<type '_csv.writer'>":
+ # we got a csv writer, then directly write on it.
+ fcsv = to_file
+ if type(to_file) == str or firstfile:
+ fcsv.writerow(['filename'] + legend)
+ # Now, complement the fcsv...
+ for item in range(nbr_evt):
+ fcsv.writerow([self.name] + [evt_list[leg][item]
+ for leg in legend])
+ if type(to_file) == str():
+ fid.close()
+
+ def export_nbr_events(self, to_file, firstfile=False):
+
+ nbr_evt = self.get_event_prop('nbr_ev_per_curve')
+ legend = ['nbr_evt', 'nbr_curves']
+ if type(to_file) == str:
+ # Create the file
+ fid = open(to_file, 'wb')
+ fcsv = csv.writer(fid)
+ elif str(type(to_file)) == "<type '_csv.writer'>":
+ # we got a csv writer, then, directly write on it.
+ fcsv = to_file
+ if type(to_file) == str or firstfile:
+ fcsv.writerow(['filename'] + legend)
+ # Now, complement the fcsv...
+ for nbr_curve, nbr_evt in zip(nbr_evt, range(len(nbr_evt))):
+ fcsv.writerow([self.name, nbr_evt, nbr_curve])
+ if type(to_file) == str():
+ fid.close()
+
+ def test_fct(self):
+ pass
+
+
+class FVFolder(object):
+ '''
+ In this class are stored all the ForceVolume files of a single experiment.
+
+ Important attribute that are read-write accessible :
+
+ filename
+ Stores the name of the original aex file.
+ short_name
+ Same as filename, but without the tree.
+ file
+ Dictionnary that contains informations about the stored files :
+
+ 'list'
+ is the list of stored files.
+ 'name'
+ is the name of stored files.
+ 'group'
+ is the group to which files respectively belongs to.
+ data_hist
+ Is a PlotData instance. Used to plot histograms and scatter with
+ plot_generic module.
+ author
+ String informing about the author of the experiment.
+ comment
+ String that contains information usefull for user.
+ date
+ Datetime instance. Stores the creation date.
+ counter, counter_text
+ Integer and string to be displayed in the progressbar for the gui.
+ parameters
+ Dictionnary that contains informations about some parameters :
+
+ 'event_detect_weight'
+ Float. [0:inf] How much the noise has to be considered for the
+ event detection. 0 means no influence (detection won't work)
+ 'rel_stiff_dist'
+ Integer. Is the maximum distance to consider for the relative
+ stiffness computation.
+ 'PoC_threshold'
+ Float. [0:inf] Threshold to detect the point of contact.
+ 'spring_constant'
+ Float. Is the spring constant of the used cantilever.
+ 'glass'
+ Float. Is the correction for the slope recorded on stiff
+ substrate.
+ 'injection_index' [Deprecated]
+ Integer. Is the index of the first file after injection of
+ chemical.
+ 'number_curves'
+ Integer. The total number of curves in the experiment.
+ event_stat
+ Dictionnary that contais event properties, ordered.
+
+ 'force', 'lr', 'dist', 'stiff', 'rel_stiff'
+ Are list containing the computed force, loading rate, distance
+ from the end of indentation, stiffness and relative stiffness
+ respectively
+ relative_stiffness
+ Dictionnary that contains information about relative stiffness.
+
+ 'data'
+ List containing the relative stiffness of event.
+ 'data_ctl'
+ List containing the relative stiffness of randomly selected
+ pixels
+ 'indice'
+ First file after chemical injection.
+
+ >>> #fv_folder = FVFolder()
+ >>> #fv_folder.load()
+ '''
+
+ def __init__(self):
+ # TODO eliminate self.relative_stiffness['mean'] or use it...
+
+ self.filename = None # stores the filename of aex file where it is
+ # saved
+ self.short_name = None # the short name to display
+ self.file = {'list': [],
+ 'name': [],
+ 'group': [],
+ 'current': 0} # Stores the indice of the current used
+ # file.
+ self.group = {'id': [], # List of int. The id of the group.
+ 'label': [], # List of str. Label corresponding to
+ # group to be displayed in the plots.
+ 'display': []} # List of bool. Informs wether to display
+ # or not the correspinding group.
+ self.data_hist = PlotData()
+ self.author = 'Author name'
+ self.comment = 'Your comment'
+ self.date = datetime.now()
+ self.counter = {'num': 0,
+ 'text': 'Please wait ...'}
+ self.parameters = {
+ 'event_detect_weight': 2,
+ # The fit use for the events (can be wlc, fjc or None)
+ 'event_fit_model': None,
+ 'rel_stiff_dist': 3,
+ 'PoC_threshold': 2,
+ 'spring_constant': None,
+ 'glass': None,
+ 'injection_index': 0,
+ 'number_curves': 0,
+ 'histo_y_rel': False, # The y axis in histogram. If False, the
+ # histogram y axis reflects the number of data. If true, it
+ # reflects the frequency of the data.
+ 'plot_size': [800, 600], # The size, in pixel, of the plot to
+ # save.
+ 'plot_hist_gauss': True,
+ }
+ self.event_stat = {'force': None,
+ 'lr': None,
+ 'dist': None,
+ 'stiff': None,
+ 'rel_stiff': None}
+ self.relative_stiffness = {'data': None,
+ 'data_ctl': None,
+ 'indice': None}
+ # The list of switch to enable or disable functions. Use self.switch()
+ # to change the state.
+ self.switch_dict = {'mask': 'No',
+ 'all': 'No'}
+
+ def __setattr__(self, att, value):
+ if att is 'PoC_threshold':
+ for fv in self.file['list']:
+ fv.stiffness['poc_threshold'] = value
+ self.parameters['PoC_threshold'] = value
+ else:
+ self.__dict__[att] = value
+
+ def __getattr__(self, att):
+ '''
+ Called if requested attribute does not exist.
+ '''
+ if att == 'event_force':
+ __list = []
+ for force_volume in self.file['list']:
+ __list = __list + force_volume.event_force_list()
+ self.event_force = __list
+ return self.event_force
+ elif att == 'event_distance':
+ __list = []
+ for force_volume in self.file['list']:
+ __list = __list + force_volume.event_dist_list()
+ self.event_distance = __list
+ return self.event_distance
+ elif att == 'event_lr':
+ __list = []
+ for force_volume in self.file['list']:
+ __list = __list + force_volume.event_lr_list()
+ self.event_lr = __list
+ return self.event_lr
+ elif att == 'event_stiffness':
+ __list = []
+ for force_volume in self.file['list']:
+ __list = __list + force_volume.event_stiff_list()
+ self.event_stiffness = __list
+ return self.event_stiffness
+ elif att == 'event_relstiff':
+ __list = []
+ for dist in range(self.parameters['rel_stiff_dist'] + 1):
+ __dist = []
+ for force_volume in self.file['list']:
+ __dist = __dist + force_volume.event_rel_stiff_list(dist)
+ __list.append(__dist)
+ self.event_relstiff = __list
+ return self.event_relstiff
+ elif att == 'average_stiffness':
+ """
+ This is the average stiffness in function of the depth
+ """
+ return [fv.compute_average_stiffness() for fv in self.file['list']]
+ else:
+ print att
+ raise AttributeError
+
+ def get_file(self, nbr):
+ """
+ Get the forcevolume file from the list.
+
+ * Parameters :
+ nbr: int
+ The index number of the force volume object.
+ """
+ self.file['current'] = nbr
+ self.update_switch()
+ return self.file['list'][nbr]
+
+ def get_masked_list(self):
+ """
+ Get the list of masked switch for each forcevolume files.
+
+ fvfolder.get_masked_list()
+ [True, False, False, False, True, True]
+ """
+
+ return [fv.get_switch('mask') for fv in self.file['list']]
+
+ def get_event_att(self, att):
+ if att in ['force', 'force_base', 'dist', 'stiff_ev', 'stiff_notev',
+ 'lr', 'stiff', 'length', 'plength']:
+ __list = []
+ __group = []
+ __fid = []
+ __coord = None # to be done...
+ for force_volume, group in zip(self.file['list'],
+ self.file['group']):
+ __add_to_list = force_volume.get_event_prop(att)
+ __add_to_group = [group for i in range(len(__add_to_list))]
+
+ __this_fid = self.file['list'].index(force_volume)
+ __add_to_fid = [__this_fid for i in range(len(__add_to_list))]
+
+ __list = __list + __add_to_list
+ __group = __group + __add_to_group
+ __fid = __fid + __add_to_fid
+ if att in ['force', 'force_base']:
+ self.event_force = __list
+ elif att == 'dist':
+ self.event_distance = __list
+ elif att == 'lr':
+ self.event_lr = __list
+ elif att == 'stiff':
+ self.event_stiffness = __list
+ elif att == 'length':
+ self.event_length = __list
+ elif att == 'plength':
+ self.event_plength = __list
+ elif att in ['relstiff', 'relstiffctl']:
+ __list = []
+ __group = []
+ __fid = []
+ __coord = None # to be done...
+ for dist in range(self.parameters['rel_stiff_dist'] + 1):
+ __dist = []
+ __dist_group = []
+ __dist_fid = []
+ # __dist_coord = []
+ for force_volume, group in zip(self.file['list'],
+ self.file['group']):
+ if att == 'relstiff':
+ __add_to_list = force_volume.event_rel_stiff_list(dist)
+ elif att == 'relstiffctl':
+ __add_to_list = \
+ force_volume.event_rand_rel_stiff_list(dist)
+ __add_to_list = list(__add_to_list)
+ __add_to_group = [group for i in range(len(__add_to_list))]
+ __this_fid = self.file['list'].index(force_volume)
+ __add_to_fid = [__this_fid
+ for i in range(len(__add_to_list))]
+ __dist = __dist + __add_to_list
+ __dist_group = __dist_group + __add_to_group
+ __dist_fid = __dist_fid + __add_to_fid
+ __list.append(__dist)
+ __group.append(__dist_group)
+ self.event_relstiff = __list
+ __fid = __dist_fid
+ return [__list, __group, __fid, __coord]
+
+ def load_file(self, fv_file):
+ '''
+ Loads the file specified
+ '''
+ self.file['list'].append(ForceVolume(fv_file))
+ self.file['list'].sort()
+ self.__regenerate_filename_list()
+ self.parameters['spring_constant'] = \
+ self.file['list'][0].header['spring_constant']
+ self.parameters['glass'] = \
+ self.file['list'][0].stiffness['glass']
+
+ def load_folder(self, fv_folder):
+ '''
+ Loads all the files in a folder
+ '''
+ list_files = os.listdir(fv_folder)
+ self.counter['num'] = 0
+ counter_total = len(list_files) + 1
+ counter_this = 0
+ for files in list_files:
+ self.counter['text'] = 'Loading ' + files + '...'
+ try:
+ self.load_file(os.path.join(fv_folder, files))
+ except TypeError as detail:
+ if detail.args[0] == "Not a supported AFM file format.":
+ pass
+ elif 'asylum' in detail.args[0]:
+ pass
+ else:
+ raise TypeError(detail)
+ counter_this += 1
+ finally:
+ counter_this += 1
+ self.counter['num'] = float(counter_this) / counter_total
+ self.file['list'].sort()
+ try:
+ self.convert_v6()
+ except KeyError:
+ pass
+ self.__regenerate_filename_list()
+ self.__regenerate_comment()
+ self.counter['num'] = 1
+
+ def convert_v6(self):
+ new_file_list = []
+ container = None
+ self.counter['text'] = 'Reshaping data from V6...'
+ for counter_this in range(1, len(self.file['list'])):
+ if (6100000 <
+ self.file['list'][counter_this].header['di_version'] <
+ 6200000):
+ name0 = os.path.splitext(self.file['list']
+ [counter_this - 1].name)[0]
+ name1 = os.path.splitext(self.file['list']
+ [counter_this].name)[0]
+ if name0 == name1:
+ if container is None:
+ container = self.file['list'][counter_this - 1]
+ container = self.merge_single(container,
+ self.file['list'][counter_this])
+ else: # A new serie is comming
+ new_file_list.append(container)
+ container = self.file['list'][counter_this]
+ new_file_list.append(container)
+ new_file_list.sort()
+ if container is not None:
+ self.file['list'] = new_file_list
+
+ def merge_single(self, container, fv_file):
+ '''
+ Merge force volume files that contains only one fv curve.
+ The first fv_file is the container that can contain already several
+ force curve.
+ The second is the force curve that will be added.
+ '''
+ ## Variable to update :
+ ##
+ if fv_file._ForceVolume__fdcurves['trace_y'].shape[0:2] == (1, 1):
+ container.add_point(fv_file._ForceVolume__fdcurves,
+ fv_file.get_array('Piezo')[0, 0])
+ return container
+
+ def load_aex(self, aex_file):
+ '''
+ Loads a AFM Experiment XML file
+ '''
+ self.filename = aex_file
+ self.short_name = os.path.split(aex_file)[1]
+ self.counter['num'] = None
+ self.counter['text'] = 'Uncompressing files ...'
+ [experiment, file_tree] = aex.load(aex_file)
+ self.author = experiment['author']
+ self.comment = experiment['comment']
+ self.date = experiment['date']
+ self.parameters = experiment['parameters']
+ self.counter['text'] = 'Loading files ...'
+ for item in file_tree:
+ self.load_file(item)
+ self.file['list'].sort()
+ self.__regenerate_filename_list
+ self.counter['num'] = 1
+ self.update_switch()
+ if len(experiment['group']['id']):
+ self.group = experiment['group']
+
+ def save(self, file_name):
+ self.filename = file_name
+ self.short_name = os.path.split(file_name)[1]
+ self.counter['num'] = None
+ self.counter['text'] = 'Saving files ...'
+ aex.save(self, file_name)
+ self.counter['num'] = 1
+
+ def set_parameters(self, param, value):
+ """
+ Change the parameters of the force volume files in the experiments.
+
+ * Parameters :
+ param: str
+ Defines which parameter to change.
+ 'spring_constant'
+ 'glass'
+ 'poisson_ratio'
+ value: depending on the parameters.
+ float if 'spring_constant', 'glass' or 'poisson_ratio'
+ """
+ if param == 'spring_constant':
+ for fc in self.file['list']:
+ fc.header['spring_constant'] = value
+ self.parameters['spring_constant'] = value
+ elif param == 'glass':
+ for fv in self.file['list']:
+ fv.stiffness['glass'] = value
+ self.parameters['glass'] = value
+ elif param == 'poisson_ratio':
+ if self.get_switch('all') == 'Yes':
+ [fv.change_poisson_ratio(value) for fv in self.file['list']]
+ else:
+ self.file['list'][self.file['current']].change_poisson_ratio(
+ value)
+ elif param == 'tip_carac':
+ if self.get_switch('all') == 'Yes':
+ [fv.set_parameters('tip_carac', value)
+ for fv in self.file['list']]
+ else:
+ self.file['list'][self.file['current']].set_parameters(
+ 'tip_carac', value)
+ elif param == 'plot_size':
+ self.parameters['plot_size'] = [int(value[0]), int(value[1])]
+ elif param == 'plot_hist_gauss':
+ # bool to display gauss fit on histograms.
+ if isinstance(value, bool):
+ self.parameters['plot_hist_gauss'] = value
+ elif isinstance(eval(value), bool):
+ self.parameters['plot_hist_gauss'] = eval(value)
+ else:
+ raise AttributeError(
+ '%s is not a correct type for this parameter' %
+ type(value))
+
+ def set_group(self, group_list):
+ """
+ Change the group appartenance for the fv files, and update the
+ group list.
+ """
+
+ for fv, group in zip(self.file['list'], group_list):
+ fv.header['group'] = group
+ self.__regenerate_filename_list()
+
+ def set_group_prop(self, prop, which, value):
+ """
+ Change the properties of the group.
+
+ * Parameters :
+ prop: str
+ 'label': to change the label
+ 'visible': to switch the visibility of the group
+ which: int
+ The group id to change. if -1, will change all the label
+ value: str or bool
+ if prop is label: str
+ if prop is visible: True or False
+ if which is -1, need a list of prop. the length has to be
+ the same as the number of groups.
+ """
+ if prop not in ['label', 'display']:
+ raise TypeError('%s is not recognized property.' % prop)
+ if which is not -1:
+ if prop == 'label' and type(value) is not str:
+ raise TypeError('Property %s expect string value.' % prop)
+ if prop == 'display' and type(value) is not bool:
+ raise TypeError('Property %s expect boolean value' % prop)
+ # we change only one group property
+ index = self.group['id'].index(which)
+ self.group[prop][index] = value
+ if which == -1:
+ if type(value) is not list:
+ raise TypeError('Expect list of values.')
+ for [gid, gval] in zip(self.group['id'], value):
+ self.set_group_prop(prop, gid, gval)
+
+ def compute_relative_stiffness(self, exclude=0):
+ # initialize temporary variables
+ tmp_indice = num.zeros(len(self.file['list']))
+ max_dot = 0
+ max_rand = 0
+ for fvfile in self.file['list']:
+ max_dot = max((fvfile.get_array('Event') > 0).sum(), max_dot)
+ max_rand = max((fvfile.get_array('Event random') > 0).sum(),
+ max_dot)
+
+ array_shape = (len(self.file['list']),
+ self.parameters['rel_stiff_dist'] + 1,
+ max_dot)
+ self.relative_stiffness['data'] = num.ma.array(
+ num.empty(array_shape),
+ mask=num.ones(array_shape))
+ array_shape = (len(self.file['list']),
+ self.parameters['rel_stiff_dist'] + 1,
+ max_rand)
+ self.relative_stiffness['data_ctl'] = num.ma.array(
+ num.empty(array_shape),
+ mask=num.ones(array_shape))
+ file_nbr = 0
+ for fvfile in self.file['list']:
+ fvfile.change_relative_distance(self.parameters['rel_stiff_dist'])
+ fvfile.compute_relative_stiffness(exclude)
+ t_shape = fvfile.event['rel_stiff'].shape
+
+ self.relative_stiffness['data'].data[file_nbr,
+ :t_shape[0],
+ :t_shape[1]] = \
+ fvfile.event['rel_stiff'].data
+ self.relative_stiffness['data'].mask[file_nbr,
+ :t_shape[0],
+ :t_shape[1]] = \
+ fvfile.event['rel_stiff'].mask
+
+ t_shape = fvfile.event['rel_stiff_ctl'].shape
+ self.relative_stiffness['data_ctl'].data[file_nbr,
+ :t_shape[0],
+ :t_shape[1]] = \
+ fvfile.event['rel_stiff_ctl'].data
+ self.relative_stiffness['data_ctl'].mask[file_nbr,
+ :t_shape[0],
+ :t_shape[1]] = \
+ fvfile.event['rel_stiff_ctl'].mask
+ if file_nbr < self.parameters['injection_index']:
+ tmp_indice[file_nbr] = 0
+ else:
+ tmp_indice[file_nbr] = 0
+ file_nbr += 1
+ self.relative_stiffness['indice'] = num.array(tmp_indice)
+ self.relative_stiffness['data'] = \
+ self.relative_stiffness['data'].transpose(0, 2, 1)
+ self.relative_stiffness['data_ctl'] = \
+ self.relative_stiffness['data_ctl'].transpose(0, 2, 1)
+
+ def generate_mask(self, mtype, mdata):
+ """
+ See FV classe.
+ """
+
+ for fvfile in self.file['list']:
+ fvfile.generate_mask(mtype, mdata)
+
+ def update_switch(self):
+ """
+ Update the switches.
+ """
+
+ convbool = {True: 'Yes', False: 'No'}
+ if self.switch_dict['all'] == 'Yes':
+ # We update other switches to reflect the whole experiment.
+ #
+ ## First the mask :
+ mask = [fv.get_switch('mask') for fv in self.file['list']]
+ if mask.count(True) == len(mask):
+ self.switch_dict['mask'] = 'Yes'
+ elif mask.count(False) == len(mask):
+ self.switch_dict['mask'] = 'No'
+ else:
+ self.switch_dict['mask'] = 'Some'
+ ## Second, the event distance threshold :
+ ev_dist = [fv.get_switch('ev_dist') for fv in self.file['list']]
+ if ev_dist.count(ev_dist[0]) == len(mask):
+ self.switch_dict['ev_dist'] = ev_dist[0]
+ else:
+ self.switch_dict['ev_dist'] = '~~'
+ ## Third, the event lenght computed from the fit :
+ curr_switch = [fv.get_switch('ev_fit_length')
+ for fv in self.file['list']]
+ if curr_switch.count(curr_switch[0]) == len(curr_switch):
+ self.switch_dict['ev_fit_length'] = curr_switch[0]
+ else:
+ self.switch_dict['ev_fit_length'] = '~~'
+ if self.switch_dict['all'] == 'No':
+ mask = self.file['list'][self.file['current']].get_switch('mask')
+ self.switch_dict['mask'] = convbool[mask]
+ self.switch_dict['ev_dist'] = \
+ self.file['list'][self.file['current']].get_switch('ev_dist')
+ self.switch_dict['ev_fit_length'] = \
+ self.file['list'][self.file['current']].get_switch(
+ 'ev_fit_length')
+
+ def get_switch(self, what):
+ if what == 'all':
+ return self.switch_dict['all']
+ elif what == 'mask':
+ self.update_switch()
+ return self.switch_dict['mask']
+ elif what == 'ev_dist':
+ self.update_switch()
+ return self.switch_dict['ev_dist']
+ elif what == 'ev_fit_length':
+ self.update_switch()
+ return self.switch_dict[what]
+
+ def set_switch(self, stype, state):
+ """
+ Switch to True or False several stuffs or indicate the state of
+ internal components.
+
+ * Parameters:
+ stype: str
+ 'all': Switch the FVFolder to consider one or all the
+ forcevolume object.
+ 'ev_dist': Threshold on the distance where event occured.
+ 'ev_fit_length': Threshold on the length of event.
+ state: str
+ if stype is 'all' :
+ 'Yes' if the feature is enabled.
+ 'No' if not.
+ if stype is 'ev_dist':
+ float
+ if stype is 'ev_fit_length' :
+ [float, float]: first is the min, second is the max.
+ """
+ if not stype in ['all', 'ev_dist', 'ev_fit_length']:
+ raise AttributeError(stype +
+ ' is not recognized as a FVFolder switch.')
+
+ if stype == 'all':
+ convbool = {True: 'Yes', False: 'No'}
+ state = convbool[state]
+ self.switch_dict[stype] = state
+ elif stype in ['ev_dist', 'ev_fit_length']:
+ if self.get_switch('all') == 'Yes':
+ [fv.set_switch(stype, state) for fv in self.file['list']]
+ else:
+ self.file['list'][self.file['current']].set_switch(stype,
+ state)
+ self.update_switch()
+
+ def set_data_hist(self, dtype, drange=None, depth=0):
+ """
+ Sets the plot data type and refresh it.
+
+ * Parameters :
+ dtype: str
+ The data type that has to be set on the plot_data instance.
+ depth: int
+ For data dependent to depth. Specify the depth at which the
+ data has to be taken.
+
+ >>> exp = FVFolder()
+ >>> exp.load_aex(STIFFNESS_COMPUTED_FILE)
+ >>> #fv = exp.get_file(1)
+ >>> exp.set_data_hist('young_modulus', \
+ drange=[0, 100000000, 10000000])
+ >>> exp.set_data_hist('young_modulus', drange=[0, 1000000, 100000])
+ """
+ self.__regenerate_filename_list()
+ if dtype == 'young_modulus':
+ flat_list = []
+ flat_group = []
+ if self.get_switch('all') == 'Yes':
+ stiff_list = self.get_stiffness_list('all', depth)
+ file_group = self.file['group']
+ for i in range(len(stiff_list)):
+ flat_list += stiff_list[i]
+ flat_group += [num.unique(file_group)[i] \
+ for nb in range(len(stiff_list[i]))]
+ self.data_hist.set_group_prop('all', self.group)
+ else:
+ fvfile = self.file['list'][self.file['current']]
+ flat_list = fvfile.stiff_list('all', depth)
+ flat_group = [0 for i in range(len(flat_list))]
+ self.data_hist.set_group_prop('all', None)
+ self.data_hist.set_data('stiff', flat_list)
+ self.data_hist.set_group('stiff', flat_group)
+ if drange == None:
+ drange = [0, max(flat_list) / 10, max(flat_list) / 500]
+ self.data_hist.set_range('stiff', drange)
+ self.data_hist.set_type('stiff')
+ self.event_gauss_fit('stiff')
+ self.data_hist.nbr_curves = self.parameters['number_curves']
+
+ def get_rel_stiff(self, dist):
+ """
+ Returns the dictionnary of relative stiffness at the desired
+ distance. It gives it in a format that is friendly to plot with
+ plot_generic.
+ """
+ rl_dict = self.relative_stiffness
+ to_return = dict()
+ for key in rl_dict.keys():
+ to_return[key] = rl_dict[key][dist]
+ return to_return
+
+ def get_relative_stiffness_random(self, dist):
+ """
+ Returns the dictionnary of relative stiffness at the desired
+ distance. It gives it in a format that is friendly to plot with
+ plot_generic.
+ """
+ to_return = dict()
+ rl_dict = self.relative_stiffness_random
+ for key in rl_dict.keys():
+ to_return[key] = rl_dict[key][dist]
+ return to_return
+
+ def get_arrays(self, which):
+ '''
+ Returns the list of the desired arrays:
+
+ which :
+ String definding the array to get. Possible values are :
+
+ * 'Event' to get the event array
+ * 'Topography' to get the topography array
+ '''
+ if which not in self.file['list'][0].get_array('keys'):
+ raise TypeError('"' + which + '" is not supported')
+ array_list = list()
+ for fvfile in self.file['list']:
+ array_list.append(fvfile.get_array(which))
+ return array_list
+
+ def get_stiffness_list(self, dtype, depth=0):
+ """
+ Get the list of the stiffness of the files contain in the
+ experiment.
+
+ It takes advantage of the group definition.
+
+ Returned list is a 2d list which look like this :
+ [Stiffness list group 1, Stiffness list group 2, ...]
+
+ Files belonging to group 0 are not
+ considered.
+
+ >>> exp = FVFolder()
+ >>> exp.load_aex(ALL_COMPUTED_FILE)
+ >>> stiff_list = exp.get_stiffness_list('event')
+ >>> stiff_list2 = exp.get_stiffness_list('not event')
+
+ In our example file, we have 5 + 191 pixels with no computed
+ stiffness for the first depth. Then, we would expect
+ (16 * 16) + (32 * 32) - 5 - 191 = 1078 number of points
+
+ >>> len(stiff_list[0]) + len(stiff_list2[0])
+ 1084
+
+ """
+ if sum(self.file['group']) == 0:
+ # no group was defined, so do not consider it.
+ group = [1 for item in self.file['group']]
+ else:
+ group = self.file['group']
+ glist = list(num.unique(group))
+ stiff_list = [[] for item in glist]
+ for fvfile, gid in zip(self.file['list'], group):
+ # gid means group id...
+ index = glist.index(gid)
+ for item in fvfile.stiff_list(dtype, depth):
+ stiff_list[index].append(item)
+ #stiff_list[index].append(fvfile.get_stiffness_list(dtype))
+ return stiff_list
+
+ def event_nbr_list(self):
+ '''
+ Retuns a list containing the number of detected event per files.
+ '''
+
+ event_list = []
+ for fv in self.file['list']:
+ event_list.append(fv.number_event())
+ return event_list
+
+ def clear_event_prop_list(self):
+ '''
+ Clear the event properties.
+ To do to refresh the list if a new computation was done.
+ '''
+
+ att_list = ['event_force', 'event_distance', 'event_lr',
+ 'event_stiffness', 'event_relstiff']
+ for item in att_list:
+ if hasattr(self, item):
+ delattr(self, item)
+
+ def event_gauss_fit(self, data_type=None, min_val=None, max_val=None,
+ dist=1):
+ '''
+ Automatically finds the best gaussian fit of the event properties.
+
+ data_type can be :
+
+ * 'force' to considere the event forces.
+ * 'lr' to considere the event loading rates.
+ * 'distance' to considere the event distance in the curve
+ * 'stiff' to considere the stiffness in the trace curve where
+ event was detected.
+ * 'relstiff' to considere the relative stiffness of the pixel
+ where event was detected.
+
+ Results are stored in event_stat[data_type] attribute.
+
+ For example :
+
+ >>> exp = FVFolder()
+ >>> exp.load_aex(EVENT_COMPUTED_FILE)
+ >>> exp.data_hist.set_range('force', [0, 1, 0.1])
+ >>> exp.data_hist.set_type('force')
+ >>> [data, group, fid, coord] = exp.get_event_att('force')
+ >>> exp.data_hist.set_data('force', data)
+ >>> exp.event_gauss_fit(data_type='force')#, min_val=0, max_val=1)
+ >>> exp.event_stat['force'].keys()
+ ['std', 'kde', 'maxima', 'count', 'mean']
+
+ * kde is the kernel density estimation. This is an array composed
+ of two vector, respectively the x and y coordinate.
+ * 'mean' is the list of detected gaussian mean
+ * 'std' is the corresponding standard deviation.
+ * 'count' is the corresponding number of elements for the mean and
+ std.
+ * 'maxima' is the detected maxima from the kde.
+
+ To fit loading rate, distance, stiffness and relative stiffness :
+
+ >>> exp.data_hist.set_range('lr', [0, 1, 0.1])
+ >>> exp.data_hist.set_type('lr')
+ >>> [data, group, fid, coord] = exp.get_event_att('lr')
+ >>> exp.data_hist.set_data('lr', data)
+ >>> exp.event_gauss_fit(data_type='lr')
+
+ >>> exp.data_hist.set_range('dist', [0, 1000, 10])
+ >>> exp.data_hist.set_type('dist')
+ >>> [data, group, fid, coord] = exp.get_event_att('dist')
+ >>> exp.data_hist.set_data('dist', data)
+ >>> exp.event_gauss_fit(data_type='dist')
+ '''
+ # defines the groups to be displayed.
+ group = num.array(self.group['id'])[
+ num.array(self.group['display'])]
+ fit, mean, std, count = None, None, None, None
+ if data_type in ['force', 'force_base', 'lr', 'dist', 'stiff',
+ 'relstiff', 'length', 'plength']:
+ min_val = self.data_hist.get_range(data_type).min
+ max_val = self.data_hist.get_range(data_type).max
+ values = self.data_hist.get_data(data_type, group)
+ index = (min_val < values) * (values < max_val)
+ self.event_stat[data_type] = post_proc.find_gauss_fit(values[index])
+
+ def export_stiffness(self, filename):
+ """
+ Export the stiffness in a set of csv file. Each file contain the
+ stiffness at
+
+ >>> exp = FVFolder()
+ >>> exp.load_aex(STIFFNESS_COMPUTED_FILE)
+ >>> exp.export_stiffness(os.path.join(EXPORT_FOLDER, \
+ 'stiffness_export'))
+ """
+ # Get all the stiffness
+ #stiffness_list = list()
+ split_filename = os.path.splitext(filename)
+ if split_filename[1] in ['.csv', '.txt']:
+ filename = split_filename[0]
+ else:
+ # It is probably not an extention, then restore it
+ filename = split_filename[0] + split_filename[1]
+ old_masked_print_option = num.ma.masked_print_option.display()
+ num.ma.masked_print_option.set_display('')
+ shapes = list()
+ size_parts = list()
+ for fv in self.file['list']:
+ if fv.has_stiffness:
+ shapes.append(fv.get_array('Stiffness').shape)
+ size_parts.append(fv.stiffness['size_parts'])
+ # The shape are in the forme :
+ # (size_x, size_y, depth)
+ # to take the max depth: shape[:,2].max()
+ shapes = num.array(shapes)
+ max_depth = shapes[:, 2].max()
+ max_size = max(shapes[:, 0] * shapes[:, 1])
+ for depth in range(max_depth):
+ this_file = filename + '_Deep_' + str(depth) + '.csv'
+ this_array = num.ma.array(
+ num.empty((max_size, len(self.file['list']))),
+ mask=num.ones((max_size,
+ len(self.file['list']))))
+ # this_averate will contain 1) mean, 2) std and 3) count.
+ this_average = num.empty((3, len(self.file['list'])))
+ for fv in self.file['list']:
+ if fv.has_stiffness:
+ if fv.get_array('Stiffness').shape[2] >= depth:
+ # Get the data and create the mask according to it.
+ try:
+ vector = fv.get_array(
+ 'Stiffness')[:, :, depth].flatten()
+ except IndexError:
+ print "SHIT"
+ vector = num.ma.array(num.empty(5),
+ mask=num.ones(5))
+ # Put the data in the masked array.
+ index = self.file['name'].index(fv.name)
+ this_array[:vector.shape[0], index] = vector
+ this_array.mask[:vector.shape[0], index] = vector.mask
+ this_average[0, index] = vector.mean()
+ this_average[1, index] = vector.std()
+ this_average[2, index] = vector.count()
+ # Write the file.
+ fid = open(this_file, 'wb')
+ fcsv = csv.writer(fid)
+ fcsv.writerow(['Stiffness data of file :', self.filename])
+ fcsv.writerow(['Injection time : ',
+ self.parameters['injection_index']])
+ fcsv.writerow(self.file['name'])
+ fcsv.writerow(['Group'])
+ fcsv.writerow(self.file['group'])
+ fcsv.writerow(['Depth size : [nm]'])
+ fcsv.writerow(num.array(size_parts))
+ fcsv.writerow(['Global: mean'])
+ fcsv.writerow(this_average[0, :])
+ fcsv.writerow(['Global: std'])
+ fcsv.writerow(this_average[1, :])
+ fcsv.writerow(['Global: number'])
+ fcsv.writerow(this_average[2, :])
+ fcsv.writerow(['Global: all values'])
+ fcsv.writerows(this_array)
+ fid.close()
+ # restore the old display option of masked arrays
+ num.ma.masked_print_option.set_display(old_masked_print_option)
+
+ def export_average_stiffness_tomo(self, filename):
+ """
+ Exports the average stiffness tomography to the specified file.
+
+ * Parameters :
+ filename: str
+ path-name to save the file.
+
+ * Output :
+ None
+ """
+
+ ## generate the filename
+ split_filename = os.path.splitext(filename)
+ if split_filename[1] in ['.csv', '.txt']:
+ filename = split_filename[0]
+ else:
+ # It is probably not an extention, then restore it
+ filename = split_filename[0] + split_filename[1]
+ filename = filename + '.csv'
+ old_masked_print_option = num.ma.masked_print_option.display()
+ num.ma.masked_print_option.set_display('')
+
+ # get the data to export
+ mean_stiffness = self.average_stiffness
+ # the data are [{'deep': [], 'mean': [], 'std': [], 'count': []}]
+ # Rearrange the data to be an array...
+ deep = [i['deep'] for i in mean_stiffness]
+ mean = [i['mean'] for i in mean_stiffness]
+ std = [i['std'] for i in mean_stiffness]
+ count = [i['count'] for i in mean_stiffness]
+
+ sizes = [len(i) for i in deep]
+ max_size = max(sizes)
+ max_index = sizes.index(max_size)
+
+ array_size = (3 * len(mean_stiffness) + 1, max_size)
+
+ this_array = num.ma.array(num.empty(array_size),
+ mask=num.ones(array_size))
+
+ # Fill the array with the data
+ this_array[0][:len(deep[max_index])] = deep[max_index]
+ this_array[0].mask[:len(deep[max_index])] = 0
+ add = 1
+ for i, this_mean in zip(range(len(mean_stiffness)), mean):
+ this_array[i + add][:len(this_mean)] = this_mean
+ this_array[i + add].mask[:len(this_mean)] = 0
+ add = 1 + len(mean_stiffness)
+ for i, this_std in zip(range(len(mean_stiffness)), std):
+ this_array[i + add][:len(this_std)] = this_std
+ this_array[i + add].mask[:len(this_std)] = 0
+ add = 1 + 2 * len(mean_stiffness)
+ for i, this_count in zip(range(len(mean_stiffness)), count):
+ this_array[i + add][:len(this_count)] = this_count
+ this_array[i + add].mask[:len(this_count)] = 0
+ #this_array = this_array.transpose(1,0)
+ label = ['deep'] + \
+ ['mean of file ' + fvname for fvname in self.file['name']] +\
+ ['std of file' + fvname for fvname in self.file['name']] +\
+ ['count of file' + fvname for fvname in self.file['name']]
+ # Write the csv
+ fid = open(filename, 'wb')
+ fcsv = csv.writer(fid)
+ fcsv.writerow(['Stiffness data of file :', self.filename])
+ fcsv.writerow(label)
+ fcsv.writerows(this_array.transpose(1, 0))
+ num.ma.masked_print_option.set_display(old_masked_print_option)
+
+ def export_events(self, filename):
+ """
+ Export the events of all files.
+
+ * Parameters :
+ filename: str
+ The file pathname to store the data
+
+ * Retruns :
+ None.
+
+ >>> exp = FVFolder()
+ >>> exp.load_aex(EVENT_COMPUTED_FILE)
+ >>> exp.export_events(EXPORT_FOLDER + '/experiment_events.csv')
+ """
+
+ fid = open(filename, 'wb')
+ fcsv = csv.writer(fid)
+ firstfile = True
+ for fv in self.file['list']:
+ fv.export_events(fcsv, firstfile=firstfile)
+ firstfile = False
+ fid.close()
+
+ def export_nbr_events(self, filename):
+ """
+ Export the number of events per curves of all files.
+
+ * Parameters :
+ filename: str
+ The file pathname to store the data
+
+ * Retruns :
+ None.
+
+ >>> exp = FVFolder()
+ >>> exp.load_aex(EVENT_COMPUTED_FILE)
+ >>> exp.export_nbr_events(EXPORT_FOLDER + \
+ '/experiment_nbr_events.csv')
+ """
+ fid = open(filename, 'wb')
+ fcsv = csv.writer(fid)
+ firstfile = True
+ for fv in self.file['list']:
+ fv.export_nbr_events(fcsv, firstfile=firstfile)
+ firstfile = False
+ fid.close()
+
+ def __regenerate_filename_list(self):
+ '''
+ Regenerates the filename list
+ '''
+ self.file['name'] = []
+ self.file['group'] = []
+ nbr_curves = 0
+ for item in self.file['list']:
+ self.file['name'].append(item.name)
+ self.file['group'].append(item.header['group'])
+ nbr_curves = nbr_curves + item.header['number_curves']
+ self.parameters['number_curves'] = nbr_curves
+ self.data_hist.total_nbr_curves = nbr_curves
+ # For the group labels and display...
+ unique_group = num.unique(self.file['group'])
+ if len(self.group['label']) > len(unique_group):
+ _to_remove = [item not in unique_group
+ for item in self.group['id']]
+ self.__remove_group(_to_remove)
+ elif len(self.group['label']) < len(unique_group):
+ _to_add = [item not in self.group['id'] for item in unique_group]
+ self.__add_group(_to_add)
+
+ def __remove_group(self, to_remove):
+ """
+ Remove item from group.
+
+ * Parameter :
+ to_remove: list
+ list of boolean to tell which remove and which one to keep.
+ """
+ for item in range(len(to_remove)):
+ if to_remove[item]:
+ self.group['label'].pop(item)
+ self.group['id'].pop(item)
+ self.group['display'].pop()
+
+ def __add_group(self, _to_add):
+ """
+ Add object from group.
+
+ * Parameter :
+ to_add: list
+ list of boolean to tell which group to add.
+ """
+
+ new_id = []
+ new_label = []
+ new_disp = []
+ unique_group = num.unique(self.file['group'])
+ for i in range(len(unique_group)):
+ if _to_add[i]:
+ _add = unique_group[i]
+ # Adds this group and put default value.
+ new_id.append(_add)
+ new_label.append('Groupe %i' % _add)
+ new_disp.append(True)
+ else:
+ # This group exist. Recover the user defined value.
+ _gr = unique_group[i]
+ index = self.group['id'].index(_gr)
+ new_id.append(self.group['id'][index])
+ new_label.append(self.group['label'][index])
+ new_disp.append(self.group['display'][index])
+ self.group['id'] = new_id
+ self.group['label'] = new_label
+ self.group['display'] = new_disp
+
+ def __regenerate_comment(self):
+ if self.comment == 'Your comment':
+ comment = ''
+ for fvfile in self.file['list']:
+ if fvfile.header['note'] != '\n':
+ comment = comment + fvfile.name + '\n' + \
+ fvfile.header['note'] + '\n'
+ if comment != '':
+ # We update only if there was a comment in the file headers,
+ # and if it's the first (i.e. self.comment == 'Your comment')
+ comment = comment[:-1] # we erase the last \n
+ self.comment = comment
+
+
+class NotReadyError(Exception):
+ pass
+
+ARRAY_TYPE = {'Piezo': 'topo',
+ 'PoCIndice': 'topo',
+ 'PoCNoise': 'topo',
+ 'Topography': 'topo',
+ 'Stiffness': 'topo',
+ 'Stiffness tomo': '3d',
+ 'Event': 'event',
+ 'Event force': 'event',
+ 'Event length': 'event',
+ 'Event distance': 'event',
+ 'Event random': 'revent',
+ 'Relative pos': 'rel',
+ 'Relative rand pos': 'rel',
+ 'Path': 'misc', # This defines a path to display info.
+ 'Masked': 'misc', # This option is to apply or not a mask to the
+ # array.
+ 'Mask': 'misc'}
+
+if __name__ == '__main__': # pragma: no cover
+# import plot_generic
+# test_file = '/home/charles/AFM/Macrophages/Living_cytB2.aex'
+# exp_1 = FVFolder()
+# exp_1.load_aex(test_file)
+# win_plot = plot_generic.ErrorBar()
+# win_plot.average_stiffness(exp_1.average_stiffness,
+# group=exp_1.file['group'],
+# group_info=exp_1.group,
+# style = 'isol')
+# win_plot.save('test.png')
+
+ import doctest
+ BIOSCOPE_FILE2 = '../docs/data/files/FR20074A.001'
+# fvfile = ForceVolume(BIOSCOPE_FILE2)
+ BIOSCOPE_FILE = '../docs/data/files/FR2310B.004'
+ BIOSCOPE_FOLDER = '../docs/data/files/'
+ STIFFNESS_COMPUTED_FILE = '../docs/data/Stiffness_computed.aex'
+ EVENT_COMPUTED_FILE = '../docs/data/Event_computed.aex'
+ ALL_COMPUTED_FILE = '../docs/data/Stiffness_and_Event_computed.aex'
+ EXPORT_FOLDER = '../docs/data/Garbage'
+ doctest.testmod()
diff --git a/openfovea/common.py b/openfovea/common.py
new file mode 100644
index 0000000..765b776
--- /dev/null
+++ b/openfovea/common.py
@@ -0,0 +1,3 @@
+APP_NAME = "OpenFovea"
+DEB_NAME = "openfovea"
+APP_VERSION = "0.1a162"
diff --git a/openfovea/dialog_experiment_properties.py b/openfovea/dialog_experiment_properties.py
new file mode 100644
index 0000000..b7dcf94
--- /dev/null
+++ b/openfovea/dialog_experiment_properties.py
@@ -0,0 +1,267 @@
+#!/usr/bin/env python
+# -*- coding: UTF-8 -*-
+
+from math import log10, floor
+
+import pygtk
+import gtk
+# for script installation
+from pkg_resources import resource_filename
+##############
+## Graphic library ##
+##############
+try:
+ matplotlib.__version__
+except NameError:
+ import matplotlib
+ matplotlib.use('Agg', warn=False)
+from matplotlib.figure import Figure
+from matplotlib.backends.backend_gtkagg import FigureCanvasGTKAgg as FigureCanvas
+from matplotlib.backends.backend_gtkagg import NavigationToolbar2GTKAgg as NavigationToolbar
+
+from fovea_toolbox import curve
+
+class main_win():
+ def __init__(self, author=None, comment=None, spring_constant=None,
+ inject_index=None, stiff_glass=None, trace_curve=None,
+ plot_size=None, group=None, _glade_filename=None,
+ linewidth=1.5, histo_y_rel=True, plot_hist_gauss=False,
+ plot_color=None):
+ builder = gtk.Builder()
+ if _glade_filename == None:
+ try:
+ _glade_filename = resource_filename(
+ 'openfovea',
+ 'glade/dialog_experiment_properties.glade')
+ except NotImplementedError:
+ _glade_filename = 'openfovea/glade/dialog_experiment_properties.glade'
+ builder.add_from_file(_glade_filename)
+ self.window = builder.get_object('ExpProp')
+ self.widget = {
+ # General tab
+ 'text_author' : builder.get_object('text_author'),
+ 'text_description' : builder.get_object('text_description'),
+ ## Configuration tab
+ # Time Lapse (This is outdated, I should clean it...)
+ 'spin_inject_index' : builder.get_object('entry_injection_index'),
+ # System
+ 'entry_spring_cst' : builder.get_object('entry_spring_constant'),
+ 'spin_stiff_glass' : builder.get_object('spin_stiff_glass'),
+ # Plot area...
+ 'plot_area' : builder.get_object('plot_area'),
+ 'box_nav_tool' : builder.get_object('box_nav_tool'),
+ ## Graphs
+ # Saved size
+ 'spin_save_plot_width' : builder.get_object('spin_save_plot_width'),
+ 'spin_save_plot_height' : builder.get_object('spin_save_plot_height'),
+ # Plot options
+ 'spin_linewidth' : builder.get_object('spin_linewidth'),
+ 'check_bw_plot' : builder.get_object('check_bw_plot'),
+ # Histogram options
+ 'check_plot_hist_gauss' : builder.get_object('check_plot_hist_gauss'),
+ 'histo_y_rel' : builder.get_object('histo_y_rel'),
+ # Group managment.
+ 'table_group' : builder.get_object('table_group'),
+ 'gid' : [], # List of group id
+ 'glabel' : [], # List of gtk label for group
+ 'gdisplay' : [], # List of gtk checkbutton for group
+ }
+ self.widget['spin_stiff_glass'].connect(
+ 'value_changed',
+ self.on_stiff_glass_changed)
+ adj = gtk.Adjustment(value=800, lower=0, upper=1600, step_incr=1, page_incr=100, page_size=0)
+ self.widget['spin_save_plot_width'].set_adjustment(adj)
+ adj = gtk.Adjustment(value=800, lower=0, upper=1600, step_incr=1, page_incr=100, page_size=0)
+ self.widget['spin_save_plot_height'].set_adjustment(adj)
+ adj = gtk.Adjustment(value=linewidth, lower=0.1, upper=5, step_incr=0.1, page_incr=1, page_size=0)
+ self.widget['spin_linewidth'].set_adjustment(adj)
+
+ builder.connect_signals(self)
+ self.author = author
+ self.comment = comment
+ self.spring_cst = spring_constant
+ self.inject_index = inject_index
+ self.stiff_glass = stiff_glass
+ self.trace_curve = trace_curve
+ self.save_size = plot_size
+ ### The plotting part :
+ self.figure = Figure(figsize=(100, 100), dpi=75, facecolor=(0.93,0.92,0.9,1))
+ self.axis = self.figure.add_subplot(111)
+ self.axis.grid(True)
+ self.canvas = FigureCanvas(self.figure) # a gtk.DrawingArea
+ self.canvas.show()
+ self.graphview = self.widget['plot_area']
+ self.graphview.pack_start(self.canvas, True, True)
+ # below is optional if you want the navigation toolbar
+ self.navToolbar = NavigationToolbar(self.canvas, self.window)
+ self.navToolbar.lastDir = '/var/tmp/'
+ self.widget['box_nav_tool'].pack_start(self.navToolbar)
+ self.navToolbar.show()
+ self.group = None
+ # About the groups...
+ if group is not None:
+ table = builder.get_object('table_group')
+ text = gtk.Label('ID')
+ text.show()
+ table.attach(text, 0, 1, 0, 1)
+ text = gtk.Label('Label')
+ text.show()
+ table.attach(text, 1, 2, 0, 1)
+ text = gtk.Label('Display')
+ text.show()
+ text = table.attach(text, 2, 3, 0, 1)
+ self.group_widget = {
+ 'label' : [],
+ 'display' : []
+ }
+ _id_gp = 1
+ for gp_id, gp_label, gp_disp in zip(
+ group['id'], group['label'], group['display']):
+ self.widget['gid'].append(gp_id)
+ text = gtk.Label(gp_id)
+ text.show()
+ table.attach(text, 0, 1, _id_gp, _id_gp+1)
+ text = gtk.Entry(0)
+ text.set_text(gp_label)
+ text.show()
+ table.attach(text, 1, 2, _id_gp, _id_gp+1)
+ self.widget['glabel'].append(text)
+ check = gtk.CheckButton('Show')
+ check.set_active(gp_disp)
+ check.show()
+ table.attach(check, 2,3,_id_gp, _id_gp+1)
+ self.widget['gdisplay'].append(check)
+ _id_gp += 1
+ #Display Options
+ self.linewidth = linewidth
+ self.histo_y_rel = histo_y_rel
+ self.plot_hist_gauss = plot_hist_gauss
+ self.plot_color = plot_color
+ self.update()
+ def update(self):
+ '''
+ Update the informations in the window.
+ '''
+ if self.author is not None:
+ self.widget['text_author'].set_text(self.author)
+ if self.comment is not None:
+ text_buffer = self.widget['text_description'].get_buffer()
+ text_buffer.set_text(self.comment)
+ if self.spring_cst is not None:
+ self.widget['entry_spring_cst'].set_text(str(self.spring_cst))
+ if self.inject_index is not None:
+ index_adjustment = self.widget['spin_inject_index'].get_adjustment()
+ index_adjustment.set_all(value=int(self.inject_index[0]),
+ upper=int(self.inject_index[1])
+ )
+ if self.stiff_glass is not None:
+ self.on_stiff_glass_changed(None)
+ if self.save_size is not None:
+ self.widget['spin_save_plot_width'].set_value(self.save_size[0])
+ self.widget['spin_save_plot_height'].set_value(self.save_size[1])
+ if self.histo_y_rel is not None:
+ self.widget['histo_y_rel'].set_active(self.histo_y_rel)
+ if self.plot_hist_gauss is not None:
+ self.widget['check_plot_hist_gauss'].set_active(self.plot_hist_gauss)
+ if self.plot_color is not None:
+ if self.plot_color is 'bw':
+ self.widget['check_bw_plot'].set_active(True)
+ else:
+ self.widget['check_bw_plot'].set_active(False)
+
+ def on_stiff_glass_changed(self, widget):
+ stiff_glass_val = self.widget['spin_stiff_glass'].get_value()
+ if stiff_glass_val:
+ self.stiff_glass = stiff_glass_val
+ stiff_glass_adjustment = self.widget['spin_stiff_glass'].get_adjustment()
+ lower = self.stiff_glass*2
+ if self.stiff_glass:
+ n_digits = int(floor(log10(abs(self.stiff_glass))))
+ if n_digits < 0:
+ self.widget['spin_stiff_glass'].set_digits(-n_digits+2)
+ step_increment = -lower / 100
+ stiff_glass_adjustment.set_all(value=self.stiff_glass,
+ upper=0,
+ lower=lower,
+ step_increment = step_increment,
+ page_increment = step_increment / 4,
+ page_size = 0)
+ self.display_plot()#update_curve()
+ def on_spin_save_plot_width_value_changed(self, widget):
+ self.save_size[0] = widget.get_value()
+ adjustment = widget.get_adjustment()
+ adjustment.set_upper(widget.get_value() + 800)
+ def on_spin_save_plot_height_value_changed(self, widget):
+ self.save_size[1] = widget.get_value()
+ adjustment = widget.get_adjustment()
+ adjustment.set_upper(widget.get_value() + 800)
+ def on_spin_linewidth_value_changed(self, widget):
+ self.linewidth = widget.get_value()
+ adjustment = widget.get_adjustment()
+ def on_button_validate_clicked(self, widget):
+ '''
+ Get the information from the window and store them in the following
+ attributes :
+ self.author
+ self.comment
+ self.spring_constant
+ self.inject_index
+ self.save_size
+ '''
+ self.author = self.widget['text_author'].get_text()
+ text_buffer = self.widget['text_description'].get_buffer()
+ self.comment = text_buffer.get_text(text_buffer.get_start_iter(),
+ text_buffer.get_end_iter())
+ self.spring_cst = eval(self.widget['entry_spring_cst'].get_text())
+ self.inject_index[0] = int(self.widget['spin_inject_index'].get_value())
+ # Get values for groups
+ self.group = {
+ 'id' : self.widget['gid'],
+ 'label' : [it.get_text() for it in self.widget['glabel']],
+ 'display' : [it.get_active() for it in self.widget['gdisplay']]
+ }
+ #Get display options
+ self.linewidth = self.widget['spin_linewidth'].get_value()
+ self.histo_y_rel = self.widget['histo_y_rel'].get_active()
+ self.plot_hist_gauss = self.widget['check_plot_hist_gauss'].get_active()
+ if self.widget['check_bw_plot'].get_active():
+ self.plot_color = 'bw'
+ else:
+ self.plot_color = 'color'
+ self.validate()
+ def on_button_cancel_clicked(self, widget):
+ self.destroy(self, widget)
+ def display_plot(self):
+ if type(self.spring_cst) == list:
+ sc = self.spring_cst[0]
+ else:
+ sc = self.spring_cst
+ [iX, iY] = curve.compute_indentation(self.trace_curve[0],
+ self.trace_curve[1],
+ self.stiff_glass,
+ sc)
+ self.axis.cla()
+ self.axis.grid(True)
+ self.axis.plot(iX, iY, 'k-', linewidth=1.5)
+ self.canvas.draw_idle()
+ def destroy(self, *widget):
+ '''
+ Pretty quit the dialog.
+ '''
+ self.window.destroy()
+ def validate(self):
+ '''
+ This function is here to be overriden by the caller in order to send the
+ signal through the classes.
+ '''
+ pass
+if __name__ == '__main__':
+ group = {'id' : [0, 1, 2],
+ 'label' : ['First', 'Second', 'Third'],
+ 'display' : [True, True, True]}
+ test = main_win(author='Charles Roduit', comment='I like it...',
+ spring_constant=[0.06, 0.04], inject_index=[0, 3], plot_size=[800, 800],
+ group=group,
+ _glade_filename='glade/dialog_experiment_properties.glade')
+ test.window.show()
+ gtk.main()
diff --git a/openfovea/dialog_export_map.py b/openfovea/dialog_export_map.py
new file mode 100644
index 0000000..0f6661f
--- /dev/null
+++ b/openfovea/dialog_export_map.py
@@ -0,0 +1,478 @@
+"""
+ This is the module to display the dialog export map.
+"""
+
+
+import pygtk
+pygtk.require('2.0')
+import gtk
+import os
+from pkg_resources import resource_filename # pylint: disable-msg=E0611
+
+
+from matplotlib.backends.backend_gtkagg import FigureCanvasGTKAgg as \
+ FigureCanvas
+import numpy
+from scipy import interpolate, ndimage
+import plot_generic
+
+class MapDisplay(object):
+ """
+ Displays the map with all the button to tune the exportation.
+ """
+
+ def __init__(self, array, folder=None, depth=1, fvname='', scan_size=None):
+ self.array = {'dict' : array,
+ 'plot' : None, # contain the current array to plot
+ 'type' : None, # contains the type of the current array
+ # (correspond to the dict key)
+ 'indice' : 0,
+ 'depth' : depth, # is the depth between slice
+ 'size' : scan_size,
+ 'scale' : [0, 0],
+ 'do_interp' : 0, # check if we interpolate or not
+ 'interp_max' : 0, # Is the maximum value to be displayed in case of interpolation
+ }
+
+ if folder is None:
+ folder = os.path.expanduser('~')
+ self.file = {'folder' : folder,
+ 'name' : 'File_%f_%t_slice_%n_depth_%d.png',
+ 'fv_name' : fvname}
+ self.dialog = gtk.Dialog(title='Export Maps',
+ buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK,
+ gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
+ )
+ self.dialog.set_default_size(500, 350)
+ try:
+ export_image = resource_filename('openfovea','Icon/export.png')
+ except NotImplementedError:
+ export_image = 'openfovea/Icon/export.png'
+ self.dialog.set_icon(gtk.gdk.pixbuf_new_from_file(export_image))
+
+ # The plot
+ self.plot_array = plot_generic.Array()
+ self.canvas = FigureCanvas(self.plot_array.figure) # a gtk.DrawingArea
+ self.canvas.show()
+
+ self.widget = {'but_folder' : gtk.Button(label=self.file['folder']),
+ 'entry_filename' : gtk.Entry(),
+ 'entry_title' : gtk.Entry(),
+ 'plot_area' : gtk.VBox(),
+ 'combo_array' : gtk.ComboBox(),
+ 'combo_cmap' : gtk.ComboBox(),
+ 'check_auto_filename' : gtk.CheckButton(label='auto'),
+ 'check_axis' : gtk.CheckButton(label='show axis label'),
+ 'check_cmap' : gtk.CheckButton(label='show color scale'),
+ 'check_title' : gtk.CheckButton(label='show title'),
+ 'check_interp' : gtk.CheckButton(label='interpolate'),
+ 'scale_max' : gtk.HScrollbar(),
+ 'scale_min' : gtk.HScrollbar(),
+ 'scale_interp' : gtk.HScrollbar(),
+ 'size_x' : gtk.SpinButton(),
+ 'size_y' : gtk.SpinButton(),
+ }
+ self._init_widgets()
+ # Everything is displayed, plot the grid:
+ self.change_array(self.array['dict'].keys()[0])
+ self.change_scale()
+ self.plot()
+ def _init_scales(self):
+ if self.array['plot'] is not None:
+ ar_min = self.array['plot'].min()
+ ar_max = self.array['plot'].max()
+ else:
+ ar_min = 0
+ ar_max = 0
+ interp_max = ar_max + ar_max * 0.1
+ # The colorscale
+ self.widget['scale_min'].set_adjustment(
+ gtk.Adjustment(ar_min, ar_min,
+ ar_max,
+ (ar_max-ar_min) / 100,
+ (ar_max-ar_min) / 25,
+ 0))
+ self.widget['scale_max'].set_adjustment(
+ gtk.Adjustment(ar_max, ar_min,
+ ar_max,
+ (ar_max-ar_min) / 100,
+ (ar_max-ar_min) / 25,
+ 0))
+ self.widget['scale_interp'].set_adjustment(
+ gtk.Adjustment(interp_max, ar_min,
+ interp_max,
+ (interp_max-ar_min) / 100,
+ (interp_max-ar_min) / 25,
+ 0))
+ self.widget['scale_min'].connect_object("change-value",
+ self.change_scale,
+ None)
+ self.widget['scale_max'].connect_object("change-value",
+ self.change_scale,
+ None)
+ self.widget['scale_interp'].connect_object("change-value",
+ self.change_scale,
+ None)
+ def change_scale(self, widget=None, toto=None, tata=None):
+ self.array['scale'] = [self.widget['scale_min'].get_value(),
+ self.widget['scale_max'].get_value()]
+ self.array['interp_max'] = self.widget['scale_interp'].get_value()
+ self.plot()
+ def _init_widgets(self):
+ """
+ Initialization of the widgets
+ """
+ # Create the table.
+ table = [gtk.Table(2, 2), gtk.Table(2, 5)]
+ table[0].set_border_width(5)
+ table[0].set_col_spacings(5)
+ table[0].set_row_spacings(5)
+ table[0].show()
+
+ # Button folder
+ self.widget['but_folder'].connect('clicked', self.change_folder)
+ self.widget['but_folder'].show()
+ table[0].attach(self.widget['but_folder'],
+ 0, 1, 0, 1, xoptions=0, yoptions=0)
+ # Text filename
+ self.widget['entry_filename'].set_text(self.file['name'])
+ self.widget['entry_filename'].show()
+ table[0].attach(self.widget['entry_filename'],
+ 1, 2, 0, 1, yoptions=0)
+ self.widget['check_auto_filename'].set_active(True)
+ self.widget['check_auto_filename'].connect('toggled', self.change_filename)
+ table[0].attach(self.widget['check_auto_filename'],
+ 2, 3, 0, 1, xoptions=0, yoptions=0)
+ # Table which will contain the display and the control of the display
+ table[1].set_border_width(5)
+ table[1].set_col_spacings(5)
+ table[1].set_row_spacings(5)
+ table[1].show()
+ # The area to plot
+ table[1].attach(self.canvas, 0, 1, 0, 5)
+ #######
+ # The scale bars :
+ grid = gtk.Table(rows=3, columns=2, homogeneous=False)
+ self.widget['scale_min'].show()
+ self.widget['scale_max'].show()
+ #self.widget['scale_interp'].show()
+ grid.attach(self.widget['scale_max'], 1, 2, 0, 1)
+ grid.attach(self.widget['scale_min'], 1, 2, 1, 2)
+ grid.attach(self.widget['scale_interp'], 1, 2, 2, 3)
+ # the labels
+ self.widget['label_scale_min'] = gtk.Label(str='color scale max : ')
+ self.widget['label_scale_min'].set_alignment(0,0)
+ self.widget['label_scale_min'].show()
+ grid.attach(self.widget['label_scale_min'], 0, 1, 0, 1, xoptions=gtk.FILL, yoptions=gtk.FILL)
+ self.widget['label_scale_max'] = gtk.Label(str='color scale min : ')
+ self.widget['label_scale_max'].set_alignment(0,0)
+ self.widget['label_scale_max'].show()
+ grid.attach(self.widget['label_scale_max'], 0, 1, 1,2, xoptions=gtk.FILL, yoptions=gtk.FILL)
+ self.widget['label_scale_interp'] = gtk.Label(str='crop stiffness : ')
+ self.widget['label_scale_interp'].set_alignment(0,0)
+ #self.widget['label_scale_interp'].show()
+ grid.attach(self.widget['label_scale_interp'], 0, 1, 2,3, xoptions=gtk.FILL, yoptions=gtk.FILL)
+ grid.show()
+ table[1].attach(grid, 0, 1, 5, 6)
+
+ # button for the title
+ self.widget['entry_title'].set_text('Title')
+ self.widget['entry_title'].show()
+ self.widget['entry_title'].connect('changed', self.change_title)
+ table[1].attach(self.widget['entry_title'],
+ 1, 2, 0, 1, xoptions=0, yoptions=0)
+ self.plot_array.title = self.widget['entry_title'].get_text()
+ # button to choose the item to plot
+ list_arr = gtk.ListStore(str)
+ for key in self.array['dict']:
+ list_arr.append([key])
+ self.widget['combo_array'].set_model(list_arr)
+ cell = gtk.CellRendererText()
+ self.widget['combo_array'].pack_start(cell, True)
+ self.widget['combo_array'].add_attribute(cell, 'text', 0)
+ self.widget['combo_array'].set_active(0)
+ self.widget['combo_array'].show()
+ self.widget['combo_array'].connect('changed', self.change_array)
+ table[1].attach(self.widget['combo_array'],
+ 1, 2, 1, 2, xoptions=0, yoptions=0)
+ # button to choose the scale
+ list_cmap = gtk.ListStore(str)
+ list_cmap.append(['gray'])
+ list_cmap.append(['jet'])
+ list_cmap.append(['copper'])
+ self.widget['combo_cmap'].set_model(list_cmap)
+ cell = gtk.CellRendererText()
+ self.widget['combo_cmap'].pack_start(cell, True)
+ self.widget['combo_cmap'].add_attribute(cell, 'text', 0)
+ self.widget['combo_cmap'].set_active(0)
+ self.widget['combo_cmap'].show()
+ self.widget['combo_cmap'].connect('changed', self.change_cmap)
+ table[1].attach(self.widget['combo_cmap'],
+ 1, 2, 2, 3, xoptions=0, yoptions=0)
+ # Display the scale ?
+ self.widget['check_axis'].show()
+ self.widget['check_axis'].set_active(True)
+ self.widget['check_axis'].connect('toggled', self.change_axis_label)
+ table[1].attach(self.widget['check_axis'],
+ 1, 2, 3, 4, xoptions=0, yoptions=0)
+ # Display the color map ?
+ self.widget['check_cmap'].show()
+ self.widget['check_cmap'].set_active(False)
+ self.widget['check_cmap'].connect('toggled', self.change_cmap)
+ table[1].attach(self.widget['check_cmap'],
+ 1, 2, 4, 5, xoptions=0, yoptions=0)
+
+ # Interpolate the data ?
+ self.widget['check_interp'].show()
+ self.widget['check_interp'].set_active(False)
+ self.widget['check_interp'].connect('toggled', self.toggle_interp)
+ table[1].attach(self.widget['check_interp'],
+ 1, 2, 5, 6, xoptions=0, yoptions=0)
+
+ # button to choose the size of the image.
+ _size_table = gtk.Table(2, 2)
+ label = gtk.Label('Width')
+ label.show()
+ _size_table.attach(label, 1, 2, 0, 1, xoptions=0, yoptions=0)
+ label = gtk.Label('Height')
+ label.show()
+ _size_table.attach(label, 1, 2, 1, 2, xoptions=0, yoptions=0)
+
+ adjustment = gtk.Adjustment(value=800, lower=0, upper=1600, step_incr=1, page_incr=100, page_size=0)
+ self.widget['size_x'].set_adjustment(adjustment)
+ adjustment.connect('value_changed', self.spin_val_changed, 'size_x')
+ self.widget['size_x'].show()
+ _size_table.attach(self.widget['size_x'],
+ 0, 1, 0, 1, xoptions=0, yoptions=0)
+ adjustment = gtk.Adjustment(value=800, lower=0, upper=1600, step_incr=1, page_incr=100, page_size=0)
+ self.widget['size_y'].set_adjustment(adjustment)
+ adjustment.connect('value_changed', self.spin_val_changed, 'size_y')
+ self.widget['size_y'].show()
+ _size_table.attach(self.widget['size_y'],
+ 0, 1, 1, 2, xoptions=0, yoptions=0)
+ _size_table.show()
+ table[1].attach(_size_table, 1, 2, 6, 7)
+ table[0].attach(table[1], 0, 2, 1, 2)
+
+ self.dialog.vbox.pack_start(table[0], True, True, 0)
+ def spin_val_changed(self, event, wtype):
+ """
+ Catch the spin val changed.
+ """
+ adjustment = self.widget[wtype].get_adjustment()
+ adjustment.set_upper(event.get_value() + 800)
+ def change_folder(self, widget):
+ """
+ Interface to change the export folder.
+ """
+
+ chooser = gtk.FileChooserDialog(
+ title='Choose folder',
+ action=gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
+ buttons=(gtk.STOCK_CANCEL,
+ gtk.RESPONSE_CANCEL,
+ gtk.STOCK_OPEN,
+ gtk.RESPONSE_OK)
+ )
+ chooser.set_select_multiple(False)
+ chooser.set_default_response(gtk.RESPONSE_OK)
+ chooser.set_local_only(False)
+ chooser.set_filename(self.file['folder'])
+ response = chooser.run()
+ # Analyse the answer
+ if response == gtk.RESPONSE_OK:
+ self.file['folder'] = chooser.get_filenames()[0]
+ self.widget['but_folder'].set_label(self.file['folder'])
+ elif response == gtk.RESPONSE_CANCEL:
+ chooser.destroy()
+ return
+ else:
+ return
+ chooser.destroy()
+ def change_title(self, widget=None):
+ """
+ Change the title of the plot.
+ """
+ self.__update_title()
+ self.plot()
+ def __update_title(self):
+ title = self.generate_text(self.widget['entry_title'].get_text())
+ self.plot_array.title = title
+ def change_filename(self, widget):
+ auto = widget.get_active()
+
+ def change_cmap(self, widget):
+ """
+ Modify the colormap of the plot. Displays or not the colorscale
+ depending on the state of the checkbutton.
+ """
+
+ self.plot_array.cmap = self.widget['combo_cmap'].get_active_text()
+ self.plot_array.color_scale['colorbar'] = \
+ self.widget['check_cmap'].get_active()
+ self.plot()
+ def change_axis_label(self, widget):
+ """
+ Modify the axis label.
+ """
+ self.plot_array.scale['show'] = self.widget['check_axis'].get_active()
+ self.plot()
+ def change_array(self, array=None):
+ """
+ Modify the array to display.
+ """
+ if type(array) == str:
+ key = array
+ elif type(array) == gtk.ComboBox:
+ key = array.get_active_text()
+ self.array['plot'] = self.array['dict'][key]
+ self.array['type'] = key
+ self._init_scales()
+ self.plot()
+ def toggle_interp(self, widget):
+ """
+ Toogle between interpolate or not.
+ """
+ self.array['do_interp'] = widget.get_active()
+ if self.array['do_interp']:
+ self.widget['scale_interp'].show()
+ self.widget['label_scale_interp'].show()
+ else:
+ self.widget['scale_interp'].hide()
+ self.widget['label_scale_interp'].hide()
+ self.plot()
+
+ def plot(self):
+ """
+ Plot the result.
+ """
+ if self.array['plot'] is not None:
+ if self.array['plot'].ndim == 3:
+ data = self.array['plot'][:,:,0]
+ else:
+ data = self.array['plot']
+ if self.array['do_interp']:
+ data = compute_interp(data, self.array['interp_max'])
+ self.__update_title()
+ self.plot_array.plot(data,
+ scan_size=self.array['size'])
+ self.plot_array.vmin = self.array['scale'][0]
+ self.plot_array.vmax = self.array['scale'][1]
+ self.show()
+ def show(self):
+ """
+ Show the result in the window.
+ """
+ labels = self.widget['check_cmap'].get_active() + \
+ self.widget['check_axis'].get_active() + \
+ len(self.widget['entry_title'].get_text())
+ if labels:
+ self.plot_array.figure.subplots_adjust(left=0.5, bottom=0.5)
+ else:
+ self.plot_array.figure.subplots_adjust(left=0, bottom=0)
+ self.canvas.draw_idle()
+ def run(self):
+ """
+ Runs the dialog.
+ """
+ response = self.dialog.run()
+ self.file['name'] = self.widget['entry_filename'].get_text()
+ if response == gtk.RESPONSE_OK:
+ # Runs the code if button OK is clicked
+ [name, ext] = os.path.splitext(self.file['name'])
+ if not len(ext):
+ ext = '.png'
+ self.export_map(name, ext)
+ self.destroy()
+ def export_map(self, name, ext):
+ """
+ Export the maps.
+ """
+ # Get the size of the image.
+ width = self.widget['size_x'].get_value()
+ height = self.widget['size_y'].get_value()
+ self.plot_array.set_size(width, height, deep=True)
+ # Get the displayed item.
+ if len(self.array['plot'].shape) == 3:
+ # block the min and max
+ self.plot_array.vmin = self.array['scale'][0]
+ self.plot_array.vmax = self.array['scale'][1]
+ for index in range(self.array['plot'].shape[2]):
+ self.array['indice'] = index
+ self.__update_title()
+ arr_to_export = self.array['plot'][:, :, index]
+ if self.array['do_interp']:
+ arr_to_export = compute_interp(arr_to_export, self.array['interp_max'])
+ self.plot_array.plot(arr_to_export)
+ newname = self.generate_text(name)
+ f_name = newname + ext
+ self.plot_array.save(os.path.join(self.file['folder'], f_name))
+ else:
+ newname = self.generate_text(name)
+ f_name = newname + ext
+ self.plot_array.plot(self.array['plot'])
+ self.plot_array.save(os.path.join(self.file['folder'], f_name))
+ def generate_text(self, pattern):
+ """
+ Generate the filename from the pattern.
+
+ %d means depth
+ %n means number
+ %f means the filename
+ %t means the type
+ """
+
+ while pattern.find('%d') is not -1:
+ indice = pattern.find('%d')
+ part_1 = pattern[:indice]
+ part_2 = pattern[indice+2:]
+ pattern = part_1 + str(self.array['indice']*self.array['depth']) + part_2
+ while pattern.find('%n') is not -1:
+ indice = pattern.find('%n')
+ part_1 = pattern[:indice]
+ part_2 = pattern[indice+2:]
+ pattern = part_1 + str(self.array['indice']) + part_2
+ while pattern.find('%f') is not -1:
+ indice = pattern.find('%f')
+ part_1 = pattern[:indice]
+ part_2 = pattern[indice+2:]
+ pattern = part_1 + self.file['fv_name'] + part_2
+ while pattern.find('%t') is not -1:
+ indice = pattern.find('%t')
+ part_1 = pattern[:indice]
+ part_2 = pattern[indice+2:]
+ pattern = part_1 + self.array['type'] + part_2
+ return pattern
+ def destroy(self):
+ """
+ Destroy the window.
+ """
+ self.dialog.destroy()
+def compute_interp(array, interp_max):
+ try:
+ _inf = array.max()
+ _array = array.filled(_inf)
+ except AttributeError:
+ _array = array
+ x, y = array.shape
+ x = complex(0, x)
+ y = complex(0, y)
+ coords = numpy.mgrid[
+ 0 : array.shape[0] - 1 : x*10,
+ 0 : array.shape[1] - 1 : y*10]
+ interp_arr = ndimage.map_coordinates(
+ _array, coords)
+ interp_arr = numpy.ma.array(interp_arr,
+ mask=interp_arr>=interp_max)
+ return interp_arr
+if __name__ == '__main__':
+ import numpy
+ TEST = {'Stiffness' : numpy.random.randn(10, 10, 5),
+ 'Piezo' : numpy.random.randn(10,10),
+ 'masked' : numpy.ma.array(numpy.random.randn(20,20),
+ mask=numpy.random.randn(20,20)>0.5,
+ fill_value=numpy.nan)
+ }
+ DISPLAY_IT = MapDisplay(TEST, depth=50, scan_size=2000)
+ DISPLAY_IT.generate_text('Deep %d is now %d')
+ DISPLAY_IT.run()
diff --git a/openfovea/dialog_flatten.py b/openfovea/dialog_flatten.py
new file mode 100644
index 0000000..a95b6a6
--- /dev/null
+++ b/openfovea/dialog_flatten.py
@@ -0,0 +1,93 @@
+#!/usr/bin/env python
+# -*- coding: UTF-8 -*-import pygtk
+"""
+ The gui to flatten the images.
+"""
+
+import gtk
+import pygtk
+# for script installation
+from pkg_resources import resource_filename
+##############
+## Graphic library ##
+##############
+try:
+ matplotlib.__version__
+except NameError:
+ import matplotlib
+ matplotlib.use('Agg', warn=False)
+from matplotlib.figure import Figure
+from matplotlib.backends.backend_gtkagg import FigureCanvasGTKAgg as FigureCanvas
+from matplotlib.backends.backend_gtkagg import NavigationToolbar2GTKAgg as NavigationToolbar
+
+from fovea_toolbox import misc
+
+class FlattenDialog():
+ def __init__(self, array, _glade_filename=None):
+
+ self.array = [array, misc.flatten(array)]
+
+ builder = gtk.Builder()
+ if _glade_filename == None:
+ try:
+ _glade_filename = resource_filename(
+ 'openfovea',
+ 'glade/dialog_flatten.glade')
+ except NotImplementedError:
+ _glade_filename = 'openfovea/glade/dialog_flatten.glade'
+ builder.add_from_file(_glade_filename)
+ self.window = builder.get_object('FlattenDialog')
+ self.widget = {
+ 'button_apply' : builder.get_object('button_apply'),
+ 'button_cancel' : builder.get_object('button_cancel'),
+ 'plot_before_flatten' : builder.get_object('view_before_flatten'),
+ 'plot_after_flatten' : builder.get_object('view_after_flatten'),
+ }
+ # The plots
+ self.flatten = {'figure' : None,
+ 'axis' : None,
+ 'canvas' : None}
+
+ self.flatten['figure'] = [
+ Figure(figsize=(100, 100), dpi=75, facecolor=(0.93,0.92,0.9,1)),
+ Figure(figsize=(100, 100), dpi=75, facecolor=(0.93,0.92,0.9,1))
+ ]
+ self.flatten['axis'] = [
+ self.flatten['figure'][0].add_subplot(111),
+ self.flatten['figure'][1].add_subplot(111)
+ ]
+ self.flatten['canvas'] = [
+ FigureCanvas(self.flatten['figure'][0]), # a gtk.DrawingArea
+ FigureCanvas(self.flatten['figure'][1]) # a gtk.DrawingArea
+ ]
+ self.flatten['canvas'][0].show()
+ self.flatten['canvas'][1].show()
+ self.widget['plot_before_flatten'].pack_start(self.flatten['canvas'][0], True, True)
+ self.widget['plot_after_flatten'].pack_start(self.flatten['canvas'][1], True, True)
+ self.plot()
+ def plot(self):
+ #vmin = min([self.array[0].min(), self.array[1].min()])
+ #vmax = max([self.array[0].max(), self.array[1].max()])
+ self.flatten['axis'][0].pcolormesh(self.array[0].transpose(), cmap='copper')
+ self.flatten['axis'][0].axis([0, self.array[0].shape[0], 0, self.array[0].shape[0]])
+ self.flatten['axis'][0].set_xticks([])
+ self.flatten['axis'][0].set_yticks([])
+ self.flatten['axis'][1].pcolormesh(self.array[1].transpose(), cmap='copper')
+ self.flatten['axis'][1].axis([0, self.array[1].shape[0], 0, self.array[1].shape[0]])
+ self.flatten['axis'][1].set_xticks([])
+ self.flatten['axis'][1].set_yticks([])
+ def run(self):
+ self.window.show()
+ response = self.window.run()
+ if response == 1:
+ return self.array[1]
+ else:
+ return self.array[0]
+ def destroy(self):
+ self.window.destroy()
+if __name__ == '__main__':
+ import numpy
+ test = main_win(numpy.array([range(10) for i in range(10)]),
+ _glade_filename='glade/dialog_flatten.glade')
+ toto = test.run()
+ print toto
diff --git a/openfovea/dialog_mask_properties.py b/openfovea/dialog_mask_properties.py
new file mode 100644
index 0000000..270c9f6
--- /dev/null
+++ b/openfovea/dialog_mask_properties.py
@@ -0,0 +1,294 @@
+"""
+ This is the module to display the dialog export map.
+"""
+
+
+import pygtk
+pygtk.require('2.0')
+import gtk
+import os
+#from pkg_resources import resource_filename # pylint: disable-msg=E0611
+
+
+from matplotlib.backends.backend_gtkagg import FigureCanvasGTKAgg as \
+ FigureCanvas
+import numpy
+#from scipy import interpolate, ndimage
+import plot_generic
+
+# Graphic library
+
+import matplotlib
+matplotlib.use('Agg')
+from matplotlib.figure import Figure
+from matplotlib.axes import Subplot
+from matplotlib.backends.backend_gtkagg import FigureCanvasGTK
+from matplotlib import cm # colormap
+from matplotlib import pylab
+#pylab.hold(False) # This will avoid memory leak
+
+class BoolUnit(object):
+ """
+ Looks like this :
+
+ ::
+ +------------------------------------------+
+ | . |
+ | . +--------------------+ |
+ | . | | |
+ | . | | |
+ | +-------------+. | | |
+ | | Select Map |. | Display Map | |
+ | +-------------+. | | |
+ |................. | | |
+ | +-------------+. | | |
+ | | 0 Invert |..+--------------------+..|
+ | +-------------+. | Numerical value | |
+ | . +--------------------+ |
+ |................:.........................|
+ | +-------------+ |
+ | | Add or bool | |
+ | +-------------+ |
+ +------------------------------------------+
+ """
+ def __init__(self, fvfile):
+ self.fvfile = fvfile
+ self.array = {'data' : None, # Store the data array used to generate mask.
+ 'mask' : None, # Store the mask itself.
+ 'invert' : False} # Invert the mask (or not).
+ # Creates the GUI.
+ self.map_select = gtk.ComboBox()
+ self.map_display = gtk.VBox()
+ self.map_display.set_size_request(250,250)
+ self.add_bool = gtk.ComboBox()
+ self.num_val = gtk.Entry()
+ self.invert = gtk.CheckButton(label='Invert')
+ self.box = gtk.VBox()
+ self.box.show()
+ map_box = gtk.HBox()
+ map_box.show()
+ __box1 = gtk.VBox() # Contains the combobox for mapselect and the invert toggle
+ __box1.show()
+
+ viz_box = gtk.VBox()
+ viz_box.show()
+
+ self.map_select.show()
+ self.add_bool.show()
+ self.num_val.show()
+ self.invert.show()
+
+ viz_box.pack_start(self.map_display)#, expand=False)
+ viz_box.pack_start(self.num_val, expand=False)
+ __box1.pack_start(self.map_select, expand=False)
+ __box1.pack_start(self.invert, expand=False)
+ map_box.pack_start(__box1, expand=False)
+ #ap_box.pack_start(self.map_select, expand=False)
+ map_box.pack_start(viz_box, expand=True)
+ self.box.pack_start(map_box, expand=True)
+ self.box.pack_start(self.add_bool, expand=False)
+
+ # Create graphics
+ self.figure = Figure(figsize=(100, 100), dpi=75)
+ self.axis = self.figure.add_subplot(111, axisbg='red')
+ self.canvas = FigureCanvas(self.figure)
+ self.canvas.show()
+ self.map_display.pack_start(self.canvas, True, True)
+ self.map_display.show()
+ self.canvas.mpl_connect('button_press_event', self.on_array_click)
+
+ # Connect buttons.
+ self.map_select.connect('changed', self.change_array)
+ self.num_val.connect('activate', self.on_num_val_changed)
+ self.invert.connect('toggled', self.on_invert_toggled)
+ # bool_list has to be connected in the caller class.
+ # Fill the list.
+ list_arr = gtk.ListStore(str)
+ __list = list()
+ for key in self.fvfile.get_array('keys'):
+ if type(self.fvfile.get_array(key)) in [numpy.ndarray, numpy.ma.core.MaskedArray]:
+ list_arr.append([key])
+ __list.append(key)
+ self.map_select.set_model(list_arr)
+ __cell = gtk.CellRendererText()
+ self.map_select.pack_start(__cell, True)
+ self.map_select.add_attribute(__cell, 'text', 0)
+ self.map_select.set_active(__list.index('Piezo'))
+
+ bool_list = gtk.ListStore(str)
+ [bool_list.append([key]) for key in ['Add', 'AND','OR','XOR']]
+ self.add_bool.set_model(bool_list)
+ __cell = gtk.CellRendererText()
+ self.add_bool.pack_start(__cell, True)
+ self.add_bool.add_attribute(__cell, 'text', 0)
+ self.add_bool.set_active(0)
+
+ def on_invert_toggled(self, event):
+ self.array['invert'] = event.get_active()
+ mask = self.array['data'].mask
+ mask = numpy.logical_not(mask)
+ self.apply_mask(mask)
+ self.plot()
+ def on_num_val_changed(self, event):
+ height = float(event.get_text())
+ self.generate_mask(height)
+ self.plot()
+
+ def on_array_click(self, event):
+ try:
+ height = self.array['data'].data[event.ydata, event.xdata]
+ except TypeError:
+ height = self.array['data'][event.ydata, event.xdata]
+ self.generate_mask(height)
+ self.plot()
+ self.num_val.set_text(str(height))
+
+ def generate_mask(self, height):
+ if type(self.array['data']) == numpy.ndarray:
+ mask = self.array['data'] < height
+ else:
+ mask = self.array['data'].data < height
+ if self.array['invert']:
+ mask = numpy.logical_not(mask)
+ self.apply_mask(mask)
+
+ def apply_mask(self, mask):
+ try:
+ self.array['data'].mask = mask
+ except AttributeError:
+ self.array['data'] = numpy.ma.masked_array(self.array['data'], mask=mask)
+
+ def change_array(self, event):
+ self.array['name'] = event.get_active_text()
+ self.array['data'] = self.fvfile.get_array(self.array['name'], raw=True)
+ if self.array['data'].ndim > 2:
+ self.array['data'] = self.array['data'][:,:,0]
+ self.plot()
+
+ def plot(self):
+ self.axis.pcolor(self.array['data'], cmap=cm.copper)
+ self.axis.axis([0, self.array['data'].shape[0],
+ 0, self.array['data'].shape[1]])
+ self.axis.set_aspect('equal')
+ self.canvas.draw_idle()
+
+ def get_op(self):
+ return self.add_bool.get_active_text()
+
+ def get_mask(self):
+ try:
+ return self.array['data'].mask
+ except:
+ return numpy.ones_like(self.array['data'], dtype=bool)
+ def destroy(self):
+ self.box.destroy()
+ return
+
+class MaskProperties(object):
+ """
+
+ """
+ def __init__(self, FVFile):
+ self.fvfile = FVFile
+ self.mask = None
+ self.dialog = gtk.Dialog(title='Mask Propreties',
+ buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK,
+ gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
+ self.dialog.set_default_size(700, 350)
+
+ self.bool_win = list() # This contains the window where each boolean operators are stored
+ # Create the first one.
+ self.box = {'left' : gtk.HBox(),
+ 'bool_view' : gtk.VBox(homogeneous=True),
+ 'center' : gtk.HBox()}
+ self.bool_win.append(BoolUnit(self.fvfile))
+ self.bool_win[0].add_bool.connect('changed', self.add_bool)
+ self.bool_win[0].canvas.mpl_connect('button_press_event', self.disp_result)
+ self.box['bool_view'].pack_start(self.bool_win[0].box)
+ self.scroll_window = gtk.ScrolledWindow()
+ self.scroll_window.set_border_width(10)
+ self.scroll_window.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
+ self.scroll_window.show()
+ self.scroll_window.add_with_viewport(self.box['bool_view'])
+ self.box['left'].pack_start(self.scroll_window, True, True, 0)
+ self.box['center'].pack_start(self.box['left'])#, True, True, 0)
+ self.box['center'].show()
+ self.box['left'].show()
+ self.box['bool_view'].show()
+ self.dialog.vbox.pack_start(self.box['center'], True, True, 0)
+
+ # Create graphics
+ self.figure = Figure(figsize=(200, 200), dpi=75)
+ self.axis = self.figure.add_subplot(111, axisbg='red')
+ self.canvas = FigureCanvas(self.figure)
+ self.canvas.show()
+ self.box['center'].pack_start(self.canvas)#, True, True, 0)
+ self.box['center'].set_homogeneous(True)
+
+ def add_bool(self, event):
+ for bool_unit in self.bool_win:
+ if bool_unit.add_bool == event:
+ index = self.bool_win.index(bool_unit)
+ selected = event.get_active_text()
+ print selected
+ if selected == 'Add':
+ self.bool_win[index].destroy()
+ self.bool_win.pop(index)
+ return
+ if index+1 == len(self.bool_win):
+ self.bool_win.append(BoolUnit(self.fvfile))
+ self.box['bool_view'].pack_start(self.bool_win[-1].box)
+ self.bool_win[-1].add_bool.connect('changed', self.add_bool)
+ #self.compute_result()
+ self.bool_win[-1].canvas.mpl_connect('button_press_event', self.disp_result)
+ self.disp_result()
+
+ def compute_result(self):
+ bool_list = [item.get_op() for item in self.bool_win]
+ mask_list = [item.get_mask() for item in self.bool_win]
+ result = numpy.logical_not(mask_list[0])
+ for item in range(len(bool_list)-1):
+ if bool_list[item] == 'AND':
+ result = numpy.logical_and(result, numpy.logical_not(mask_list[item+1]))
+ elif bool_list[item] == 'OR':
+ result = numpy.logical_or(result, numpy.logical_not(mask_list[item+1]))
+ elif bool_list[item] == 'XOR':
+ result = numpy.logical_xor(result, numpy.logical_not(mask_list[item+1]))
+ self.mask = numpy.logical_not(result)
+
+ def disp_result(self, event=None):
+ self.compute_result()
+ self.axis.pcolor(self.mask, cmap=cm.gray)
+ self.axis.axis([0, self.mask.shape[0],
+ 0, self.mask.shape[1]])
+ self.axis.set_aspect('equal')
+ self.canvas.draw_idle()
+
+ def run(self):
+ """
+ Runs the dialog.
+ """
+ response = self.dialog.run()
+ if response == gtk.RESPONSE_OK:
+ # Runs the code if button OK is clicked
+ self.fvfile.set_array('Mask', self.mask)
+ self.fvfile.set_switch('mask', True)
+ return True
+ else:
+ self.fvfile.set_switch('mask', False)
+ return False
+ def destroy(self):
+ """
+ Destroy the window.
+ """
+ self.dialog.destroy()
+ return False
+
+if __name__ == '__main__':
+ import numpy
+ import classes
+ FVFOLDER = classes.FVFolder()
+ FVFOLDER.load_aex('../docs/data/Stiffness_and_Event_computed.aex')
+ DISPLAY_IT = MaskProperties(FVFOLDER.file['list'][1])
+ answer = DISPLAY_IT.run()
+ print answer
diff --git a/openfovea/fovea_toolbox/__init__.py b/openfovea/fovea_toolbox/__init__.py
new file mode 100644
index 0000000..01678e9
--- /dev/null
+++ b/openfovea/fovea_toolbox/__init__.py
@@ -0,0 +1 @@
+__all__ = ['curve', 'file']
\ No newline at end of file
diff --git a/openfovea/fovea_toolbox/curve.py b/openfovea/fovea_toolbox/curve.py
new file mode 100644
index 0000000..ebde392
--- /dev/null
+++ b/openfovea/fovea_toolbox/curve.py
@@ -0,0 +1,1174 @@
+#! /usr/bin/env python
+#-*- coding: iso-8859-1 -*-
+'''
+The curve module contains function that are usefull for curve data processing
+'''
+
+## Fonctions pour Open Fovea.
+#import pdb
+import csv
+#import warnings
+#warnings.simplefilter("ignore")
+#warnings.filterwarnings('error', category=Warning)
+
+import numpy as num
+#from scipy.optimize import leastsq
+from scipy import odr
+from copy import deepcopy
+CURVE_TEST = '../../docs/data/bugged_curve/curve_bug_1.csv'
+CURVE_EVENT = ['../../docs/data/curves/event_1.csv',
+ '../../docs/data/curves/event_2.csv',
+ '../../docs/data/curves/event_3.csv',
+ '../../docs/data/curves/WLC.csv']
+FIGURE_FOLDER = 'tests/'
+
+
+def open_csv_curve(file_name):
+ """
+ Open the curve in csv file.
+ """
+ fid = open(file_name)
+ temp_file = csv.reader(fid)
+ # The two first lines are descripion.
+ temp_file.next()
+ temp_file.next()
+ # Get the datas
+ arr_list = [item for item in temp_file]
+ curves = [num.asarray(
+ [item[place] for item in arr_list if len(item[place])],
+ dtype=num.float)
+ for place in range(len(arr_list[0]))]
+ return curves
+
+
+def find_poc(curve_x, curve_y, threshold=1, method='curve_fit',
+ limit_slide=False, len_slice=0.3):
+ """
+ Finds the point of contact of a force curve.
+
+ >>> curve = open_csv_curve(CURVE_TEST)
+ >>> result = find_poc(curve[0], curve[1], threshold=1, method='curve_fit')
+ >>> result.keys()
+ ['deriv', 'Poly Fit', 'Slice', 'PoC', 'Error']
+
+ curve_x and curve_y = array
+
+ threshold : integer
+ how much the curve has to diverge from the fit to be a point
+ of contact. (threshold * noise)
+
+ method : string
+ Defines the method used to detect the point of contact.
+
+ * 'deriv' (default) : uses the derivative method.
+ When the derivative of the curve is zero (then
+ it's linear), the point of contact is detected.
+ The advantage of this method is to get rid of
+ irregularities observed in the off-contact part
+ of the curve.
+
+ * 'curve_fit' : uses the curve fit method.
+ A linear fit is computed from the off-contact part of
+ the curve. The point of contact is then detected at
+ the point when the curve deviates from this linear
+ fit. The advantage of this method is that it does not
+ depend on the shape of the on contact part of the
+ curve.
+ """
+ if len(curve_x) <= 2:
+ return None
+ if curve_x == None or curve_y == None:
+ return None
+ if method == 'deriv':
+ return find_poc_deriv(curve_x, curve_y, threshold, limit_slide,
+ len_slice)
+ elif method == 'curve_fit':
+ try:
+ result = find_poc_curve_fit(curve_x, curve_y, threshold,
+ limit_slide, len_slice)
+ except TypeError as detail:
+ if 'expected non-empty vector' in detail.message:
+ # This error occurs when a too small curve is given.
+ return None
+ else:
+ raise detail
+ else:
+ result['deriv'] = None
+ return result
+
+
+def platgliti(curve):
+ """
+ Return the slice of the first non flat part. This is usefull when a
+ curve is complemented with the identical values at the end.
+ """
+
+ indice = 0
+ prev_val = curve[indice]
+ indice += 1
+ next_val = curve[indice]
+ while prev_val == next_val and indice + 1 < len(curve):
+ indice += 1
+ next_val, prev_val = curve[indice], next_val
+ return indice
+
+
+def true_data_indice(curve_x):
+ """
+ Return the true length curve. It removes all similar values that are at
+ the beginning or at the end of the curve.
+ """
+ new_curve_x = curve_x[:-1] - curve_x[1:]
+ non_zero = new_curve_x.nonzero()[0]
+ start_indice = non_zero[0]
+ stop_indice = non_zero[-1] + 2
+
+ return start_indice, stop_indice
+
+
+def find_poc_curve_fit(curve_x, curve_y, threshold, limit_slide=False,
+ len_slice=0.3):
+ '''
+ Finds the point of contact, using the method of curve fit.
+
+ This methods makes a first order fits the off contact part of the curve.
+ The point of contact is then detected when the force curve deviates from
+ the linear fit. The deviation is related to the error measured during the
+ fit.
+ '''
+ loop = {'slope_negative': 1,
+ 'step': 0}
+ reversed_curve = 0
+ if curve_y[0] > curve_y[-1]:
+ reversed_curve = 1
+ curve_y = curve_y[::-1]
+ curve_x = curve_x[::-1]
+ # Linear fit of the segment slice
+ _ind = platgliti(curve_y)
+ _true_indice = true_data_indice(curve_x)
+ #_true_indice = (0, len(curve_x))
+ #print _true_indice
+ #print _ind
+ _length = _true_indice[1] - _true_indice[0]
+
+ seg_slice = slice(_ind, int(_length * len_slice + _ind))
+ # the fit is :
+ # p(x) = coeff[0]*x + coeff[1]
+ coeff = num.polyfit(curve_x[seg_slice],
+ curve_y[seg_slice], 1, full=True)[0]
+ #
+ # We make a loop by testing the slope of the fit.
+ # If this slope is negative, we look a little bit forward in the curve
+ # until the slope becomes positive
+ #
+ fit = num.polyfit(curve_x[seg_slice], curve_y[seg_slice], 1, full=True)
+ # Look in the curve slope was positive at the beginning
+ # i.e. :
+ # \
+ # \ _.-
+ # \̣.-`
+ if fit[0][0] >= 0:
+ fall_off = 1
+ else:
+ fall_off = 0
+ while loop['slope_negative'] and (seg_slice.stop + 1 < len(curve_x)):
+ new_seg_slice = slice(seg_slice.start + 2, seg_slice.stop + 2)
+ fit = num.polyfit(curve_x[new_seg_slice], curve_y[new_seg_slice],
+ 1, full=True)
+ if coeff[0] < fit[0][0] and fit[0][0] <= 0:
+ loop['step'] = 0
+ if limit_slide and (seg_slice.stop + 1 >= len(curve_x) / 2):
+ # The slice is going farer than the half of the curve. Then,
+ # if we choosed to limit the sliding, stop at this step.
+ loop['slope_negative'] = 0
+ if seg_slice.stop > len(curve_x):
+ # The slice is going outside the curve, then stop anyway.
+ loop['slope_negative'] = 0
+ elif fall_off and fit[0][0] >= 0:
+ loop['step'] = 0
+ elif fall_off and fit[0][0] < 0:
+ # we directly stop here and go back
+ loop['slope_negative'] = 0
+ [new_seg_slice, fit] = go_back(curve_x, curve_y, seg_slice)
+# new_seg_slice = slice(seg_slice.start - 4, seg_slice.stop - 4)
+# fit = num.polyfit(curve_x[new_seg_slice], curve_y[new_seg_slice],
+# 1, full = True)
+ else:
+ loop['step'] = loop['step'] + 1
+ coeff = fit[0]
+ if loop['step'] > 2:
+ coeff[0] = fit[0][0]
+ coeff[1] = fit[0][1]
+ loop['slope_negative'] = 0
+ seg_slice = new_seg_slice
+ #
+ # We take the selected slice to detect the point of contact
+ #
+ error = (num.sqrt(fit[1]) / 5) * threshold
+ # Find when the curve goes away from the fit
+ #pdb.set_trace()
+ if not len(error):
+ error = num.array([0])
+ _ind = num.polyval([coeff[0], coeff[1] + error], curve_x)
+ point_of_contact = 0
+
+ for item_nbr in xrange(len(curve_y)):
+ if _ind[item_nbr] >= curve_y[item_nbr]:
+ point_of_contact = item_nbr
+
+ if reversed_curve:
+ curve_y = curve_y[::-1]
+ point_of_contact = len(curve_y) - point_of_contact - 1
+ seg_slice = slice(len(curve_y) - seg_slice.stop,
+ len(curve_y) - seg_slice.start)
+ return {'PoC': point_of_contact,
+ 'Poly Fit': [coeff[0], coeff[1]],
+ 'Error': error,
+ 'Slice': seg_slice}
+
+
+def go_back(curve_x, curve_y, seg_slice):
+ """
+ Go back in the fit to find the best place to find poc.
+ """
+ fit = num.polyfit(curve_x[seg_slice], curve_y[seg_slice], 1,
+ full=True)
+ better = 0
+ while not better:
+ new_seg_slice = slice(seg_slice.start - 2, seg_slice.stop - 2)
+ new_fit = num.polyfit(curve_x[seg_slice], curve_y[seg_slice], 1,
+ full=True)
+ if new_fit[1] > fit[1]:
+ better = 1
+ else:
+ seg_slice = new_seg_slice
+ fit = new_fit
+ if seg_slice.start <= 2:
+ better = 1
+ return seg_slice, fit
+
+
+def compute_derivative(curve_x, curve_y, delta=10):
+ '''
+ Computes the derivative of a curve
+ '''
+ _my = num.zeros_like(curve_y)
+ _mx = num.zeros_like(curve_y)
+ for item in range(len(curve_y)):
+ t_slice = slice(item, item + delta)
+ _my[item] = curve_y[t_slice].mean()
+ _mx[item] = curve_x[t_slice].mean()
+ d_y = (_my[1:] - _my[:-1])
+ return[curve_x[:-1], d_y]
+
+
+def find_poc_deriv(curve_x, curve_y, threshold=1, limit_slide=False,
+ len_slice=0.3):
+ '''
+ Finds the point of contact, using the derivative of the curve.
+ '''
+ seg = {'slice': None, # Contains the slice of the segment
+ 'coef': None} # Contains the coefficient of the fit
+ # We slips on the curve. Then, we define the same for the next portion of
+ # curve
+ next_seg = {'slice': None, # Contains the slice of the segment
+ 'coef': None} # Contains the coefficient of the fit
+
+ deriv = compute_derivative(curve_x, curve_y, delta=10)
+ # define the slice to compute the fit
+ seg['slice'] = slice(int(len(deriv[1]) - len(deriv[1]) * len_slice) - 1,
+ len(deriv[1]) - 1)
+ # and compute the fit...
+ seg['coef'] = num.polyfit(deriv[0][seg['slice']], deriv[1][seg['slice']],
+ 1, full=True)
+ # make the slice slips on the curve, untill we find the one with the lower
+ # error.
+ # Indeed, some curves begin with a big noise that prevent a good point of
+ # contact detection.
+ loop = {'ok': 0,
+ 'i': 0,
+ 'step': 0}
+ while not loop['ok']:
+ next_seg['slice'] = slice(seg['slice'].start - loop['step'],
+ seg['slice'].stop - loop['step'])
+ next_seg['coef'] = num.polyfit(deriv[0][next_seg['slice']],
+ deriv[1][next_seg['slice']],
+ 1, full=True)
+ if seg['coef'][1] <= next_seg['coef'][1]:
+ loop['ok'] = 1
+ #else:
+ # i = 0
+ if limit_slide and (next_seg['slice'].start < len(deriv[1]) / 2):
+ loop['ok'] = 1
+ if next_seg['slice'].start < loop['step']:
+ loop['ok'] = 1
+ seg['coef'] = next_seg['coef']
+ loop['i'] += 1
+ if loop['i'] > 10:
+ seg['slice'] = next_seg['slice']
+ loop['ok'] = 1
+ # Now, we apply the threshold on the error and find the first point that
+ # deviates from the fit (same idea as the old curve_fit algorythm)
+ error = seg['coef'][1] * threshold
+ seg['coef'] = seg['coef'][0]
+ polynom = num.polyval(seg['coef'], deriv[0]) - error
+ on_contact = (deriv[1] <= polynom) - 1 # True are the points that are
+ # close to the fit
+ indice = num.asarray(num.nonzero(on_contact)[0])
+ # Sometimes, in the on-contact part of the curve, the tips slips on the
+ # surface that is indented. The resulting curve looks like this :
+ # \
+ # \ /\
+ # `v \
+ # "_
+ # `¬___________________
+ # | |
+ # V V
+ # 000100000011111111111111111111 Resulting on_contact
+ # 123456789012345678901234567890
+ # [4, 11, 12, 13, 14, 15, 16, 17, ...] Resulting indice
+ # [7, 1, 1, 1, 1, 1, 1, ...] Resulting diff_indice
+ #
+ # With the diff_indice, we can then detect such holes. I arbitrary choose
+ # 40. It could be nice to set this as a parameter. Will see later.
+ diff_indice = indice[1:] - indice[:-1]
+ hole = num.nonzero(diff_indice > 40)[0]
+ if len(hole):
+ i_poc = hole[-0] + 1
+ else:
+ i_poc = 0
+ non_zero_list = num.nonzero(on_contact)[0]
+ if len(non_zero_list):
+ point_of_contact = num.nonzero(on_contact)[0][i_poc]
+ else:
+ point_of_contact = 0
+ return {'PoC': point_of_contact,
+ 'Poly Fit': [0, seg['coef'][0]],
+ 'Error': error,
+ 'Slice': seg['slice'],
+ 'deriv': deriv}
+
+
+def compute_indentation(curve_x, curve_y,
+ deflection_sensitivity, spring_constant):
+ '''
+ Compute the indentation curve.
+
+ * Parameters :
+ curve_x : 1d array
+ Values of the x coordinate.
+ curve_y : 1d array
+ Values of the y coordinates.
+ deflection_sensitivity : float
+ Parameter of the glass indentation.
+ spring_constant : float
+ sping constant of the cantilever.
+
+ * Returns :
+ indentation : 1d array
+ Value of the x coordinate.
+ force : 1d array
+ Value of the y coordinate.
+ '''
+ #deflection_sensitivity=1
+ if curve_x == None or curve_y == None or len(curve_x) <= 1:
+ return [None, None]
+ indice = true_data_indice(curve_x)
+ length = len(curve_x)
+
+ curve_x = curve_x[indice[0]:indice[1]]
+ curve_y = curve_y[indice[0]:indice[1]]
+ if curve_x[0] < curve_x[-1]:
+ indent_curve = curve_x[::-1]
+ else:
+ indent_curve = curve_x
+
+ indent_curve = indent_curve + num.polyval([deflection_sensitivity, 0],
+ curve_y)
+ force_curve = curve_y * spring_constant
+
+ force_curve = num.r_[force_curve,
+ [force_curve[-1]] * (length - len(force_curve))]
+ indent_curve = num.r_[indent_curve,
+ [indent_curve[-1]] * (length - len(indent_curve))]
+ return [indent_curve, force_curve]
+
+
+def correct_curve_drop(curve_y):
+ '''
+ Corrects the drops that occurs at the beginning of some force-distance
+ curves. This function corrects a bug from Veeco AFM.
+ '''
+ first_non_zero = curve_y.nonzero()[0][1] + 1
+ for indice in range(first_non_zero, len(curve_y)):
+ curve_y[indice] = curve_y[first_non_zero - 1]
+
+
+def segment_curve(curve_x, curve_y, segment_number, segment_deep):
+ """
+ Segmentation of curve.
+
+ Parameters :
+ * curve_x : 1d array
+ Values of the x coordinate.
+ * curve_y : 1d array
+ Values of the y coordinate.
+ * segment_number : int
+ specifies the number of segments to generate
+ -1 : create one segment from the begining to
+ the end of the curve.
+ 0 : create as many segment as possible.
+ >0 : specifies the exact number of segment. If the
+ curve is smaller, less segment is created.
+ * segment_deep : int
+ specifies the deep (curve_x value) of each segment.
+ in case segment_number = -1, this parameter is not
+ considered.
+
+ Returns :
+ * parts_x : 1d array
+ segment_number+1 array containing the curve_x values that
+ correspond to each segment
+ * parts_y : 1d array
+ segment_number+1 array containing the curve_y values that
+ correspond to each segment
+
+ for example : segment n is made of parts_x[n-1],parts_x[n]
+ and parts_y[n-1],parts_y[n]
+ """
+ ## Initialisation des valeurs
+ ##
+ #
+ if not len(curve_y):
+ if segment_number == 0:
+ return [[num.nan], [num.nan]]
+ else:
+ return [num.zeros(segment_number) * num.nan,
+ num.zeros(segment_number) * num.nan]
+ if curve_y[0] > curve_y[-1]:
+ curve_x = curve_x[::-1] # We invert the curve to be more convenient
+ curve_y = curve_y[::-1]
+
+ meta_parts_x = []
+ meta_parts_y = []
+
+ if segment_number == -1:
+ # We create one segment that correspond to the whole curve.
+ meta_parts_y.append(curve_y)
+ meta_parts_x.append(curve_x)
+ return [meta_parts_x, meta_parts_y]
+ elif segment_number == 0:
+ segment_number = num.inf
+ if len(curve_x):
+ curve_size = curve_x.max() - curve_x.min()
+ else:
+ curve_size = 0
+ size_done = segment_deep
+ nbr_selection = 1
+ ## Element zero
+ parts_x = []
+ parts_y = []
+ if curve_size:
+ parts_x.append(curve_x.min())
+ parts_y.append(curve_y[num.nonzero(curve_x == parts_x[0])[0][0]])
+ else:
+ parts_x.append(num.nan)
+ parts_y.append(num.nan)
+ ## From the first to the last segment
+ all_indexes = [0]
+ while curve_size > size_done and nbr_selection <= segment_number:
+ parts_x.append(parts_x[-1] + segment_deep)
+ # Let's find the next point
+ indexes = num.nonzero(curve_x >= parts_x[nbr_selection])[0]
+ indexes = indexes[0]
+ if indexes == 0:
+ indexes = 1
+ all_indexes.append(indexes)
+ # The point stands between index-1 and index. We then make a linear fit
+ # and we'll find the value. Done between index-1 and index+1 'cause it
+ # does _not_ take the last value.
+ polynome = num.polyfit(curve_x[indexes - 1:indexes + 1],
+ curve_y[indexes - 1:indexes + 1],
+ 1)
+ parts_y.append(num.polyval(polynome, parts_x[nbr_selection]))
+ this_slice = slice(all_indexes[nbr_selection - 1],
+ all_indexes[nbr_selection])
+ if parts_x[-2] != curve_x[this_slice.start]:
+ tmp_x = [parts_x[-2]]
+ tmp_y = [parts_y[-2]]
+ else:
+ tmp_x = []
+ tmp_y = []
+ tmp_x.extend(curve_x[this_slice])
+ tmp_x.append(parts_x[-1])
+ #print tmp_x
+ #tmp_y = [parts_x[-1]]
+ tmp_y.extend(curve_y[this_slice])
+ tmp_y.append(parts_y[-1])
+ meta_parts_x.append(tmp_x)
+ meta_parts_y.append(tmp_y)
+ nbr_selection = nbr_selection + 1
+ size_done = size_done + segment_deep
+ if nbr_selection <= segment_number and segment_number is not num.inf:
+ ## adding NAN values
+ while nbr_selection <= segment_number:
+ meta_parts_x.append([num.nan])
+ meta_parts_y.append([num.nan])
+ nbr_selection += 1
+ return [meta_parts_x, meta_parts_y]
+
+
+def compute_stiffness(indent_curve, force_curve, model='Sphere', tip_carac=40,
+ poisson_ratio=0.3, method='Raw'):
+ """
+ Compute : the stiffness of the different depth.
+
+ indent_curve and force_curve : the verctors return by segment_curve.
+
+ model : describe the Hertz model used and can be 'Sphere' or 'Cone'.
+
+ tip_carac : the characteristic of the tip. Radius and Semi-opening angle in
+ radian for Sphere or Cone model respectively.
+ poisson_ratio : the poisson ratio of the indented substrate.
+ Common values : 0.3 for normal cells
+ 0.5 for rubber
+ method : string
+ 'Raw' for a fit of the raw data with the model.
+ 'Linear' for a linearized fit.
+ 'Extrema' for a linearized fit from the extrem points.
+ 'Median' for a computation of the ym from the median point in the
+ segment.
+ """
+ if indent_curve == None or force_curve == None:
+ return num.ones(1) * num.nan
+ ## The indentation and force have to begin from 0...
+ stop = False
+ if not len(indent_curve):
+ stop = True
+ elif type(indent_curve[0]) == list:
+ stop = len(indent_curve[0]) == 1 and num.isnan(indent_curve[0][0])
+ elif type(indent_curve[0]) in [num.float64, float]:
+ stop = num.isnan(indent_curve[0])
+ if stop:
+ return num.ones(len(indent_curve)) * num.nan
+ # Make the point of contact being at zero:
+ zero_point = indent_curve[0][0]
+ indent_curve = [item - zero_point for item in indent_curve]
+ zero_point = force_curve[0][0]
+ force_curve = [item - zero_point for item in force_curve]
+ # Now, we prepare the fit
+ young_modulus = []
+ for indent, force in zip(indent_curve, force_curve):
+ if num.isnan(indent[0]):
+ young_modulus.append(num.nan)
+ elif method == 'Raw':
+ # Choose the initial parameters :
+ p = [100]
+
+ def ym_model(p, indent):
+ if model == 'Sphere':
+ force = 4 / 3. * p[0] / (1 - poisson_ratio ** 2) *\
+ num.sqrt(tip_carac) * indent ** 1.5
+ elif model == 'Cone':
+ force = 2. / num.pi * p[0] / (1 - poisson_ratio ** 2) *\
+ num.tan(tip_carac) * indent ** 2
+ return force
+
+ try:
+ fit_it = odr.ODR(odr.Data(indent, force),
+ odr.Model(ym_model), p)
+ # And we run it
+ out = fit_it.run()
+ # Get the result and rescale it.
+ young_modulus.append(out.beta[0])
+ if out.beta[0] == 100:
+ print indent
+ out.pprint()
+ except IndexError, message:
+ if type(indent) in [float, num.float64] and num.isnan(indent):
+ young_modulus.append(num.nan)
+ else:
+ raise IndexError(message)
+ elif method == 'Linear':
+ # Choose the initial parameters :
+ p = [100]
+
+ def ym_model(p, d_indent):
+ #slope = (force[1:] - force[:-1]) /(indent[1:] - indent[:-1])
+ if model == 'Sphere':
+ d_force = 4 / 3. * p[0] / (1 - poisson_ratio ** 2) *\
+ num.sqrt(tip_carac) * d_indent
+ elif model == 'Cone':
+ d_force = 2. / num.pi * p[0] / (1 - poisson_ratio ** 2) *\
+ num.tan(tip_carac) * d_indent
+ return d_force
+ try:
+ if model == 'Sphere':
+ indent = indent ** (3 / 2.)
+ elif model == 'Cone':
+ indent = indent ** 2
+ d_force = num.diff(force)
+ d_indent = num.diff(indent)
+ fit_it = odr.ODR(odr.Data(d_indent, d_force),
+ odr.Model(ym_model), p)
+ # And we run it
+ out = fit_it.run()
+ # Get the result and rescale it.
+ young_modulus.append(out.beta[0])
+ except IndexError, message:
+ if type(indent) in [float, num.float64] and num.isnan(indent):
+ young_modulus.append(num.nan)
+ else:
+ raise IndexError(message)
+ elif method == 'Extrema':
+ # Modification of the axes ...
+ if model == 'Sphere':
+ indent = indent ** (3 / 2.)
+ elif model == 'Cone':
+ indent = indent ** 2
+ # Compute slopes
+ try:
+ slope = (force[-1] - force[0]) / (indent[-1] - indent[0])
+ except IndexError, message:
+ if type(indent) in [float, num.float64] and num.isnan(indent):
+ young_modulus.append(num.nan)
+ else:
+ raise IndexError(message)
+ finally:
+ ## Compute the young modulus according to the models
+ if model == 'Sphere':
+ young_modulus.append((1 - poisson_ratio ** 2) *
+ slope *
+ 3 / (4 * num.sqrt(tip_carac)))
+ elif model == 'Cone':
+ young_modulus.append((1 - poisson_ratio ** 2) *
+ slope *
+ num.pi / (2 * num.tan(tip_carac)))
+ elif method == 'Median':
+ # Modification of the axes ...
+ if model == 'Sphere':
+ indent = indent ** (3 / 2.)
+ elif model == 'Cone':
+ indent = indent ** 2
+ m_force = num.median(force)
+ m_indent = num.median(indent)
+ if model == 'Sphere':
+ young_modulus.append((1 - poisson_ratio ** 2) *
+ (m_force / m_indent) *
+ 3 / (4 * num.sqrt(tip_carac)))
+ elif model == 'Cone':
+ young_modulus.append((1 - poisson_ratio ** 2) *
+ (m_force / m_indent) *
+ num.pi / (2 * num.tan(tip_carac)))
+ return num.array(young_modulus)
+
+
+def event_find(curve_x, curve_y, deflection_sensitivity, spring_constant,
+ weight=2.5, fit_model=None, poc=0, baseline=None, debug=0):
+ '''
+ This function finds the protein - protein unbinding events from a
+ retraction curve.
+
+ * Parameters :
+ curve_x : 1d array
+ Values of the x coordinate.
+ curve_y : 1d array
+ Values of the y coordinates.
+ deflection_sensitivity : float
+ The deflection sensitivity is used to compute properties of the
+ event.
+ spring_constant : float
+ The property of the cantilever to compute the force of the
+ event.
+ weight : float
+ Threshold to detect the event. Smallest is more sensitive.
+ fit_model : str
+ 'wlc' for worm like chain model
+ 'fjc' for freely jointed chain model
+ poc : int
+ The index of the point of contact in the curve. Used for the
+ fit.
+ baseline : [a, b] -> y = ax + b
+ The indices of the baseline of the off contact part of the
+ curve. Used to compute the fit.
+
+ * Returns :
+ event_list : list
+ List of events as returned by event_properties.
+ '''
+ if curve_x == None or curve_y == None:
+ return None
+ # find curve jumps...
+ [curve_pos, curve_mean, curve_std] = find_curve_jump(curve_y)
+ indent = compute_indentation(curve_x, curve_y, deflection_sensitivity,
+ spring_constant)
+ distances = -indent[0] + max(indent[0])
+ threshold = weight * curve_pos * curve_std
+ positions = threshold > 1
+ event_list = None
+ # Find the point of contac
+ poc_xy = [curve_x[poc], curve_y[poc]]
+ if baseline is not None:
+ f_y = baseline[0] * curve_x + baseline[1]
+ try:
+ new_poc = num.nonzero(curve_y < f_y)[0][0] - 1
+ except:
+ new_poc = poc
+ poc_xy = [curve_x[new_poc], curve_y[new_poc]]
+ if sum(positions):
+ event_list = event_complete(curve_x, curve_y, positions)
+ for event in event_list:
+ event = event_properties(curve_x, curve_y, event, spring_constant,
+ distances, fit_model, poc_xy=poc_xy)
+ if not debug:
+ return event_list
+ else:
+ return [event_list, threshold]
+
+
+def event_complete(curve_x, curve_y, position):
+ '''
+ from a vector which points the putative events position, this function
+ extracts and sort and adds some informations like force,...
+ '''
+ point_list = position.nonzero()[0]
+ # Finds adjascent points that defines in fact a single event...
+ event_list = list()
+ first_pos = 0
+ to_remove = list()
+ for i in xrange(len(point_list) - 1):
+ if point_list[i] + 1 != point_list[i + 1]:
+ event_list.append({'X': point_list[i],
+ 'Slice': slice(point_list[first_pos],
+ point_list[i] + 1)})
+ first_pos = i + 1
+ if i + 1 == len(point_list) - 1:
+ event_list.append({'X': point_list[i],
+ 'Slice': slice(point_list[first_pos],
+ point_list[i + 1] + 1)})
+ first_pos = i + 1
+ if len(point_list) == 1:
+ event_list = [{'X': point_list[0],
+ 'Slice': slice(point_list[0],
+ point_list[0] + 1)}]
+ # Complete all the events.
+ for event_nbr in xrange(len(event_list)):
+ event = event_list[event_nbr]
+ # Complete the events to the right
+ next_is_higher = 1
+ right_pos = event['Slice'].stop
+ while next_is_higher and right_pos + 1 < len(curve_y):
+ if curve_y[right_pos + 1] >= curve_y[right_pos]:
+ right_pos = right_pos + 1
+ else:
+ right_pos = right_pos + 1
+ next_is_higher -= 1
+ # And complete to the left.
+ previous_is_lower = 1
+ left_pos = event['Slice'].start
+ while previous_is_lower:
+ if curve_y[left_pos - 1] < curve_y[left_pos]:
+ left_pos = left_pos - 1
+ #previous_is_lower = 1
+ elif curve_y[left_pos - 1] < curve_y[left_pos + 1]:
+ left_pos = left_pos - 1
+ #previous_is_lower = 1
+ else:
+ #print curve_y[left_pos]
+ #print curve_y[left_pos + 1]
+ left_pos = left_pos - 1
+ previous_is_lower -= 1
+ # We are at the bottom of the event. Let's see the event jump...
+ nbr_test = round(len(curve_y) / 130)
+ previous_is_higher = nbr_test
+ while previous_is_higher and left_pos > 0:
+ if curve_y[left_pos - 1] >= curve_y[left_pos]:
+ left_pos = left_pos - 1
+# previous_is_higher = 1
+ elif curve_y[left_pos - 1] >= curve_y[left_pos + 3]:
+ left_pos = left_pos - 1
+ #previous_is_lower =
+ else:
+ left_pos = left_pos - 1
+ previous_is_higher = previous_is_higher - 1
+ left_pos = left_pos + nbr_test
+ #print curve_x[left_pos]
+ if left_pos <= nbr_test or curve_x[left_pos] <= 5:
+ # This is a jump off contact
+ to_remove.append(event_nbr)
+ event['Slice'] = slice(left_pos, right_pos + 1)
+ for rm_nb in range(len(to_remove)):
+ #print(to_remove[rm_nb] - rm_nb)
+ event_list.pop(to_remove[rm_nb] - rm_nb)
+ return event_list
+
+
+def event_properties(curve_x, curve_y, event, spring_constant, distances,
+ fit_method=None, poc_xy=None):
+ '''
+ Given an event, event_properties computes the properties of the events. The
+ result is returned as a dictionnary with the following keys :
+ 'min' : the minimum of the event, just before the bound break
+ 'max' : the maximum of the event, just after the bound break
+ 'slope' : the slope of the curve that defins the bound breaking
+ 'force' : the unbinding force of the event
+ 'loading_rate' : the loading rate of the event
+ 'fit_length' : the length of the protein found via the fit.
+ 'fit_plength' : the persistent length of the protein.
+ '''
+ # Finds minimum
+ value = min(curve_y[event['Slice']])
+ event['min'] = int(list(curve_y[event['Slice']]).index(value) + \
+ event['Slice'].start)
+ if event['min'] - event['Slice'].start == 1:
+ # Minimum and start are too close. Increase the slice size of the event
+ # to the left.
+ event['Slice'] = slice(event['Slice'].start - 1, event['Slice'].stop)
+ # Finds maximum
+ value = max(curve_y[event['min']:event['Slice'].stop])
+ event['max'] = list(curve_y[event['min']:
+ event['Slice'].stop]).index(value) + event['min']
+ if event['max'] > len(curve_x):
+ event['max'] = len(curve_x)
+ # Find jumpSlope
+ event['slope'] = ((curve_y[event['max']] - curve_y[event['min']]) /
+ (curve_x[event['max']] - curve_x[event['min']]))
+ # Find loading rage
+ if event['Slice'].start == event['min']:
+ event['loading_rate'] = num.nan
+ else:
+ event['loading_rate'] = -((curve_y[event['Slice'].start] -
+ curve_y[event['min']]) /
+ (curve_x[event['Slice'].start] -
+ curve_x[event['min']]))
+ # Find force
+ event['force'] = (curve_y[event['max']] - curve_y[event['min']]) * \
+ spring_constant
+ event['force_base'] = (curve_y[-1] - curve_y[event['min']]) * \
+ spring_constant
+ #print('Force : %f, Force_base : %f')%(event['force'], event['force_base'])
+ #print('Curve 0 : %f, Curve -1 : %f, ')%(curve_y[0], curve_y[-1])
+ #print('Event max : %f')%(curve_y[event['max']])
+ # find distance
+ event['dist'] = distances[event['min']]
+ ## integrate with PoCposition
+ # Perfor the fit on the event.
+ if event_fit:
+ [length, plength, fit_x, fit_y] = event_fit(curve_x, curve_y, event,
+ method=fit_method,
+ spring_constant=spring_constant,
+ poc_xy=poc_xy)
+ else:
+ [length, plength, fit_x, fit_y] = [None, None, None, None]
+
+ event['fit_length'] = length
+ event['fit_plength'] = plength
+ event['fit_x'] = fit_x
+ event['fit_y'] = fit_y
+ return event
+
+
+def find_curve_jump(curve):
+ '''
+ This function finds jumps in a curve. It returns three vectors of the same
+ size of the input vector.
+ curve_jump : filled with zeros and ones. Ones depicted position of jumps.
+ curve_mean : contain the flatten curve
+ curve_std : contain the standard deviation measured along the curve.
+ '''
+ window_size = 10
+ curve_jump = num.zeros(len(curve))
+ curve_mean = deepcopy(curve)
+ curve_std = num.zeros(len(curve))
+ portion_std = num.std(curve)
+ for point_nbr in xrange(len(curve) - window_size):
+ curve_portion = curve[point_nbr:point_nbr + window_size]
+ indice = 0
+ middle_point = curve_portion[indice]
+ portion_mean = num.mean(curve_portion)
+ portion_std = num.std(curve_portion)
+ curve_mean[point_nbr] = portion_mean
+ curve_std[point_nbr] = portion_std
+ if middle_point < portion_mean - portion_std * 2:
+ if point_nbr + indice:
+ curve_jump[point_nbr + indice] = \
+ curve_jump[point_nbr + indice - 1] + 1
+ else:
+ curve_jump[point_nbr + indice] = 1
+ return curve_jump, curve_mean, curve_std
+
+
+def event_fit(curve_x, curve_y, event, method,
+ spring_constant=0.06, poc_xy=None):
+ '''
+ Fit the event with the correct method.
+
+ * Parameters :
+ curve_x : 1d array
+ Values of the x coordinate.
+ curve_y : 1d array
+ Values of the y coordinates.
+ event : dict
+ dictionnary as returned by event_find function.
+ method : str
+ 'wlc' for worm like chain model
+ 'fjc' for freely jointed chain model
+ spring_constant : float
+ The spring constant of the used cantilever
+ poc : int
+ The indice of the point of contact as detected in the approach
+ curve.
+
+ * Returns :
+ length : 1d vector
+ length[0] = length in nm.
+ lenght[1] = error in nm.
+ plength : 1d vector
+ plength[0] = persistent length in nm.
+ plenght[1] = error in nm.
+ fit_x : 1d array
+ contains the x values of the fit on the curve.
+ fit_y : 1d array
+ contains the y values of the fit on the curve.
+
+ As an example, we can get a curve with event and find event with
+ event_find :
+
+ >>> curve = open_csv_curve(CURVE_EVENT[0])
+ >>> events = event_find(curve[2], curve[3], 0, 0.06)
+
+ In order to fit with the worm like chain model :
+
+ >>> [length, plength, fit_x, fit_y] = \
+ event_fit(curve[2], curve[3], events[1], method='wlc',\
+ spring_constant=0.0554)
+
+ The result are the length and the persistent length :
+
+ >>> print('Length : %.5f, error : %.5f')%(length[0], length[1])
+ Length : 282.42567, error : 1.19537
+ >>> print('Plength : %.5f, error : %.5f')%(plength[0], plength[1])
+ Plength : 0.26088, error : 0.01271
+
+ Let's plot the result :
+
+ >>> import pylab
+ >>> wlc_plot = pylab.plot(curve[2], curve[3])
+ >>> wlc_plot = pylab.plot(fit_x, fit_y)
+ >>> pylab.savefig(FIGURE_FOLDER + 'wlc_plot.png')
+
+ Here is the same for the free joint chain model :
+
+ >>> [length, plength, fit_x, fit_y] = \
+ event_fit(curve[2], curve[3], events[1], method='fjc',\
+ spring_constant=0.0554)
+
+ You can see, in that case, that the result are close to the wlc model :
+
+ >>> print('%.3f, %.3f')%(length[0],length[1])
+ 263.263, 0.865
+ >>> print('%.3f, %.3f')%(plength[0],plength[1])
+ 0.253, 0.010
+
+ Let's plot the result :
+
+ >>> pylab.cla()
+ >>> fjc_plot = pylab.plot(curve[2], curve[3])
+ >>> fjc_plot = pylab.plot(fit_x, fit_y)
+ >>> pylab.savefig(FIGURE_FOLDER + 'fjc_plot.png')
+ '''
+ # extract the portion of the curve that contains the event
+ ar_event = [curve_x[event['Slice']], curve_y[event['Slice']]]
+ if poc_xy is None:
+ poc_xy = [curve_x[0], None]
+ #max_y = None
+ if method == 'wlc':
+ result, fit, p_eval, maxy = __wlc_fit(ar_event[0], ar_event[1],
+ poc=poc_xy[0],
+ spring_constant=spring_constant,
+ max_y=poc_xy[1])
+ # prepare the array for the fit
+ poc_indice = num.nonzero(curve_x >= poc_xy[0])[0][0]
+ new_slice = slice(poc_indice, event['Slice'].stop) # event['min'])
+ fit_x = curve_x[new_slice]
+ fit_y = p_eval(result.beta, fit_x)
+ elif method == 'fjc':
+ result, fit, p_eval, maxy = __fjc_fit(ar_event[0], ar_event[1],
+ poc=poc_xy[0],
+ spring_constant=spring_constant,
+ max_y=poc_xy[1])
+# # prepare the array for the fit
+ min_y = min(ar_event[1])
+# if baseline is not None:
+# max_y = baseline_y[0]
+# else:
+# max_y = max(ar_event[1])
+ fit_y = num.arange(min_y, maxy, (maxy - min_y) / 100)
+ fit_x = p_eval(result.beta, fit_y) + poc_xy[0]
+ #fit_x, fit_y = None, None
+ elif method in [None, 'None']:
+ return [None, None, None, None]
+ else:
+ raise TypeError('Method %s is not known.' % method)
+ return [[result.beta[0] * 1e9, result.sd_beta[0] * 1e9],
+ [result.beta[1] * 1e9, result.sd_beta[1] * 1e9],
+ fit_x, fit_y]
+
+
+def __wlc_fit(x, y, poc=0, spring_constant=0.06, baseline_y=None, max_y=None):
+ '''
+ Perform Worm Like Chain fit on the curve portion.
+
+ It's better to use "event_fit" with the option "method=wlc" as it
+ returns more comprehensive results.
+
+ * Parameters :
+ curve_x : 1d array
+ Values of the x coordinate.
+ curve_y : 1d array
+ Values of the y coordinates.
+ poc : int
+ The index of the point of contact.
+ spring_constant : float
+ The spring constant of the cantilever (in N/m)
+
+ * Returns :
+ out : odr.ODR object
+ The object from which you can get the parameters of the fit.
+ result : The parameter of the fit # we can remove it...
+
+ pevalr : fct
+ function to evaluate the fit.
+ rec_maxy
+ A conversion value to evaluate the fit.
+ '''
+ # First, we take the beginning to the lowest point.
+ new_x, new_y, rec_maxy = __prepare_event_for_fit(x, y, spring_constant,
+ poc=poc, max_y=max_y)
+ # The initial condition
+ T = 285
+ L = new_x[-1] + (new_x[-1] * 0.1)
+ Lp = 0.16e-9
+ p0 = [L, Lp]
+
+ def peval(p, x):
+ """
+ Define the function to fit
+ """
+ # p[0] = L
+ # p[1] = Lp
+ Kb = 1.381e-23 # Boltzmann constant
+
+ old_settings = num.seterr(all='ignore')
+ _peval_result = ((Kb * T / p[1]) *
+ (((x) / p[0]) + 1 / (4 * (1 - (x) / p[0]) ** 2) - 1 / 4))
+ num.seterr(**old_settings) # restore settings
+ return _peval_result
+
+ def pevalr(p, x):
+ """
+ This is the function that will be return in order to complete the
+ fit. Usefull for pretty plots.
+ """
+ # Lp = 1e-9
+ new_x = (x - poc) * 1e-9
+ result = peval(p, new_x)
+ result = -result * 1e9 / spring_constant + rec_maxy
+ return result
+
+ # Now, we prepare the fit
+ fit_it = odr.ODR(odr.Data(new_x, new_y), odr.Model(peval), p0)
+ # And we run it
+ out = fit_it.run()
+ # Get the result and rescale it.
+ result = [new_x, peval(out.beta, new_x)]
+ result[0] = result[0] * 1e9 + poc
+ result[1] = -(result[1] * 1e9 / spring_constant) + rec_maxy
+ return [out, result, pevalr, rec_maxy]
+
+
+def __prepare_event_for_fit(x, y, spring_constant, poc=0, max_y=None):
+ min_index = num.where(y == min(y))[0][0]
+ if max_y is not None:
+ rec_maxy = max_y
+ else:
+ rec_maxy = max(y)
+ new_x = x[0:min_index + 1] - poc # - x[poc]
+ new_y = y[0:min_index + 1]
+ # Then, we rescale to be in the metric system (we were in nm)
+ new_x = (new_x) * 1e-9
+ new_y = -(new_y - rec_maxy) * 1e-9 * spring_constant # * 0.06
+ return new_x, new_y, rec_maxy
+
+
+def __fjc_fit(x, y, poc=0, spring_constant=0.06, max_y=None):
+ '''
+ Perform Worm Like Chain fit on the curve portion.
+
+ It's better to use "event_fit" with the option "method=fjc" as it
+ returns more comprehensive results.
+
+ * Parameters :
+ curve_x : 1d array
+ Values of the x coordinate.
+ curve_y : 1d array
+ Values of the y coordinates.
+ poc : int
+ The index of the point of contact.
+ spring_constant : float
+ The spring constant of the cantilever (in N/m)
+
+ * Returns :
+ out : odr.ODR object
+ The object from which you can get the parameters of the fit.
+ result : The parameter of the fit # we can remove it...
+
+ pevalr : fct
+ function to evaluate the fit.
+ rec_maxy
+ A conversion value to evaluate the fit.
+ '''
+ # First, we get only the part of the curve that should be fitted.
+ new_x, new_y, rec_maxy = __prepare_event_for_fit(x, y, spring_constant,
+ poc, max_y=max_y)
+ # The initial condition
+ T = 285
+ L = new_x[-1] + (new_x[-1] * 0.1)
+ Lp = 0.4e-9
+ p0 = [L, Lp]
+
+ def peval(p, y):
+ # p[0] = L
+ # p[1] = Lp
+ Kb = 1.381e-23 # Boltzmann constant
+ # Avoid zero elements in y. As force should be positive, if zero is
+ # given, it's the minimum one. Then replace by the non-zero minimum
+ # value, which should be little bit higher value.
+ y[num.nonzero(y == 0)] = min(y[num.nonzero(y != 0)])
+ return (p[0] * (1 / num.tanh((y * p[1]) / (Kb * T)) -
+ (Kb * T) / (y * p[1])))
+
+ def pevalr(p, y):
+ """
+ This is the function that will be return in order to complete the
+ fit. Usefull for pretty plots.
+ """
+ new_y = -(y - rec_maxy) * 1e-9 * spring_constant
+ result = peval(p, new_y)
+ result = result * 1e9
+ return result
+ # Prepare the fit
+ fit_it = odr.ODR(odr.Data(new_y, new_x), odr.Model(peval), p0)
+ # And we run it
+ out = fit_it.run()
+ # Get the result and rescale it.
+ result = [new_y, peval(out.beta, new_y)]
+ result[0], result[1] = result[1], result[0]
+ result[0] = result[0] * 1e9
+ result[1] = -(result[1] * 1e9 / 0.06 - rec_maxy)
+ return [out, result, pevalr, rec_maxy]
+
+
+if __name__ == '__main__':
+ import doctest
+ doctest.testmod()
diff --git a/openfovea/fovea_toolbox/file_util/__init__.py b/openfovea/fovea_toolbox/file_util/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/openfovea/fovea_toolbox/file_util/aex.py b/openfovea/fovea_toolbox/file_util/aex.py
new file mode 100644
index 0000000..5d294c6
--- /dev/null
+++ b/openfovea/fovea_toolbox/file_util/aex.py
@@ -0,0 +1,713 @@
+from xml.dom.minidom import Document, parse
+import os
+import commands
+import tempfile
+import zipfile
+from datetime import datetime
+from distutils.version import StrictVersion
+import pdb
+
+import numpy
+from numpy import array, Inf, inf, NaN, nan
+
+import succellus_import
+
+from common import Variables
+GLOB_VARS = Variables()
+
+version = '0.1a68'
+
+###
+# 0.1a68 : Add PoCNoise array.
+# 0.1a67 : Add event length and persistent length.
+
+
+# TODO correct the small error while saving the event force. I get an error of
+# ~2e-12 between original and saved data (see aexTest, I had to change a
+# assertEqual to assertAlmostEqual)
+def save(fv_folder_object, file_name):
+ '''
+ Save a FVFolder object to an aex file.
+ '''
+ tmp_dir = tempfile.mkdtemp()
+ if not os.path.splitext(file_name)[1] == '.aex':
+ file_name = file_name + '.aex'
+ save_exp(fv_folder_object, tmp_dir, version)
+ start_path = os.path.split(file_name)[0]
+ for fv_object in fv_folder_object.file['list']:
+ if fv_object.header['data_location'] not in ['local', u'local']:
+ curr_path = os.path.split(fv_object.header['data_location'])[0]
+ rel_path = os.path.relpath(curr_path, start_path)
+ fv_object.header['data_location'] = os.path.join(
+ rel_path,
+ os.path.split(fv_object.header['data_location'])[1])
+ save_fv(fv_object, tmp_dir, version)
+ mime_type = open(os.path.join(tmp_dir,'mimetype'), 'wb')
+ mime_type.write('application/afm.aex.data')
+ mime_type.close()
+ # Compress all files in the zip
+ aex_file = zipfile.ZipFile(file_name,'w')
+
+ for item in os.listdir(tmp_dir):
+ if os.path.isfile(os.path.join(tmp_dir, item)):
+ aex_file.write(os.path.join(tmp_dir, item), item)
+ os.remove(os.path.join(tmp_dir, item))
+ for item in os.listdir(os.path.join(tmp_dir, 'data')):
+ aex_file.write(os.path.join(tmp_dir, 'data', item),
+ os.path.join('data', item))
+ os.remove(os.path.join(tmp_dir, 'data', item))
+ aex_file.close()
+ os.rmdir(os.path.join(tmp_dir, 'data'))
+ os.rmdir(tmp_dir)
+
+def save_exp(fv_folder, base_dir, version):
+ exp_tree = Document()
+ exp_node = exp_tree.createElement('fvfolder')
+ exp_node.setAttribute('xmlns:openfovea','initiator_software')
+ exp_node.setAttribute('openfovea:version', version)
+
+ info_node = exp_tree.createElement('info')
+ #####
+ # Date node
+ date_node = exp_tree.createElement('date')
+ date_element = exp_tree.createTextNode(str(fv_folder.date))
+ date_node.appendChild(date_element)
+ exp_node.appendChild(date_node)
+ # End of date node
+ #####
+ # Author node
+ author_node = exp_tree.createElement('author')
+ author_element = exp_tree.createTextNode(fv_folder.author)
+ author_node.appendChild(author_element)
+ exp_node.appendChild(author_node)
+ # End of author node
+ #####
+ # Comment node
+ comment_node = exp_tree.createElement('comment')
+ comment_element = exp_tree.createTextNode(fv_folder.comment)
+ comment_node.appendChild(comment_element)
+ exp_node.appendChild(comment_node)
+ # End of comment node
+ #####
+ # Parameters node
+ parameters_node = exp_tree.createElement('parameters')
+ for key in fv_folder.parameters:
+ parameters_node.setAttribute(key, str(fv_folder.parameters[key]))
+ exp_node.appendChild(parameters_node)
+ # End of parameters node
+ #####
+ # Group node
+ group_node = exp_tree.createElement('group')
+ for key in fv_folder.group:
+ group_node.setAttribute(key, str(fv_folder.group[key]))
+ exp_node.appendChild(group_node)
+ # End of group node
+ #####
+ exp_tree.appendChild(exp_node)
+
+ xml_id = open(os.path.join(base_dir, 'experiment.xml'), 'wb')
+ xml_id.write(exp_tree.toprettyxml())
+ xml_id.close()
+
+def load_exp(file_name):
+ xmldoc=parse(file_name)
+ child=xmldoc.firstChild
+ #exp_dict['OF_version'] = child.getAttribute('openfovea:version')
+ exp_dict = {'date' : '',
+ 'author' : '',
+ 'comment': '',
+ 'parameters' : {'event_detect_weight' : None,
+ 'event_fit_model' : None,
+ 'rel_stiff_dist' : None,
+ 'injection_index' : 0,
+ 'PoC_threshold' : 2,
+ 'histo_y_rel' : False,
+ 'plot_size' : [800, 600],
+ 'plot_hist_gauss' : True,
+ },
+ 'group' : { 'id' : [],
+ 'label' : [],
+ 'display' : []},
+ 'OF_version' : child.getAttribute('openfovea:version')
+ }
+ for elm in child.childNodes:
+ if elm.nodeName in exp_dict.keys():
+ if elm.nodeName == 'parameters':
+ for key in exp_dict['parameters'].keys():
+ string = elm.getAttribute(key)
+ if len(string):
+ try:
+ exp_dict['parameters'][key] = eval(string)
+ except NameError:
+ exp_dict['parameters'][key] = str(string)
+ elif elm.nodeName == 'group':
+ for key in exp_dict['group'].keys():
+ exp_dict['group'][key] = eval(elm.getAttribute(key))
+ else:
+ string = elm.childNodes[0].data
+ string = string.replace('\n\t\t','')
+ string = string.replace('\n\t','')
+ #for key in exp_dict.keys:
+ if elm.nodeName == 'date':
+ exp_dict['date'] = datetime.strptime(string,
+ '%Y-%m-%d %H:%M:%S.%f')
+ else:
+ exp_dict[elm.nodeName] = string
+ exp_dict['parameters']['rel_stiff_dist'] = int(exp_dict['parameters']['rel_stiff_dist'])
+ return exp_dict
+
+def save_fv(fv_object, base_dir, version):
+ '''
+ Save a single force volume file to an xml file.
+ '''
+ fv_tree = Document()
+
+ force_volume_node = fv_tree.createElement('fvfile')
+ force_volume_node.setAttribute('xmlns:array','numpy_array')
+ force_volume_node.setAttribute('xmlns:openfovea','initiator_software')
+ force_volume_node.setAttribute('openfovea:version', version)
+ force_volume_node.setAttribute('filename', fv_object.name)
+
+ header_node = fv_tree.createElement('header')
+ for key in fv_object.header:
+ if key == 'x_factor':
+ header_node.setAttribute(key, '%.15f'%fv_object.header[key])
+ elif key == 'date':
+ header_node.setAttribute(key, str(fv_object.header[key]))
+ elif key == 'note':
+ header_node.setAttribute(key, fv_object.header[key].__repr__())
+ elif key == 'index_array':
+ _node = __node_array(fv_tree.createElement('index_array'),
+ fv_object.header[key],
+ 'index',
+ fv_object.name,
+ base_dir)
+ if _node is not None:
+ force_volume_node.appendChild(_node)
+ else:
+ header_node.setAttribute(key, str(fv_object.header[key]))
+ force_volume_node.appendChild(header_node)
+
+ ## Save the switches
+ switch_node = fv_tree.createElement('switch')
+ for key in fv_object.get_switch('list'):
+ switch_node.setAttribute(key, str(fv_object.get_switch(key)))
+ force_volume_node.appendChild(switch_node)
+ original_switches = fv_object.get_switch('restore')
+ # Force switches to be null in order to save all the data
+ fv_object.set_switch('reset')
+ ## Piezo array
+ try:
+ __array = fv_object.get_array('Piezo')
+ except Exception as inst:
+ if 'Topography array is not loaded' in inst.message:
+ pass
+ else:
+ raise inst
+ else:
+ _node = __node_array(fv_tree.createElement('piezo_array'),
+ __array,
+ 'piezo',
+ fv_object.name,
+ base_dir)
+ if _node is not None:
+ force_volume_node.appendChild(_node)
+ ## Stiffness header
+ stiffness_header_node = fv_tree.createElement('stiffness_header')
+ for key in fv_object.stiffness:
+ stiffness_header_node.setAttribute(key, str(fv_object.stiffness[key]))
+ force_volume_node.appendChild(stiffness_header_node)
+ ## Stiffness array
+ st_array = fv_object.get_array('Stiffness')
+ if type(st_array) == numpy.ma.core.MaskedArray:
+ stiffness_node = fv_tree.createElement('stiffness')
+ #stiffness_node.setAttribute('shape',str(st_array.shape))
+# for key in fv_object.stiffness:
+# stiffness_node.setAttribute(key, str(fv_object.stiffness[key]))
+
+ stiff_data_node = fv_tree.createElement('data')
+ rel_name = 'data/' + fv_object.name + '_stiffness.data'
+ abs_name = os.path.join(base_dir, 'data', fv_object.name + '_stiffness.data')
+ stiff_data_node.setAttribute('shape', str(st_array.data.shape))
+ stiff_data_node.setAttribute('array:href', rel_name)
+ __save_array(st_array.data, abs_name)
+ stiffness_node.appendChild(stiff_data_node)
+
+ stiff_mask_node = fv_tree.createElement('mask')
+ rel_name = 'data/' + fv_object.name + '_stiffness_mask.data'
+ abs_name = os.path.join(base_dir, 'data', fv_object.name + '_stiffness_mask.data')
+ stiff_mask_node.setAttribute('shape', str(st_array.mask.shape))
+ stiff_mask_node.setAttribute('array:href', rel_name)
+ __save_array(st_array.mask.astype(float), abs_name)
+ stiffness_node.appendChild(stiff_mask_node)
+
+ force_volume_node.appendChild(stiffness_node)
+
+ # Mask array
+ _node = __node_array(fv_tree.createElement('mask_array'),
+ fv_object.get_array('Mask'),
+ 'mask', fv_object.name, base_dir)
+ if _node is not None:
+ force_volume_node.appendChild(_node)
+ ## PoC array
+ _node = __node_array(fv_tree.createElement('poc_array'),
+ fv_object.get_array('PoCIndice'),
+ 'poc_indice', fv_object.name, base_dir)
+ if _node is not None:
+ force_volume_node.appendChild(_node)
+ ## PoC Noise array
+ _node = __node_array(fv_tree.createElement('pocnoise_array'),
+ fv_object.get_array('PoCNoise'),
+ 'poc_noise', fv_object.name, base_dir)
+ if _node is not None:
+ force_volume_node.appendChild(_node)
+ ## Topography array
+ _node = __node_array(fv_tree.createElement('topo_array'),
+ fv_object.get_array('Topography'),
+ 'topography', fv_object.name, base_dir)
+ if _node is not None:
+ force_volume_node.appendChild(_node)
+ ## Event array
+ _node = __node_array(fv_tree.createElement('event'),
+ fv_object.get_array('Event'),
+ 'event', fv_object.name, base_dir)
+ if _node is not None:
+ # Save the parameters of the events.
+ for key in fv_object.event:
+ if fv_object.event[key] is not None:
+ _node.setAttribute(key, str(fv_object.event[key]))
+ else:
+ pass
+ # Save also the details of the events.
+ for event in fv_object.event_list:
+ det_event_node = fv_tree.createElement('detail')
+ for key in event:
+ # TODO Putative dead code : find out when it is necessary
+# if key == 'Stiffness':
+# det_event_node.setAttribute('Stiffness_data', str(list(event[key].data)))
+# det_event_node.setAttribute('Stiffness_mask', str(list(event[key].mask)))
+ det_event_node.setAttribute(key, str(event[key]))
+ _node.appendChild(det_event_node)
+ force_volume_node.appendChild(_node)
+ ## Random array
+ _node = __node_array(fv_tree.createElement('event_rand_array'),
+ fv_object.get_array('Event random'),
+ 'event_random', fv_object.name, base_dir)
+ if _node is not None:
+ force_volume_node.appendChild(_node)
+
+ # The force-distance curve :
+ curve_dict = fv_object._ForceVolume__fdcurves
+
+ data_location = fv_object.header['data_location']
+ if data_location not in ['local', u'local']:
+ pass
+ else:
+ # trace x
+ trace_x = fv_tree.createElement('trace_x_array')
+ rel_name = 'data/' + fv_object.name + '_trace_x.data'
+ abs_name = os.path.join(base_dir, 'data', fv_object.name + '_trace_x.data')
+ __save_array(curve_dict['trace_x'], abs_name)
+ trace_x.setAttribute('array:href', rel_name)
+ trace_x.setAttribute('shape',str(curve_dict['trace_x'].shape))
+ force_volume_node.appendChild(trace_x)
+ # retrace x
+ retrace_x = fv_tree.createElement('retrace_x_array')
+ rel_name = 'data/' + fv_object.name + '_retrace_x.data'
+ abs_name = os.path.join(base_dir, 'data', fv_object.name + '_retrace_x.data')
+ __save_array(curve_dict['retrace_x'], abs_name)
+ retrace_x.setAttribute('array:href', rel_name)
+ retrace_x.setAttribute('shape',str(curve_dict['retrace_x'].shape))
+ force_volume_node.appendChild(retrace_x)
+
+ # Trace force curves
+ trace_array = fv_tree.createElement('trace_array')
+ rel_name = 'data/' + fv_object.name + '_trace.data'
+ abs_name = os.path.join(base_dir, 'data', fv_object.name + '_trace.data')
+ __save_array(curve_dict['trace_y'], abs_name)
+ trace_array.setAttribute('array:href', rel_name)
+ trace_array.setAttribute('shape',str(curve_dict['trace_y'].shape))
+ force_volume_node.appendChild(trace_array)
+
+ # Retrace force curves
+ retrace_array = fv_tree.createElement('retrace_array')
+ rel_name = 'data/' + fv_object.name + '_retrace.data'
+ abs_name = os.path.join(base_dir, 'data', fv_object.name + '_retrace.data')
+ __save_array(curve_dict['retrace_y'], abs_name)
+ retrace_array.setAttribute('array:href', rel_name)
+ retrace_array.setAttribute('shape',str(curve_dict['retrace_y'].shape))
+ force_volume_node.appendChild(retrace_array)
+
+ fv_tree.appendChild(force_volume_node)
+ xml_id = open(os.path.join(base_dir, fv_object.name + '.xml'), 'wb')
+ xml_id.write(fv_tree.toprettyxml())
+ xml_id.close()
+ fv_object.set_switch('restore', original_switches)
+def __node_array(array_node, array, array_name, object_name, base_dir):
+ """
+ Save the array.
+
+ * Parameters :
+ array_node : xml node
+ The node where to put these information.
+ array : 2d numpy.array
+ The array to save.
+ array_name : str
+ The name of the array. This will be saved in the xml tree
+ object_name : str
+ The name of the fv object. This is used to relate the array to
+ the correct object when loading.
+ parent_node : xml node
+ The parent node where to put the array node.
+ base_dir : str
+ The base directory to save the data.
+ """
+
+ if type(array) == numpy.ndarray:
+ rel_name = 'data/' + object_name + '_' + array_name + '.data'
+ abs_name = os.path.join(base_dir, 'data', object_name + '_' + array_name + '.data')
+ array_node.setAttribute('shape',str(array.shape))
+ array_node.setAttribute('array:href', rel_name)
+ __save_array(array, abs_name)
+ return array_node
+ else:
+ return None
+
+def __save_array(array, c_file_name):
+ """
+ Saves the array to a binary format.
+
+ * Parameters :
+ array : numpy.ndarray
+ The array to save.
+ c_file_name : str
+ The filename to save it.
+
+ * Returns :
+ None
+ """
+
+ [directory, file_name] = os.path.split(c_file_name)
+ if not os.path.exists(directory):
+ os.makedirs(directory)
+ file_id = open(c_file_name, 'wb')
+ if type(array) == numpy.ndarray:
+ array = array.astype('float64')
+ file_id.write(array.tostring('C'))
+ file_id.close()
+
+def __load_array(elm, folder_name):
+ """
+ Loads the array from an xml node of the array type
+
+ * Parameters :
+ elm : xml node
+ xml node with 'shape' and 'array:href' attributes.
+ folder_name : str
+ The path to the file.
+
+ * Returns :
+ array : numpy.ndarray
+ The dimension of the array accords with 'shape' attribute of the
+ elm xml node.
+ """
+ target = os.path.join(folder_name, elm.getAttribute('array:href'))
+ shape = eval(elm.getAttribute('shape'))
+ array_id = open(target, 'rb')
+ array_str = array_id.read()
+ array = numpy.fromstring(array_str, dtype=numpy.float64)
+ return array.reshape(shape)
+def convert2current(xmldoc):
+ """
+ Convert a previous version tree in current.
+ """
+ child = xmldoc.firstChild
+ fv_dict = {'OF_version' : child.getAttribute('openfovea:version')}
+ if StrictVersion(fv_dict['OF_version']) <= StrictVersion('0.1a63'):
+ # There is no stiffness_header element.
+ # This element is in stiffness with the arrays.
+ elem = child.getElementsByTagName('stiffness')
+ if len(elem):
+ stiffness_node = elem[0]
+ stiff_header = xmldoc.createElement('stiffness_header')
+ # get the stiffness properties
+ stiff_prop = {'HertzModel' : '', 'PointCarac' : '', 'calibSens' : '',
+ 'glass' : '', 'nbParts' : '', 'sizeParts' : '',
+ 'relativeMaxDist' : '', 'PoCThreshold' : '',
+ 'PoCMethod' : u'deriv', 'recomputePoC' : u'False',
+ 'limitSlide' : u'False', 'poisson_ratio' : u'0.3',
+ 'poc_len_slice' : u'0.33'
+ }
+ for key in stiff_prop.keys():
+ if stiffness_node.hasAttribute(key):
+ val = stiffness_node.getAttribute(key)
+ else:
+ val = stiff_prop[key]
+ stiff_prop[key] = val
+ for item in S_HEADER_064_2_CURRENT:
+ val = stiff_prop[S_HEADER_064_2_CURRENT[item]]
+ stiff_header.setAttribute(item, val)
+ stiff_header.setAttribute('poc_len_slice', 0.33)
+ child.appendChild(stiff_header)
+ if StrictVersion(fv_dict['OF_version']) == StrictVersion('0.1a64'):
+ stiff_header = child.getElementsByTagName('stiffness_header')[0]
+ for item in S_HEADER_064_2_CURRENT:
+ value = stiff_header.getAttribute(S_HEADER_064_2_CURRENT[item])
+ stiff_header.removeAttribute(S_HEADER_064_2_CURRENT[item])
+ stiff_header.setAttribute(item, value)
+ stiff_header.setAttribute('poc_len_slice', 0.33)
+ if StrictVersion(fv_dict['OF_version']) <= StrictVersion('0.1a65'):
+ # The change is in the curve_x. Before it was always a 1d vector. Now,
+ # it can be also 3d vector. The node name changed from 'curve_x_array'
+ # to 'trace_x_array' and 'retrace_y_array' for the trace and retrace
+ # curve respectively.
+ node = child.getElementsByTagName('curve_x_array')[0]
+ target = node.getAttribute('array:href')
+ shape = node.getAttribute('shape')
+ trace_x = xmldoc.createElement('trace_x_array')
+ trace_x.setAttribute('array:href', target)
+ trace_x.setAttribute('shape', shape)
+ retrace_x = xmldoc.createElement('retrace_x_array')
+ retrace_x.setAttribute('array:href', target)
+ retrace_x.setAttribute('shape', shape)
+ child.appendChild(trace_x)
+ child.appendChild(retrace_x)
+ if StrictVersion(fv_dict['OF_version']) <= StrictVersion('0.1a66'):
+ # The change is in the presence of a wlc fit for events.
+ pass
+ return xmldoc
+def load_fv(file_name, curdir=''):
+ '''
+ Loads the force volume xml file and returns a dictionnary that contains the
+ datas.
+ '''
+ folder_name = os.path.split(file_name)[0]
+ xmldoc=parse(file_name)
+ child=xmldoc.firstChild
+ fv_dict = {'name' : None,
+ 'header' : {},
+ 'stiffness_header' : {},
+ 'piezo_array' : None,
+ 'topo_array' : None,
+ 'poc_array' : None,
+ 'pocnoise_array' : None,
+ 'trace_x_array' : None,
+ 'retrace_x_array' : None,
+ 'trace_array' : None,
+ 'retrace_array' : None,
+ 'event_rand_array' : None,
+ 'stiffness' : None,
+ 'mask_array' : None,
+ 'index_array' : None,
+ 'switch' : {'mask' : 'False'},
+ 'event' : {}}
+ fv_dict['name'] = child.getAttribute('filename')
+ fv_dict['OF_version'] = child.getAttribute('openfovea:version')
+ if StrictVersion(fv_dict['OF_version']) < StrictVersion(version):
+ xmldoc = convert2current(xmldoc)
+ child = xmldoc.firstChild
+ for elm in child.childNodes:
+ if elm.nodeName in fv_dict.keys():
+ if 'array' in elm.nodeName:
+ fv_dict[elm.nodeName] = __load_array(elm, folder_name)
+ if elm.nodeName == 'event_rand_array':
+ nbr_random = elm.getAttribute('nbr_random')
+ if len(nbr_random):
+ ev_prop = {'nbr_random' : int(nbr_random)}
+ if elm.nodeName == 'mask_array' and fv_dict[elm.nodeName] is not None:
+ fv_dict[elm.nodeName] = fv_dict[elm.nodeName].astype('bool')
+ elif elm.nodeName == 'stiffness_header':
+ stiff_prop = {}
+ for key, fct in zip(STIFFNESS_HEADER.keys(), STIFFNESS_HEADER.values()):
+ fv_dict['stiffness_header'][key] = fct(elm.getAttribute(key))
+ elif elm.nodeName == 'stiffness':
+ # get the data and mask
+ for array_node in elm.childNodes:
+ if array_node.nodeName == 'data':
+ stiff_data = __load_array(array_node, folder_name)
+ elif array_node.nodeName == 'mask':
+ stiff_mask = __load_array(array_node, folder_name)
+ stiff_array = numpy.ma.MaskedArray(stiff_data,
+ mask = stiff_mask)
+ fv_dict['stiffness'] = stiff_array
+ elif elm.nodeName == 'event':
+ if StrictVersion(fv_dict['OF_version']) < StrictVersion('0.1a62'):
+ # Loading rate wasn't computed...
+ pass
+ else:
+ # get the event array
+ fv_dict['event']['array'] = __load_array(elm, folder_name)
+ # get the detail of the events
+ detail = list()
+
+ for array_node in elm.childNodes:
+ if array_node.nodeName == 'detail':
+ ev_det = {}
+ ev_det['Slice'] = array_node.getAttribute('Slice')
+ ev_det['X'] = int(array_node.getAttribute('X'))
+ ev_det['force'] = float(array_node.getAttribute('force'))
+ ev_det['max'] = int(array_node.getAttribute('max'))
+ ev_det['min'] = int(array_node.getAttribute('min'))
+ ev_det['pos_x'] = int(array_node.getAttribute('pos_x'))
+ ev_det['pos_y'] = int(array_node.getAttribute('pos_y'))
+ ev_det['slope'] = float(array_node.getAttribute('slope'))
+ ev_det['loading_rate'] = float(array_node.getAttribute('loading_rate'))
+ ev_det['dist'] = float(array_node.getAttribute('dist'))
+ if StrictVersion(fv_dict['OF_version']) > StrictVersion('0.1a66'):
+ ev_det['fit_length'] = eval(array_node.getAttribute('fit_length'))
+ ev_det['fit_plength'] = eval(array_node.getAttribute('fit_plength'))
+ else:
+ ev_det['fit_length'] = None
+ ev_det['fit_plength'] = None
+ # TODO Putative dead code : find when it's necessary
+ # if array_node.hasAttribute('Stiffness_mask'):
+ # mask = eval(array_node.getAttribute('Stiffness_mask'))
+ # data = eval(array_node.getAttribute('Stiffness_data'))
+ # ev_det['Stiffness'] = numpy.ma.array(data, mask=mask)
+ detail.append(ev_det)
+ fv_dict['event']['detail'] = detail
+ elif elm.nodeName == 'header':
+ header_dict = GLOB_VARS.common_header
+ for key in header_dict.keys():
+ if elm.getAttribute(key) is not '':
+ header_dict[key] = elm.getAttribute(key)
+ try:
+ header_dict[key] = eval(header_dict[key])
+ except:
+ pass
+ try:
+ header_dict['date'] = datetime.strptime(header_dict['date'],
+ '%Y-%m-%d %H:%M:%S.%f')
+ except ValueError:
+ header_dict['date'] = datetime.strptime(header_dict['date'],
+ '%Y-%m-%d %H:%M:%S')
+ except TypeError:
+ header_dict['date'] = None
+ if StrictVersion(fv_dict['OF_version']) < StrictVersion('0.1a63') :
+ header_dict['number_curves'] = header_dict['matrix_length'] ** 2
+ if StrictVersion(fv_dict['OF_version']) < StrictVersion('0.1a64') :
+ header_dict['event_dist_thresh'] = 0.0
+ if type(header_dict['size']) not in [list, tuple]:
+ header_dict['size'] = (header_dict['size_x'], header_dict['size_y'])
+ if type(header_dict['scan_size']) not in [list, tuple]:
+ header_dict['scan_size'] = (header_dict['scan_size'],
+ header_dict['scan_size'])
+ if type(header_dict['pixel_size']) not in [list, tuple]:
+ header_dict['pixel_size'] = (header_dict['pixel_size'],
+ header_dict['pixel_size'])
+ fv_dict['header'] = header_dict
+ elif elm.nodeName == 'switch':
+ fv_switch = {}
+ for key_nb in range(elm.attributes.length):
+ fv_dict['switch'][elm.attributes.item(key_nb).name] = \
+ elm.attributes.item(key_nb).value
+
+ if fv_dict['header']['data_location'] not in ['local', u'local']:
+ data_location = fv_dict['header']['data_location']
+
+ # Get the correct path.
+ try_path = [fv_dict['header']['data_location'],
+ os.path.join(curdir, os.path.split(fv_dict['header']['data_location'])[-1]),
+ os.path.join(curdir, fv_dict['header']['data_location'].split('\\')[-1])]
+
+ correct_path = [os.path.exists(__itpath) for __itpath in try_path]
+ data_location = try_path[correct_path.index(True)]
+
+ if os.path.isdir(data_location): # Asylum case
+ fv_dict['data_fid'] = data_location
+ else : # JPK case
+ fv_dict['data_fid'] = zipfile.ZipFile(data_location, 'r')
+
+ fv_dict['header']['data_location'] = data_location
+ fv_dict['header']['index_array'] = fv_dict['index_array']
+ return fv_dict
+
+def load(aex_name):
+ # first unzip the file to a tmp folder
+ compressed_file = zipfile.ZipFile(aex_name)
+ tmp_dir = tempfile.mkdtemp()
+ file_list = []
+ directory_list = []
+ file_tree = []
+ for file_in in compressed_file.filelist:
+ directory = os.path.split(file_in.filename)[0]
+ if not directory == '':
+ if not os.path.isdir(os.path.join(tmp_dir,directory)):
+ directory_list.append(directory)
+ os.makedirs(os.path.join(tmp_dir, directory))
+ outfile = open(os.path.join(tmp_dir, file_in.filename), 'wb')
+ outfile.write(compressed_file.read(file_in.filename))
+ outfile.close()
+ file_list.append(file_in.filename)
+
+ if 'mimetype' in file_list:
+ # OpenFovea version of aex
+ for file_name in file_list:
+ c_file_name = os.path.join(tmp_dir, file_name)
+ if file_name == 'experiment.xml':
+ experiment = load_exp(c_file_name)
+ elif os.path.splitext(file_name)[1] == '.xml':
+ file_tree.append(load_fv(c_file_name, curdir=os.path.split(aex_name)[0]))
+ os.remove(c_file_name)
+ for directory in directory_list:
+ os.rmdir(os.path.join(tmp_dir,directory))
+ else:
+ # Succellus version of aex, designed to disapear...
+ for file_name in file_list:
+ c_file_name = os.path.join(tmp_dir, file_name)
+ if file_name == 'experiment.xml':
+ experiment = succellus_import.xmlExtractExperiment(c_file_name)
+ os.remove(c_file_name)
+ else :
+ file_tree.append(succellus_import.xmlExtractFV(c_file_name))
+ os.remove(c_file_name)
+ os.rmdir(tmp_dir)
+ return [experiment, file_tree]
+
+STIFFNESS_HEADER_0_64 = {
+ 'HertzModel' : lambda x: x,
+ 'PoCMethod' : lambda x: x,
+ 'PoCThreshold' : lambda x: float(x),
+ 'PointCarac' : lambda x: float(x),
+ 'calibSens' : lambda x: float(x),
+ 'glass' : lambda x: float(x),
+ 'limitSlide' : lambda x: eval(x),
+ 'nbParts' : lambda x: int(x),
+ 'poisson_ratio' : lambda x: float(x),
+ 'recomputePoC' : lambda x: eval(x),
+ 'relativeMaxDist' : lambda x: int(x),
+ 'sizeParts' : lambda x: int(x)}
+
+S_HEADER_064_2_CURRENT = {
+ 'hertz_model' : 'HertzModel',
+ 'poc_method' : 'PoCMethod',
+ 'poc_threshold' : 'PoCThreshold',
+ 'point_carac' : 'PointCarac',
+ 'calib_sens' : 'calibSens',
+ 'glass' : 'glass',
+ 'limit_slide' : 'limitSlide',
+ 'nb_parts' : 'nbParts',
+ 'poisson_ratio' : 'poisson_ratio',
+ 'recompute_poc' : 'recomputePoC',
+ 'relative_max_dist' : 'relativeMaxDist',
+ 'size_parts' : 'sizeParts'}
+
+STIFFNESS_HEADER = {
+ 'hertz_model' : lambda x: x,
+ 'fit_method' : lambda x: x,
+ 'poc_method' : lambda x: x,
+ 'poc_threshold' : lambda x: float(x),
+ 'point_carac' : lambda x: float(x),
+ 'calib_sens' : lambda x: float(x),
+ 'glass' : lambda x: float(x),
+ 'limit_slide' : lambda x: eval(x),
+ 'nb_parts' : lambda x: int(x),
+ 'poisson_ratio' : lambda x: float(x),
+ 'recompute_poc' : lambda x: eval(x),
+ 'relative_max_dist' : lambda x: int(x),
+ 'size_parts' : lambda x: int(x),
+ 'poc_len_slice' : lambda x: float(x),
+}
+
+if __name__ == '__main__':
+ load_fv('../../../docs/data/files/aex_xml/0.1a64/fvfile.xml')
diff --git a/openfovea/fovea_toolbox/file_util/afm_file.py b/openfovea/fovea_toolbox/file_util/afm_file.py
new file mode 100644
index 0000000..0f2b23b
--- /dev/null
+++ b/openfovea/fovea_toolbox/file_util/afm_file.py
@@ -0,0 +1,353 @@
+#! /usr/bin/python
+# -*- coding: utf-8 -*-
+'''
+This module is used to parse the files form Digital Instrument AFM
+'''
+__author__ = "Charles Roduit <charles.roduit at gmail.com>"
+__date__ = "01.07.2007"
+__license__ = "GNU Public License (GPL) version 3"
+__version__ = "0.1"
+
+import os
+import csv
+import zipfile
+
+import numpy
+from struct import unpack
+import re
+
+import nanoscope
+import jpk
+import asylum
+import csem
+
+
+def load(file_name):
+ """
+ Get the type of the file.
+
+ >>> [header, deflection_av, deflection_re, plotx, piezo] = \
+ load(ASYLUM5_FILE)
+ >>> print plotx.shape
+ (42622,)
+ >>> print deflection_av.shape
+ (1, 1, 42622)
+ >>> [header, deflection_av, deflection_re, plotx, piezo] = \
+ load(ASYLUM5_DIR)
+ >>> print deflection_av.shape
+ (8, 8, 301)
+
+ * Parameters :
+ file_name : str
+ The file name (with the complete path) to load.
+
+ * Returns :
+ header : dict
+ The header dictionnary.
+ trace : 3D numpy.array
+ Contains the 3d array of the trace deflection.
+ trace[0,0,:] is the curve at pos_x = 0, pos_y = 0
+ retrace : 3D numpy.array
+ Contains the 3d array of the retrace deflection.
+ retrace[0,0,:] is the curve at pos_x = 0, pos_y = 0
+ plotx : 1D numpy.array
+ Contains the piezo positions (x scale).
+ piezo : 2D array
+ The height of the piezo at the end of the indentation.
+ """
+
+ value = {
+ 'header': None,
+ 'trace_x': None,
+ 'retrace_x': None,
+ 'trace_y': None,
+ 'retrace_y': None,
+ 'piezo': None,
+ 'data_fid': None}
+ if os.path.isdir(file_name):
+ #print('directory')
+ # User wants to load a directory...
+ # Perhaps it's better to choose a single file...
+ if is_asylum_dir(file_name):
+ # This is an asylum directory.
+
+ __value = asylum.load(file_name)
+ value['piezo'] = __value['piezo']
+ value['data_fid'] = __value['fid']
+ value['header'] = __value['header']
+ header = __value['header']
+ #[trace, retrace, value['piezo'], header] = asylum.load(file_name)
+ #value['trace_y'] = trace[1]
+ #value['retrace_y'] = retrace[1]
+ #piezo = numpy.zeros(deflection_av.shape[0:2])
+ #value['trace_x'] = trace[0]
+ #value['retrace_x'] = retrace[0]
+ else:
+ raise TypeError('Not a supported AFM file format.')
+ elif is_nanoscope(file_name):
+ #print('Nanoscope')
+ header = nanoscope.lect_header(file_name)
+ [value['trace_y'], value['retrace_y']] = nanoscope.load_force_curves(
+ file_name, header)
+ value['piezo'] = nanoscope.load_image(file_name, header)
+ # Creates the x vector
+ value['trace_x'] = numpy.arange(header['force_samples_per_curve']) * \
+ header['x_factor']
+ value['retrace_x'] = value['trace_x']
+ header = nanoscope.clean_header(header)
+ elif is_jpk(file_name):
+ #print('jpk')
+ __value = jpk.load(file_name)
+ value['data_fid'] = __value['fid']
+ header = __value['header']
+# print header
+# trace_x = trace[0]
+# retrace_x = retrace[0]
+# trace_y = trace[1]
+# retrace_y = retrace[1]
+ #value['piezo'] = jpk.load_piezo(header, value['data_fid'])
+ #piezo = numpy.zeros(trace_y.shape[0:2])
+ value['piezo'] = __value['piezo']
+ elif is_jpk_single(file_name):
+# print('jpk_single')
+ [trace, retrace, header] = jpk.load_single(file_name)
+ value['trace_x'] = trace[0]
+ value['retrace_x'] = retrace[0]
+ value['trace_y'] = trace[1]
+ value['retrace_y'] = retrace[1]
+ value['piezo'] = numpy.zeros(value['trace_y'].shape[0:2])
+ elif is_asylum(file_name):
+ #print('asylum')
+ [trace, retrace, value['piezo'], header] = asylum.load(file_name)
+ value['trace_x'] = trace[0]
+ value['retrace_x'] = retrace[0]
+ value['trace_y'] = trace[1]
+ value['retrace_y'] = retrace[1]
+ value['piezo'] = numpy.zeros(value['trace_y'].shape[0:2])
+ elif csem.is_csem(file_name):
+ [trace, retrace, header, piezo] = csem.load(file_name)
+ value['trace_x'] = trace[0]
+ value['retrace_x'] = retrace[0]
+ value['trace_y'] = trace[1]
+ value['retrace_y'] = retrace[1]
+ value['piezo'] = piezo
+ else:
+ #print ('Not recognize')
+ raise TypeError('Not a supported AFM file format.')
+ # Add header for OpenFovea
+ header['event_dist_thresh'] = 0.0
+ header['thresh_event_fit_length'] = [None, None]
+ header['thresh_event_fit_plength'] = [None, None]
+ header['event_fit_model'] = None
+
+ value['header'] = header
+ #print value['piezo']
+ return value
+
+
+def is_nanoscope(file_name):
+ """
+ Test if the file comes from a veeco di nanoscope microscope
+
+ >>> is_nanoscope(NANOSCOPE_FILE)
+ True
+ >>> is_nanoscope(JPK_FILE)
+ False
+ """
+
+ file_id = open(file_name, 'rb')
+ line_in_mem = file_id.readline()
+ if not '\*Force file list' in line_in_mem:
+ # This is not a nanoscoe file
+ return False
+ else:
+ return True
+
+
+def is_jpk(file_name):
+ '''
+ Test if the file comes from a jpk microscope.
+
+ >>> is_jpk(JPK_FILE)
+ True
+ >>> is_jpk(ASYLUM5_FILE)
+ False
+ '''
+ this_file = open(file_name)
+ this_csv = csv.reader(this_file, delimiter=' ')
+ ftype = None
+ try:
+ first_line = this_csv.next()
+ ftype = 'csv'
+ except: # [csv.Error, StopIteration], reason:
+ pass
+ # return False
+ try:
+ fid = zipfile.ZipFile(file_name)
+ ftype = 'zip'
+ except:
+ pass
+ if ftype == 'csv':
+ if len(first_line) == 1:
+ return False
+ if not 'scanner:' in first_line[1]:
+ return False
+ else:
+ return True
+ elif ftype == 'zip':
+ if 'header.properties' in fid.namelist():
+ return True
+ else:
+ return False
+
+
+def is_jpk_zip(file_name):
+ '''
+ Test if the file is a zipped jpk
+ '''
+
+
+def is_jpk_single(file_name):
+ """
+ Test if the file comes from a jpk microscope and is made of a single
+ fc.
+ """
+ this_file = open(file_name)
+ this_csv = csv.reader(this_file, delimiter=' ')
+ try:
+ first_line = this_csv.next()
+ except csv.Error:
+ return False
+ if len(first_line) == 1:
+ return False
+ if not 'xPosition:' in first_line[1]:
+ return False
+ else:
+ return True
+
+
+def is_asylum(file_name):
+ """
+ Test if the file comes from an asylum microscope.
+
+ >>> is_asylum(ASYLUM5_FILE)
+ True
+ >>> is_asylum(NANOSCOPE_FILE)
+ False
+ """
+ if os.path.isdir(file_name):
+ return False
+ file_id = open(file_name)
+ ver_igor = unpack('h', file_id.read(2))[0]
+ if ver_igor in [1, 2, 3, 5]:
+ return True
+ else:
+ return False
+
+
+def is_asylum_dir(file_name):
+ """
+ Test if the directory is from asylum.
+
+ >>> is_asylum_dir(ASYLUM5_DIR)
+ True
+ """
+ if not os.path.isdir(file_name):
+ return False
+ dir_list = asylum.get_dir_list(file_name)
+ if len(dir_list) == 0:
+ return False
+ file_list = os.listdir(os.path.join(file_name, dir_list[0]))
+ _good_shape = [bool(re.match("Line\d+Point\d+.ibw", x)) for x in file_list]
+ if True in _good_shape:
+ file_list[0]
+ file_name = os.path.join(file_name, dir_list[0],
+ file_list[0])
+ return is_asylum(file_name)
+ else:
+ return False
+
+
+def load_curve(fid, pos, dtype, header, origin='relative'):
+ """
+ Load the curve on the fly.
+
+ * Parameters :
+ fid : instance of file id (obtained when opening a file)
+
+ pos : list : [pos_x, pos_y]
+ The position of the curve to load.
+
+ dtype : str
+ the curve data type to load.
+ 'trace_x', 'trace_y', 'retrace_x', 'retrace_y'
+ header : dict
+ The microscope type.
+ Currently supported values are : 'JPK', 'Asylum'
+ origin : str
+ If the origin is 'relative', the curve beginns at 0.
+ If the origin is 'absolute', the curve is not modified.
+
+ * Returns :
+ curve : array
+ """
+
+ if header['Microscope'] == 'JPK':
+ curve = jpk.load_curve(fid, pos, dtype)
+ #return jpk.load_curve(fid, pos, dtype, header['index_array'])
+ elif header['Microscope'] == 'Asylum':
+ curve = asylum.load_curve(fid, pos, dtype, header['index_array'])
+ else:
+ raise TypeError('The microscope type %s is not supported.' %
+ header['Microscope'])
+# if origin == 'relative':
+# if 'x' in dtype:
+# curve = curve - curve[0]
+# elif 'y' in dtype:
+# curve = curve - curve[-1]
+ if curve is None:
+ return curve
+# if 'x' in dtype and curve[0] > curve[-1]:
+# curve = curve[::-1]
+# elif 'y' in dtype and curve[0] < curve[-1]:
+# curve = curve[::-1]
+ return curve
+
+
+def force_load_array(fid, header):
+ """
+ This function loads the arrays from the NanoWizard3 zipped files.
+
+ * Parameters :
+ fid : instance of file id (obtained when opening a file)
+
+ header : dict
+ The microscope type.
+ Currently supported values are : 'JPK'
+
+ * Returns :
+ dictionnary with :
+ 'Index' : The index array (i.e. the folder name in the
+ corresponding pixel) This index is generated from
+ the coordinate in the header of each curve. This
+ is useful if the index array automatically
+ generated is wrong.
+ 'Piezo' : The piezo array. This is the height of the piezo
+ at the end of the indentation. This array is used
+ to generate the "zero force image" or "Topography
+ image".
+ """
+
+ if header['Microscope'] == 'JPK':
+ return jpk.load_arrays(fid)
+
+if __name__ == '__main__':
+ ASYLUM5_FILE = '../../../docs/data/files/ASYLUM/Cell0005.ibw'
+ NANOSCOPE_FILE = '../../../docs/data/files/FR2310B.004'
+ JPK_FILE = \
+ '../../../docs/data/files/JPK/mappp-data-2008.11.07-11.43.24.map_txt'
+ ASYLUM5_DIR = '/home/charles/AFM/Asylum Files/Figures/Archive'
+ ASYLUM5_DIR = \
+ '/home/charles/AFM/Robert_Ross/cropstack cell1/cell100-Cropped fmap'
+ import doctest
+ doctest.testmod()
diff --git a/openfovea/fovea_toolbox/file_util/asylum.py b/openfovea/fovea_toolbox/file_util/asylum.py
new file mode 100644
index 0000000..41e28c3
--- /dev/null
+++ b/openfovea/fovea_toolbox/file_util/asylum.py
@@ -0,0 +1,524 @@
+#! /usr/bin/env python
+#-*- coding: iso-8859-1 -*-
+
+"""
+ This module imports files from asylum microscopes. To use it make as
+ follow :
+
+ >>> [trace, retrace, header, piezo] = load(ASYLUM5_FILE)
+ >>> len(trace)
+ 2
+ >>> len(trace[0]) == len(retrace[0])
+ True
+
+ Trace[0] is the curve x and trace[1] is the array containing the y values
+ for all the force distance curves.
+
+ >>> len(trace[0]) == len(trace[1][0][0])
+ True
+
+ If you want to load a complete force spectroscopy data set :
+
+ >>> [trace, retrace, header, piezo] = load(ASYLUM5_DIR)
+ >>> print trace[1].shape # The y arrays
+ (8, 8, 301)
+ >>> print trace[0].shape # The x array
+ (301,)
+"""
+
+__author__ = "Charles Roduit <charles.roduit at gmail.com>"
+__date__ = "20.07.2010"
+__license__ = "GNU Public License (GPL) version 3"
+__version__ = "0.1"
+
+from struct import unpack
+import os
+from datetime import datetime
+import numpy
+import re
+
+from common import Variables
+import afm_file
+#from afm_file import, is_asylum, is_asylum_dir
+GLOB_VARS = Variables()
+
+def load(file_name):
+ """
+ Load asylum file. It detects automatically if the given path is a file
+ or is an asylum path.
+
+ * Parameters :
+ file_name : str
+ The complete path to open. The path contains the file or the
+ asylum path to open.
+
+ * Returns :
+ trace : 3D numpy.array
+ Contains the 3d array of the trace deflection.
+ trace[0,0,:] is the curve at pos_x = 0, pos_y = 0
+ retrace : 3D numpy.array
+ Contains the 3d array of the retrace deflection.
+ retrace[0,0,:] is the curve at pos_x = 0, pos_y = 0
+ header : dict
+ Contains usefull information to understand the curves.
+ piezo : 2D numpy.array
+ The piezo position at the end of the indentation.
+
+ To detect the type of the file see is_asylum and is_asylum_dir in
+ afm_file module.
+ """
+ value = {
+ 'trace_x' : None,
+ 'trace_y' : None,
+ 'retrace_x' : None,
+ 'retrace_y' : None,
+ 'header' : None,
+ 'fid' : None
+ }
+ if afm_file.is_asylum(file_name):
+ piezo = None
+ trace, retrace, header, piezo = get_file(file_name)
+ # reshape arrays to be a 3d array.
+ for key in trace:
+ trace[key].shape = (1, 1, trace[key].shape[0])
+ for key in retrace:
+ retrace[key].shape = (1, 1, retrace[key].shape[0])
+ value['trace_x'] = trace['lvdt']
+ value['trace_y'] = trace['defl']
+ value['retrace_x'] = retrace['lvdt']
+ value['retrace_y'] = retrace['defl']
+ value['header'] = header
+ value['piezo'] = piezo
+ elif afm_file.is_asylum_dir(file_name):
+ value['fid'], value['header'], value['piezo'] = load_dir(file_name)
+ #trace, retrace, header, piezo = load_dir(file_name)
+ value['piezo'] = value['piezo'] - value['piezo'].min()
+ return value
+
+def load_dir(dir_name):
+ """
+ Load asylum file contained in a directory.
+
+ * Parameters :
+ dir_name : str
+ The complete path to open.
+
+ * Returns :
+ trace : 3D numpy.array
+ Contains the 3d array of the trace deflection.
+ trace[0,0,:] is the curve at pos_x = 0, pos_y = 0
+ retrace : 3D numpy.array
+ Contains the 3d array of the retrace deflection.
+ retrace[0,0,:] is the curve at pos_x = 0, pos_y = 0
+ header : dict
+ Contains usefull information to understand the curves.
+ piezo : 2D numpy.array
+ The piezo position at the end of the indentation.
+ """
+ dir_list = get_dir_list(dir_name)
+ size = [len(dir_list),
+ len(get_file_list(os.path.join(dir_name, dir_list[0])))]
+ piezo = numpy.zeros((size[0], size[1]))
+ file_index = [['' for y in xrange(size[1])] for x in xrange(size[0])]#numpy.zeros((size[0], size[1]), dtype=str)
+ #trace = []
+ #retrace = []
+ header = []
+ for _item in dir_list:
+ folder_name = os.path.join(dir_name, _item)
+ file_list = get_file_list(folder_name)
+
+ #pos_x = list(dir_list).index(_item)
+ for _f_item in file_list:
+ # File names are LineXXXXPointYYYY.ibw.
+ # We extract the pos_x and pos_y :
+ pos_x = int(_f_item[_f_item.rindex('e')+1:_f_item.rindex('P')])
+ pos_y = int(_f_item[_f_item.rindex('t')+1:_f_item.rindex('.')])
+ file_index[pos_x][pos_y] = os.path.join(folder_name, _f_item)
+ # load the file...
+ # _item[0] is trace, _item[1] is retrace, _item[2] is header and
+ # _itme[3] is piezo
+ _item = get_file(os.path.join(folder_name, _f_item))
+ #pos_y = list(file_list).index(_f_item)
+ #trace.append(_item[0])
+ #retrace.append(_item[1])
+ header.append(_item[2])
+ piezo[int(pos_x), int(pos_y)] = _item[3]
+ #length = max([len(item.values()[0]) for item in trace] +
+ # [len(item.values()[0]) for item in retrace])
+ #trace = reorder_curves(trace, size[0], size[1], length)
+ #retrace = reorder_curves(retrace, size[0], size[1], length)
+ header = header[0]
+ header['size_x'] = size[0]
+ header['size_y'] = size[1]
+ header['size'] = (size[0], size[1])
+ header['scan_size'] = (header['scan_size'], header['scan_size'])
+ header['number_curves'] = size[0] * size[1]
+ header['data_location'] = dir_name
+ #return [trace, retrace, header, piezo]
+ return file_index, header, piezo
+
+def reorder_curves(data, size_x, size_y, length):
+ """
+ Reorder the curves to be in two 3d array, one for the trace and one for
+ the retrace curve.
+
+ * Parameters :
+ data : dict
+ contains a list of dictionnaries which contains at least 'lvdt'
+ and 'defl'.
+ e.g. data[0]['defl'] = array_like
+ size_x : int
+ The x size of the final array.
+ size_y : int
+ The y size of the final array.
+ length : int
+ The z size of the final array (the length of the curves).
+
+ * Returns :
+ data : dict
+ data['lvdt'] : numpy.array
+ Contains the 3d array of the piezo position
+ data['lvdt'][0,0,:] is the curve at pos_x = 0, pos_y = 0
+ data['defl'] : numpy.array
+ Contains the 3d array of the deflection
+ data['lvdt'][0,0,:] is the curve at pos_x = 0, pos_y = 0
+ """
+ # Create the dictionnary that will contain arrays of the correct size
+ out = {}
+ for key in ['lvdt', 'defl']:#data[0].keys():
+ out[key] = numpy.empty((size_x, size_y+1, length))
+ # Fill them with the complete curves
+ pos = -1
+ for item in data:
+ pos += 1
+ for key in out:
+ if key == 'lvdt':
+ out[key][pos/size_x, pos % size_x, :] = complete_array(
+ item[key],
+ length,
+ 'add')
+ else:
+ out[key][pos/size_x, pos % size_x, :] = complete_array(
+ item[key],
+ length)
+ return out
+
+def complete_array(array, length, add_type='idem'):
+ """
+ Complete the array with arbitrary values at the end.
+
+ * Parameters :
+ array : array_like
+ The array to be completed.
+ length : int
+ The final length
+ add_type : str
+ The type of completion (*default* : 'idem')
+
+ * 'idem' : adds the same values at the end of the array
+ * 'add' : adds cummulative (n = n-1 + (n-2 - n-1)) so that the
+ arrays increase linearly at the end.
+
+ * Returns :
+ array : numpy array
+ The completed array.
+ """
+ array = list(array)
+ if add_type == 'idem':
+ array = numpy.r_[array, [array[-1]] * (length - len(array))]
+ elif add_type == 'add':
+ step = array[-1] - array[-2]
+ start = array[-1] + step
+ stop = start + step * (length - len(array))
+ array = numpy.r_[array, numpy.arange(start, stop, step)]
+ return array
+
+def get_file(file_name):
+ """
+ Get the raw data and header. Need to be ordered to be incorporated in
+ OpenFovea.
+
+ * Parameters :
+ file_name : str
+ The complete path for the file to open.
+
+ * Returns :
+ trace : dict
+ Contains the arrays for the trace curve. This dictionnary is
+ composed of two 1D numpy array.
+
+ * trace['lvdt'] for the x axis
+ * trace['defl'] for the deflection
+ retrace : dict
+ Contains the arrays for the retrace curve. This dictionnary is
+ composed of two 1D numpy array.
+
+ * retrace['lvdt'] for the x axis
+ * retrace['defl'] for the deflection
+ header : dict
+ The note dictionnary
+ piezo : float
+ The height of the piezo at the end of the indentation
+ """
+ header = {}
+ file_id = open(file_name, 'rb')
+ ver_igor = unpack('=' + 'h', file_id.read(2))[0]
+ # Currently, only version 5 is supported.
+ if ver_igor == 5:
+ order = GLOB_VARS.asylum5_order
+ header_list = GLOB_VARS.asylum5_header
+ # Get the headers
+ for item in order:
+ _ud = header_list[item]
+ header[item] = _ud[2](unpack('='+_ud[0], file_id.read(_ud[1])))
+ # Some more information from the headers
+ if header['type'] != 0:
+ _ud = TYPE[header['type']]
+ data = unpack('=' + str(header['nbr_points']) + _ud[0],
+ file_id.read(header['nbr_points'] * _ud[1]))
+ if ver_igor == 5:
+ _len = header['formula_size']
+ header['formula_data'] = unpack('=' + str(_len)+'s',
+ file_id.read(_len))[0]
+ _len = header['note_size']
+ note = unpack('=' + str(_len)+'s', file_id.read(_len))
+ _len = header['data_eunit_size']
+ header['extended_data_units'] = unpack('=' + str(_len) + 's',
+ file_id.read(_len))[0]
+ header['dimension_units'] = [
+ unpack('=' + str(_len) + 'c', file_id.read(_len))
+ for _len in header['dim_eunit_size']
+ ]
+ header['dimension_label'] = [
+ unpack('=' + str(_len) + 's', file_id.read(_len))
+ for _len in header['dim_label_size']
+ ]
+ _len = header['string_indice_size']
+ header['string_indice'] = unpack('=' + str(_len) + 'l',
+ file_id.read(_len * 4))
+ data = numpy.asanyarray(data)
+ if header['n_dim'][2]:
+ raise TypeError('Problem in asylum file.\n'+
+ 'Want to reshape from array : %s to %s' %
+ (data.shape, (header['n_dim'][1], header['n_dim'][0])))
+
+ # data.shape = (header['n_dim'][1], header['n_dim'][0], header['n_dim'][2])
+ else:
+ data.shape = (header['n_dim'][1], header['n_dim'][0])
+ # I should reorder the header to have the minimal.
+ header = parse_note(note[0])
+ file_id.close()
+ average = average_signal(data[1], data[2], final_length=512)
+ # Get the middle point to separate the trace from the retrace curve
+ middle_point = numpy.nonzero(average[0] == average[0].max())[0][0]
+ trace = {
+ 'defl' : average[1][:middle_point][::-1] * 1e9,
+ 'lvdt' : average[0][:middle_point][::-1] * -1e9,
+ }
+ retrace = {
+ 'defl' : average[1][middle_point:]* 1e9,
+ 'lvdt' : average[0][middle_point:]* -1e9,
+ }
+ return [trace, retrace, header, data[2].max() * -1e9]
+
+def parse_note(note):
+ """
+ Extracts the usefull data from the note.
+
+ * Parameters :
+ note : dict
+ The note extracted from asylum file.
+
+ * Returns :
+ extracted : dict
+ The usefull note.
+ """
+ # Usefull data, and the way to extract them are contained in the global
+ # variable NOTE_INDEX.
+ extracted = GLOB_VARS.common_header
+ for key in NOTE_INDEX:
+ _text = NOTE_INDEX[key][0]
+ _extract = NOTE_INDEX[key][1]
+ _index = note.index(_text)
+ _end = note[_index+1:].index('\r') + _index + 1
+ sub_note = note[_index:_end]
+ extracted[key] = _extract(sub_note)
+ try:
+ extracted['date'] = datetime.strptime(
+ extracted['strdate'] + extracted['strtime'],
+ '%a, %b %d, %Y%I:%M:%S %p')
+ except ValueError:
+ extracted['date'] = datetime.strptime(
+ extracted['strdate'] + extracted['strtime'],
+ '%Y-%m-%d%I:%M:%S %p')
+ extracted['Microscope'] = 'Asylum'
+ return extracted
+
+def get_dir_list(folder):
+ """
+
+ Gets the folder list of an asylum foler.
+
+ Asylum folders are organized like this :
+
+ $ ls FMLine000
+ Line0000Point0000.ibw Line0000Point0001.ibw
+ $ ls FMLine001
+ Line0001Point0000.ibw Line0001Point0001.ibw
+
+ get_asylum_folder_list simply returns the ordered folder list that match
+ this organization.
+
+ * Parameters :
+ folder : string
+ The path for the folder.
+
+ * Returns :
+ folder_list : list
+ The list of the folders.
+ """
+ folder_list = os.listdir(folder)
+ # we should have the first folder being filenameNNNN
+ f_index = [bool(re.match("FMLine\d+", x)) for x in folder_list]
+ try:
+ basename = folder_list[f_index.index(True)][:-4]
+ except ValueError:
+ # There is no True elements in this folder...
+ folder_list = []
+ else:
+ f_index = [bool(x.find(basename) + 1) for x in folder_list]
+ f_index = numpy.asarray(f_index)
+ folder_list = numpy.asarray(folder_list)
+ # Get only the correct items
+ folder_list = folder_list[f_index]
+ folder_list.sort()
+ return folder_list
+
+def get_file_list(folder):
+ """
+ Gets the file list from an asylum folder.
+
+ * Parameters :
+ folder : string
+ The path for the folder.
+
+ * Returns :
+ file_list : list
+ The list of the files.
+ """
+ # Sort the file list
+ file_list = numpy.asarray(os.listdir(folder))
+ file_index = numpy.asarray([bool(x.find('.ibw')) for x in file_list])
+ file_list = file_list[file_index]
+ file_list.sort()
+ return file_list
+
+def load_curve(fid, pos, dtype, curve_index):
+ """
+ Load the curve on the fly.
+
+ * Parameters :
+ fid : instance of file id (obtained when opening a file)
+
+ pos : list : [pos_x, pos_y]
+ The position of the curve to load.
+
+ dtype : str
+ 'trace_x', 'trace_y', 'retrace_x', 'retrace_y'
+
+ * Returns :
+ curve : arraytrace['lvdt']
+ """
+ if type(fid) == list:
+ data = get_file(fid[pos[0]][pos[1]])
+ else:
+ fcfolder = os.path.join('FMLine%04i'%pos[0], 'Line%04iPoint%04i.ibw'%(pos[0], pos[1]))
+ file_path = os.path.join(fid, fcfolder)
+ data = get_file(file_path)
+ if 'retrace' in dtype:
+ data = data[1]
+ elif 'trace' in dtype:
+ data = data[0]
+ if 'x' in dtype:
+ data = data['lvdt']
+ elif 'y' in dtype:
+ data = data['defl']
+ #print fid[pos[0]][pos[1]]
+ #print pos
+ #print dtype
+ #print curve_index
+ return data
+def average_signal(signal, x_scale, win_size=100, final_length=None):
+ """
+ Average the signal versus time.
+
+ * Parameters :
+ signal : array_like
+ the array which contains the signal
+ x_scale : array_like
+ The array which contains the x scale.
+ win_size : int
+ The size (in pixel) of the window (*Default* = 100)
+
+ * Returns :
+ average_x : array_like
+ The array that contains the new x scale.
+ average : array_like
+ The array that contains the averaged signal
+ """
+ begin = 0
+ end = len(signal)
+ if final_length is not None:
+ win_size = int(end / final_length)
+ t_slice = numpy.arange(begin, end, win_size)
+ average = []
+ average_x = []
+ for begin, end in zip(t_slice[:-1], t_slice[1:]):
+ average.append(signal[begin:end].mean())
+ average_x.append(x_scale[begin])
+
+ return [numpy.array(average_x), numpy.array(average)]
+
+
+NOTE_INDEX = {'spring_constant' : ['\rSpringConstant:',
+ lambda x : float(x[16:])],
+ 'scan_size' : ['\rScanSize:', lambda x : float(x[10:]) * 1e6],
+ 'size_x' : ['\rScanPoints:', lambda x : int(x[12:])],
+ 'size_y' : ['\rScanLines:', lambda x : int(x[11:])],
+ 'sensit_deflection' : ['\rTriggerDeflection:',
+ lambda x : float(x[19:])],
+ 'strdate' : ['\rDate',
+ lambda x : x[6:]],
+ 'strtime' : ['\rTime',
+ lambda x : x[6:]],
+ 'piezo_pos' : ['\rExtendZ:', lambda x : float(x[9:]) * 1e6],
+ 'number_curves': ['\rXOPVersion', lambda x : 1], # just to force 1
+ }
+MIN_HEADER = ['scan_size', 'number_curves', 'size_x', 'size_y',
+ 'spring_constant', 'sensit_deflection', 'date']
+TYPE = {2 : ['f', 4], #'float',
+ 8 : ['c', 1], #'char',
+ 16 : ['i', 4], #'int16',
+ 32 : ['l', 4],#'int32',
+ 72 : None, #'ubit8',
+ 80 : None, #'uint16',
+ 96 : None, #'uint',
+ }
+
+if __name__ == '__main__':
+# import sys
+# sys.path.append(os.path.abspath('./'))
+ ASYLUM5_FILE = '../../../docs/data/files/Asylum/Cell0005.ibw'
+ ASYLUM5_DIR = '/home/charles/AFM/Asylum Files/Figures/Archive'
+ ASYLUM5_DIR2 = '/home/charles/AFM/Asylum Files/Figures/remove/CPA22_00'
+# import pylab
+# TRACE, RETRACE, HEADER, PIEZO = load(ASYLUM5_DIR2)
+# pylab.plot(TRACE[0], TRACE[1][0][0], 'ro')
+# pylab.hold(True)
+# pylab.plot(RETRACE[0], RETRACE[1][0][0], 'bo')
+# pylab.xlabel('Scanner extention [nm]')
+# pylab.ylabel('Cantilever deflection [nm]')
+# pylab.show()
+# print header
+ import doctest
+ doctest.testmod()
diff --git a/openfovea/fovea_toolbox/file_util/common.py b/openfovea/fovea_toolbox/file_util/common.py
new file mode 100644
index 0000000..2e52d0d
--- /dev/null
+++ b/openfovea/fovea_toolbox/file_util/common.py
@@ -0,0 +1,288 @@
+# The header of a force volume file
+from datetime import datetime
+import re
+
+
+class Variables(object):
+
+ def __init__(self):
+ pass
+
+ def __getattribute__(self, key):
+ if key == 'common_header':
+ return {
+ 'data_location': 'local', # The location of the data. If
+ # local, all the data are in memory. Otherwise, the relative
+ # path to the data file is specified here.
+ 'index_array': None, # The index array. If the data are not
+ # local, the fd curves are located in separate folders. This
+ # index array contains the location for each fd curve.
+ 'Microscope': None, # The type of the microscope used.
+ 'spring_constant': None, # The spring constant of the
+ # cantilever. If the data needs no conversion, value is 1.
+ 'scan_size': None, # The size of the scan (in nm).
+ # Tuple (x, y)
+ 'sensit_deflection': None, # The sensitivity deflection
+ 'number_curves': None, # The number of curves that the file
+ # contains
+ 'size_x': 0, # The size in x direction.
+ 'size_y': 0, # The size in y direction.
+ 'size': None, # The tuple size (x, y). Goal is to replace the
+ # two previous.
+ 'pixel_size': None, # The size of the pixel.
+ 'date': None, # The date of the exeperiment.
+ 'note': '/n', # Some notes that appears on the original file.
+ 'event_detect_weight': 2, # The weight of the event
+ # detection.
+ 'group': 0, # To which group this file belongs.
+ 'event_dist_thresh': 0.0, # The event distance threshold.
+ 'thresh_event_fit_length': [None, None],
+ 'thresh_event_fit_plength': [None, None],
+ # The event fit model. Look at curve.event_fit to see the
+ # available models.
+ 'event_fit_model': None,
+ # Tell if the event fit has to be aware or not about the point
+ # of contact.
+ 'event_fit_from_poc': False,
+ 'Masked': False} # A switch to mask or not the data.}
+ if key == 'fv_header':
+ return {'Microscope': None, # The type of the microscope used.
+ 'di_version': None, # TODO Nanoscope Specific
+ 'spring_constant': None, # The spring constant of the
+ # cantilever. If the data needs no
+ # conversion, value is 1.
+ 'force_bytes_per_pixel': None, # TODO Nanoscope Specific
+ 'hard_z_scale': None, # TODO Nanoscope Specific
+ 'image_mode': 0, # TODO Nanoscope Specific
+ # The mode. Only force volume is understood.
+ 'image_scale': None, # TODO Nanoscope Specific
+ 'scan_size': None, # The size of the scan (in nm)
+ 'sensit_deflection': None, # The sensitivity deflection
+ 'sens_z_scan': None, # TODO Nanoscope Specific
+ 'force_samples_per_curve': None, # TODO Nanoscope Specific
+ 'image_number_lines': None, # TODO Nanoscope Specific
+ 'number_curves': None, # The number of curves that the file
+ # contains
+ 'ramp_size': None, # TODO Nanoscope Specific
+ 'size_x': None, # The size in x direction.
+ 'size_y': None, # The size in y direction.
+ # 'pixel_size': None, # The size of the pixel.
+ 'date': None, # The date of the exeperiment.
+ 'force_data_length': None, # TODO Nanoscope Specific
+ 'force_data_offset': None, # TODO Nanoscope Specific
+ 'image_length': None, # TODO Nanoscope Specific
+ 'image_offset': None, # TODO Nanoscope Specific
+ 'image_samps_per_line': None, # TODO Nanoscope Specific
+ 'matrix_length': None, # TODO Nanoscope Specific
+ 'x_factor': 1, # TODO Nanoscope Specific
+ 'note': '/n', # Some notes that appears on the original file.
+ 'event_detect_weight': 2, # The weight of the event detection.
+ 'group': 0, # To which group this file belongs.
+ 'event_dist_thresh': 0.0, # The event distance threshold.
+ 'thresh_event_fit_length': [None, None],
+ 'thresh_event_fit_plength': [None, None],
+ # The event fit model. Look at curve.event_fit to see the
+ # available models.
+ 'event_fit_model': None,
+ # Tell if the event fit has to be aware or not about the point
+ # of contact.
+ 'event_fit_from_poc': False,
+ 'Masked': False} # A switch to mask or not the data.
+ if key == 'jpk_connector':
+ return {'fastSize:': ('scan_size', lambda x: eval(x) * 1E6),
+ 'numPoints:': ('number_curves', eval),
+ 'iLength:': ('size_x', eval),
+ 'jLength:': ('size_y', eval),
+ 'springConstant:': ('spring_constant', eval),
+ 'sensitivity:': ('sensit_deflection', eval),
+ 'date:': ('date', lambda x: datetime.strptime(x,
+ '%a %b %d %H:%M:%S %Z %Y'))}
+ if key == 'jpk_zip_connector':
+ return {
+ 'quantitative-imaging-map.position-pattern.grid.ilength': 'size_x',
+ 'quantitative-imaging-map.position-pattern.grid.jlength': 'size_y',
+ 'force-scan-map.position-pattern.grid.ilength': 'size_x',
+ 'force-scan-map.position-pattern.grid.jlength': 'size_y'}
+ if key == 'jpk_fc_connector':
+ return {'zRelativeEnd:': ('z_end', lambda x: eval(x)),
+ 'zRelativeStart:': ('z_start', lambda x: eval(x)),
+ 'direction:': ('direction', lambda x: x)
+ }
+ if key == 'jpk_header':
+ return {'scanner': None,
+ 'gridPattern': None,
+ 'fastSize': None,
+ 'slowSize': None,
+ 'xOffset': None,
+ 'yOffset': None,
+ 'iLength': None,
+ 'jLength': None,
+ 'theta': None,
+ 'reflect': None,
+ 'numPoints': None,
+ 'forceSettings': None,
+ 'zRelativeStart': None,
+ 'zRelativeEnd': None,
+ 'forceBaseline': None,
+ 'forceBaselineMeasured': None,
+ 'forceRelativeSetpoint': None,
+ 'setpoint': None,
+ 'kLength': None,
+ 'zStartPauseOption': None,
+ 'zEndPauseOption': None,
+ 'tipsaverPauseOption': None,
+ 'feedbackMode': None,
+ 'traceScanTime': None,
+ 'retraceScanTime': None,
+ 'pauseBeforeFirst': None,
+ 'pauseAtStart': None,
+ 'pauseAtEnd': None,
+ 'pauseOnTipsaver': None,
+ 'scanner': None,
+ 'date': None}
+ if key == 'asylum5_header':
+ # The following is inspired from the file read_wave.m from ...
+ return {
+ 'checksum': ['h', 2, lambda x: x], # Check sum of header
+ # and wave header.
+ 'wfm_size': ['l', 4, lambda x: x], # Size of wave header.
+ 'formula_size': ['l', 4, lambda x: x[0]], # Size of the
+ # dependency formula (0 if none).
+ 'note_size': ['l', 4, lambda x: x[0]], # Size of the
+ # notes...
+ 'data_eunit_size': ['l', 4, lambda x: x[0]], # Size of the
+ # extended data units.
+ 'dim_eunit_size': ['4l', 16, lambda x: x], # Size of the
+ # extended dimension unit. Need all data
+ 'dim_label_size': ['4l', 16, lambda x: x], # Size of the
+ # dimension labels.
+ 'string_indice_size': ['l', 4, lambda x: x[0]], # Size of
+ # the indices if it's a text.
+ 'option1': ['l', 4, lambda x: x],
+ 'option2': ['l', 4, lambda x: x],
+ 'next': ['l', 4, lambda x: x],
+ 'creation_date': ['l', 4, lambda x: x],
+ 'modification_date': ['l', 4, lambda x: x],
+ 'nbr_points': ['l', 4, lambda x: x[0]],
+ 'type': ['h', 2, lambda x: x[0]], # The type of the data.
+ # See TYPE global.
+ 'd_lock': ['h', 2, lambda x: x], # ??
+ 'whpad1': ['c', 1, lambda x: x],
+ 'wh_version': ['h', 2, lambda x: x],
+ 'fseek': ['5c', 5, lambda x: x],
+ 'b_name': ['32c', 32, lambda x: x],
+ 'whpad2': ['l', 4, lambda x: x],
+ 'd_folder': ['l', 4, lambda x: x],
+ 'n_dim': ['4l', 16, lambda x: x],
+ 'sfa': ['4d', 32, lambda x: x],
+ 'sfb': ['4d', 32, lambda x: x],
+ 'data_units': ['4c', 4, lambda x: x],
+ 'dim_units0': ['4c', 4, lambda x: x],
+ 'dim_units1': ['4c', 4, lambda x: x],
+ 'dim_units2': ['4c', 4, lambda x: x],
+ 'dim_units3': ['4c', 4, lambda x: x],
+ 'fs_valid': ['h', 2, lambda x: x],
+ 'whpad3': ['h', 2, lambda x: x],
+ 'top_full_scale': ['d', 8, lambda x: x], # The max and
+ # max full scale value for wave.
+ 'bottom_full_scale': ['d', 8, lambda x: x], # The max and
+ # max full scale value for wave.
+ 'data_eunit': ['l', 4, lambda x: x], # Ignore...
+ 'dim_eunits': ['4l', 16, lambda x: x], # Ignore...
+ 'dim_labels': ['4l', 16, lambda x: x], # Ignore...
+ 'wave_note_h': ['l', 4, lambda x: x], # Ignore...
+ 'wh_unused': ['16l', 64, lambda x: x], # Ignore...
+ 'a_modified': ['h', 2, lambda x: x], # Ignore...
+ 'w_modified': ['h', 2, lambda x: x], # Ignore...
+ 'sw_modified': ['h', 2, lambda x: x], # Ignore...
+ 'use_bits': ['c', 1, lambda x: x], # Ignore...
+ 'kind_bits': ['c', 1, lambda x: x], # Ignore...
+ 'formula': ['l', 4, lambda x: x], # Ignore...
+ 'dep_id': ['l', 4, lambda x: x], # Ignore...
+ 'whpad4': ['h', 2, lambda x: x], # Ignore...
+ 'src_folder': ['h', 2, lambda x: x], # Ignore...
+ 'file_name': ['l', 4, lambda x: x], # Ignore...
+ 's_indices': ['l', 4, lambda x: x], # Ignore...
+ }
+ if key == 'asylum5_order':
+ # This is the order where the header must be readen.
+ return ['checksum', 'wfm_size', 'formula_size', 'note_size',
+ 'data_eunit_size', 'dim_eunit_size', 'dim_label_size',
+ 'string_indice_size', 'option1', 'option2', 'next',
+ 'creation_date', 'modification_date', 'nbr_points', 'type',
+ 'd_lock', 'whpad1', 'wh_version', 'fseek', 'b_name',
+ 'whpad2', 'd_folder', 'n_dim', 'sfa', 'sfb', 'data_units',
+ 'dim_units0', 'dim_units1', 'dim_units2', 'dim_units3',
+ 'fs_valid', 'whpad3', 'top_full_scale',
+ 'bottom_full_scale', 'data_eunit', 'dim_eunits',
+ 'dim_labels', 'wave_note_h', 'wh_unused', 'a_modified',
+ 'w_modified', 'sw_modified', 'use_bits', 'kind_bits',
+ 'formula', 'dep_id', 'whpad4', 'src_folder', 'file_name',
+ 's_indices']
+
+# For informations:
+"""
++===========+=======================+=======================+=================+
+| Format | C Type | Python type | Standard size |
++===========+=======================+=======================+=================+
+| x | pad byte | no value | |
++-----------+-----------------------+-----------------------+-----------------+
+| c | char | string of length 1 | 1 |
++-----------+-----------------------+-----------------------+-----------------+
+| b | signed char | integer | 1 |
++-----------+-----------------------+-----------------------+-----------------+
+| B | unsigned char | integer | 1 |
++-----------+-----------------------+-----------------------+-----------------+
+| ? | _Bool | bool | 1 |
++-----------+-----------------------+-----------------------+-----------------+
+| h | short | integer | 2 |
++-----------+-----------------------+-----------------------+-----------------+
+| H | unsigned short | integer | 2 |
++-----------+-----------------------+-----------------------+-----------------+
+| i | int | integer | 4 |
++-----------+-----------------------+-----------------------+-----------------+
+| I | unsigned int | integer | 4 |
++-----------+-----------------------+-----------------------+-----------------+
+| l | long | integer | 4 |
++-----------+-----------------------+-----------------------+-----------------+
+| L | unsigned long | integer | 4 |
++-----------+-----------------------+-----------------------+-----------------+
+| q | long long | integer | 8 |
++-----------+-----------------------+-----------------------+-----------------+
+| Q | unsigned long long | integer | 8 |
++-----------+-----------------------+-----------------------+-----------------+
+| f | float | float | 4 |
++-----------+-----------------------+-----------------------+-----------------+
+| d | double | float | 8 |
++-----------+-----------------------+-----------------------+-----------------+
+| s | char[] | string | |
++-----------+-----------------------+-----------------------+-----------------+
+| p | char[] | string | |
++-----------+-----------------------+-----------------------+-----------------+
+| P | void * | integer | |
++-----------+-----------------------+-----------------------+-----------------+
+"""
+
+
+def extract_num(file_id, pos=0):
+ '''
+ Etract number from a string
+ '''
+ number = re.findall('([0-9]+[.0-9]*)', file_id)[pos]
+ try:
+ number = int(number)
+ except ValueError:
+ number = float(number)
+ return number
+
+
+def extract_num_rev(file_id):
+ '''
+ Etract number from a string
+ '''
+ number = re.findall('([0-9]+[.0-9]*)', file_id)[-1]
+ try:
+ number = int(number)
+ except ValueError:
+ number = float(number)
+ return number
diff --git a/openfovea/fovea_toolbox/file_util/csem.py b/openfovea/fovea_toolbox/file_util/csem.py
new file mode 100644
index 0000000..a6e4f12
--- /dev/null
+++ b/openfovea/fovea_toolbox/file_util/csem.py
@@ -0,0 +1,82 @@
+#! /usr/bin/python
+# -*- coding: utf-8 -*-
+
+import csv
+import numpy
+
+from common import Variables
+GLOB_VARS = Variables()
+
+'''
+ Read file for the csem home made microscope.
+'''
+
+
+def is_csem(file_name):
+ this_file = open(file_name)
+ this_csv = csv.reader(this_file, delimiter='\t')
+ try:
+ line = this_csv.next()
+ except:
+ return False
+ else:
+ if line[0] == '':
+ return True
+ else:
+ return False
+
+
+def load(file_name):
+ '''
+ Test if the file is from csem.
+ '''
+ header = GLOB_VARS.common_header
+ header['Microscope'] = 'csem'
+ this_file = open(file_name)
+ this_csv = csv.reader(this_file, delimiter='\t')
+ array = []
+ for line in this_csv:
+ try:
+ float(line[0])
+ except:
+ if line[0] == '':
+ # list of the cantilever
+ cantilever = line
+ array_x = []
+ array_y = numpy.empty
+ elif line[0] in ['rad/nm']:
+ convertion = [1] + [float(item) for item in line[1:]]
+ elif line[0] == 'cste raideur levier N/m':
+ spring_cst = [float(item) for item in line[1:]]
+ elif line[0] == 'position scanner':
+ pos = line
+ else:
+ print('Unknown line :')
+ print line
+ else:
+ array.append([float(item) * conv
+ for item, conv in zip(line, convertion)])
+ # Make headers
+ header['spring_constant'] = spring_cst
+ header['number_curves'] = len(cantilever) - 1
+ # Rearange the arrays
+ array_all = numpy.asanyarray(array)
+ array_x = -array_all[:, 0]
+ array_y = array_all[:, 1:]
+ cut = array_x.argmin()
+ trace_x = array_x[:cut][::-1]
+ retrace_x = array_x[cut:]
+ trace_y = array_y[:cut, :][::-1]
+ retrace_y = array_y[cut:, :]
+ trace_y = trace_y.transpose(1, 0)
+ trace_y.shape = (trace_y.shape[0], 1, trace_y.shape[1])
+ retrace_y = retrace_y.transpose(1, 0)
+ retrace_y.shape = (retrace_y.shape[0], 1, retrace_y.shape[1])
+ # Add last header informations
+ header['size_x'] = trace_y.shape[0]
+ header['size_y'] = trace_y.shape[1]
+ header['size'] = [header['size_x'], header['size_y']]
+ header['scan_size'] = [header['size_x'], header['size_y']]
+ piezo = numpy.ones(trace_y.shape[0:2])
+ piezo = piezo * array_x[-1]
+ return [trace_x, trace_y], [retrace_x, retrace_y], header, piezo
diff --git a/openfovea/fovea_toolbox/file_util/jpk.py b/openfovea/fovea_toolbox/file_util/jpk.py
new file mode 100644
index 0000000..33b341e
--- /dev/null
+++ b/openfovea/fovea_toolbox/file_util/jpk.py
@@ -0,0 +1,1376 @@
+import csv
+import os
+import zipfile
+import tempfile
+from copy import deepcopy
+#from datetime import datetime
+from dateutil import parser as date_parser
+
+import numpy
+import Image
+
+from common import Variables
+GLOB_VARS = Variables()
+
+#CHANNEL_X = 'vDeflection'
+#CHANNEL_X = 'capacitiveSensorHeight' #
+CHANNEL_X = 'height' # 'capacitiveSensorHeight'
+
+def load(file_name):
+ '''
+ Loads jpk force volume files
+ '''
+ ftype = get_file_type(file_name)
+ value = {
+ 'adv_mat' : None,
+ 'ret_mat' : None,
+ 'header' : None,
+ 'fid' : None
+ }
+
+ if ftype == 'txt':
+ value['adv_mat'], value['ret_mat'], value['header'] = load_csv(file_name)
+ value['piezo'] = numpy.zeros(value['header']['size'])
+ elif ftype == 'zip':
+ value['fid'], value['header'] = load_zip(file_name)
+ value['piezo'] = None
+ return value
+
+def load_csv(file_name):
+
+ this_file=open(file_name)
+ header = get_header_csv(this_file)
+ this_file.seek(0)
+ this_csv=csv.reader(this_file, delimiter=' ')
+ index=-1
+ for line in this_csv:
+ if len(line) == 3:
+ # We are in the header
+ if line[1] == 'iLength:':
+ x_size = int(line[2])
+ elif line[1] == 'jLength:':
+ y_size = int(line[2])
+ elif line[1] == 'kLength:':
+ curve_length = int(line[2])
+ elif line[1] == 'index:':
+ index = int(line[2])
+ elif line[1] == 'iIndex:':
+ x_pos = int(line[2])
+ elif line[1] == 'jIndex:':
+ y_pos = int(line[2])
+ elif line[1] == 'direction:':
+ direction = line[2]
+ elif len(line) == 1:
+ # We are between headers
+ if index == -1:
+ # We are at the very beginning and have to build the matrices
+ # that will be used to store the data.
+ shape = (x_size, y_size, curve_length)
+ adv_mat = [numpy.zeros(shape) for i in range(4)]
+ ret_mat = [numpy.zeros(shape) for i in range(4)]
+ else:
+ data_pos = 0
+ pass
+ elif len(line) == 4:
+ # We are in the datas
+ if direction == 'trace':
+ (adv_mat[0][x_pos, y_pos, data_pos],
+ adv_mat[1][x_pos, y_pos, data_pos],
+ adv_mat[2][x_pos, y_pos, data_pos],
+ adv_mat[3][x_pos, y_pos, data_pos]) = line
+ elif direction == 'retrace':
+ (ret_mat[0][x_pos, y_pos, data_pos],
+ ret_mat[1][x_pos, y_pos, data_pos],
+ ret_mat[2][x_pos, y_pos, data_pos],
+ ret_mat[3][x_pos, y_pos, data_pos]) = line
+ data_pos += 1
+
+ # reorder the force curves...
+ adv_mat[0] = reorder_array(adv_mat[0], 1)
+ adv_mat[1] = reorder_array(adv_mat[1], header['spring_constant'])
+ adv_mat[2] = reorder_array(adv_mat[2], 1)
+ adv_mat[3] = reorder_array(adv_mat[3], 1)
+ ret_mat[0] = reorder_array(ret_mat[0], 1)
+ ret_mat[1] = reorder_array(ret_mat[1], header['spring_constant'])
+ ret_mat[2] = reorder_array(ret_mat[2], 1)
+ ret_mat[3] = reorder_array(ret_mat[3], 1)
+ return [adv_mat, ret_mat, header]
+
+def load_single(file_name):
+ """
+ Load jpk file made of a single force curve.
+ """
+ global_header = get_header_csv(file_name)
+ this_file=open(file_name)
+ this_csv=csv.reader(this_file, delimiter=' ')
+ index=-1
+ jpk_fv_con = GLOB_VARS.jpk_fc_connector
+ header = {}
+ vec_trace = [[],[],[],[]]
+ vec_retrace = [[],[],[],[]]
+ for line in this_csv:
+ if len(line) <= 1:
+ pass
+ elif line[0] == '#': # It's a header
+ if jpk_fv_con.has_key(line[1]):
+ key = jpk_fv_con[line[1]][0]
+ fct = jpk_fv_con[line[1]][1]
+ header[key] = fct(line[2])
+ else: # It's a data point
+ if header['direction'] == 'trace':
+ for item, vect in zip(line, vec_trace):
+ vect.append(item)
+ if header['direction'] == 'retrace':
+ for item, vect in zip(line, vec_retrace):
+ vect.append(item)
+ shape = (1,1,len(vec_trace[0]))
+ arr_trace = [numpy.zeros(shape) for i in range(4)]
+
+ shape = (1,1,len(vec_retrace[0]))
+ arr_retrace = [numpy.zeros(shape) for i in range(4)]
+
+ for i in range(4):
+ arr_trace[i][0,0,:] = numpy.asarray(vec_trace[i])
+ arr_retrace[i][0,0,:] = numpy.asarray(vec_retrace[i])
+ global_header['scan_size'] = (0, 0)
+ global_header['number_curves'] = 1
+ return [arr_trace, arr_retrace, global_header]
+
+def reorder_array(this_array, factor):
+ for x in range(this_array.shape[0]):
+ for y in range(this_array.shape[1]):
+ this_array[x,y,:] = this_array[x,y,::-1] * 1e9 / factor
+ this_array[x,y,:] = this_array[x,y,:] - min(this_array[x,y,:])
+ return this_array
+
+def get_header_csv(file_name):
+ """
+ Get the header from files that have headers and data in the same file.
+ """
+ this_file = file_name
+ this_csv=csv.reader(this_file, delimiter=' ')
+ jpk_con = GLOB_VARS.jpk_connector
+ header = GLOB_VARS.common_header
+ for line in this_csv:
+ if len(line) > 4:
+ d_string = ''
+ for x in line[2:]: d_string = d_string + str(x) + ' '
+ d_string = d_string[:-1] # erase the last space
+ line = [line[0], line[1], d_string]
+ if len(line) == 3:
+ # We are in the header
+ if jpk_con.has_key(line[1]):
+ key = jpk_con[line[1]][0]
+ fct = jpk_con[line[1]][1]
+
+ header[key] = fct(line[2])
+ header['matrix_length'] = header['size_x']
+ header['size'] = (header['size_x'], header['size_y'])
+ header['scan_size'] = (header['scan_size'], header['scan_size'])
+ return header
+
+
+def load_arrays(fid):
+ """
+ This function loads the arrays from the NanoWizard3 zipped files.
+
+ * Parameters :
+ None
+
+ * Returns :
+ dictionnary with :
+ 'Index' : The index array (i.e. the folder name in the
+ corresponding pixel) This index is generated from
+ the coordinate in the header of each curve. This
+ is useful if the index array automatically
+ generated is wrong.
+ 'Piezo' : The piezo array. This is the height of the piezo
+ at the end of the indentation. This array is used
+ to generate the "zero force image" or "Topography
+ image".
+ If the index of curves were missgenerated or lost, we can regenerate
+ them from the headers. This take long time.
+ """
+
+ main_header = get_header_zip(fid.open('header.properties'), htype='main')
+ shared_header = get_header_zip(fid.open('shared-data/header.properties'),
+ htype='shared')
+# ############################################################################
+# # Generate an index array :
+# #
+# index_array = numpy.empty((main_header['size_x'], main_header['size_y']),
+# dtype=int)
+# index_array[:]=-1
+ piezo_array = numpy.zeros((main_header['size_x'], main_header['size_y']),
+ dtype=float)
+#
+# pixel_size = [main_header['msize_x'] / main_header['size_x'],
+# main_header['msize_y'] / main_header['size_y']]
+# # works if center of array is [0, 0]. Here is to modify if we have
+# # different files.
+# center = [(-main_header['mcenter_x'] +
+# (main_header['msize_x'] / 2))/pixel_size[0],
+# (-main_header['mcenter_y'] +
+# (main_header['msize_y'] / 2))/pixel_size[1]]
+ for index_nb in range(main_header['index_min'], main_header['index_max']+1):
+ f_name = 'index/%s/header.properties'%index_nb
+ index_header = get_header_zip(fid.open(f_name),
+ htype='index')#, stop='position')
+ # Current pixel position :
+ pos = pos_from_index(index_nb,
+ size=[main_header['size_x'], main_header['size_y']],
+ mode=main_header['file_type'])
+# pos = [round(index_header['mpos_x'] / pixel_size[0] + center[0]
+# - 0.5),
+# round(index_header['mpos_y'] / pixel_size[1] + center[1]
+# - 0.5)]
+# index_array[pos[0], pos[1]] = index_nb
+ gf = _global_factor(shared_header, CHANNEL_X)
+
+ f_name = 'index/%i/segments/%i/'%(index_nb, 0)
+
+ sgmt_header = get_header_zip(fid.open(f_name+'segment-header.properties'),
+ htype='segment')
+ sgmt_name = sgmt_header['%s.fname' % CHANNEL_X]
+
+ curve = load_zip_curve(fid.open(f_name + sgmt_name),
+ dtype='signedinteger',
+ factor=gf)
+ piezo_array[pos[0], pos[1]] = curve[-1]*1e9
+ return {#'Index' : index_array,
+ 'Piezo' : piezo_array}
+
+
+def load_zip(file_name):
+ """
+ Load file from zipped JPK.
+
+ This will return only a header, as the FD curves will be loaded on
+ demand.
+ """
+
+ header = GLOB_VARS.common_header
+ fid = zipfile.ZipFile(file_name, 'r')
+
+ main_header = get_header_zip(fid.open('header.properties'), htype='main')
+ shared_header = get_header_zip(fid.open('shared-data/header.properties'),
+ htype='shared')
+ if main_header['size_unit'] == 'm':
+ main_header['msize_x'] = main_header['msize_x']*1e9
+ main_header['msize_y'] = main_header['msize_y']*1e9
+
+
+ header['number_curves'] = main_header['index_max'] - \
+ main_header['index_min'] + 1
+ header['scan_size'] = [main_header['msize_x'], main_header['msize_y']]
+ header['size'] = [main_header['size_x'], main_header['size_y']]
+ header['size_x'] = main_header['size_x']
+ header['size_y'] = main_header['size_y']
+ header['data_location'] = file_name
+ header['Microscope'] = 'JPK'
+ header['sensit_deflection'] = 1
+ header['spring_constant'] = shared_header['spring_constant']
+ return [fid, header]
+
+
+def _get_lambda_factor(offset, factor):
+ return lambda x: (x+offset)*factor
+
+
+def _global_factor(header, dtype):
+ """
+ Header should be the shared header.
+ dtype : str
+ Is the type of curve you want to load.
+ 'height', 'vDeflection'
+ """
+ n_index = header['name'].index(dtype)
+ convert_list = header['conversion_list'][n_index].split(' ')
+ gf = header['encode_multiplier'][n_index]
+ of = header['encode_offset'][n_index]
+ fct = [_get_lambda_factor(of, gf)]
+ for conv_item in convert_list:
+# t_fact = header[conv_item+'_multiplier'][n_index]
+# t_off = header[conv_item+'_offset'][n_index]
+ if conv_item+'_multiplier' in header:
+ if type(header[conv_item+'_multiplier']) == list:
+ #print shared_header[conv_item+'_multiplier'][n_index]
+ t_fact = header[conv_item+'_multiplier'][n_index]
+ else:
+ t_fact = header[conv_item+'_multiplier']
+ else:
+ t_fact=1
+ if conv_item+'_offset' in header:
+ if type(header[conv_item+'_offset']) == list:
+ #print shared_header[conv_item+'_multiplier'][n_index]
+ t_off = header[conv_item+'_offset'][n_index]
+ else:
+ t_off = header[conv_item+'_offset']
+ else:
+ t_off=0
+ fct.append(_get_lambda_factor(t_off, t_fact))
+ return fct
+
+
+def index_from_pos(pos, size, mode):
+ """
+ Generate the curve index in function of the curve position.
+ This can differ corresponding to the imaging mode used.
+
+ qi-map force-map
+ ... ...
+ ----------> ----------->
+ ----------> <-----------
+ ----------> ----------->
+ """
+ index = 0
+ if mode == 'force-scan-map':
+ if pos[1] % 2:
+ index = pos[1] * size[0] + (size[0] - pos[0] - 1)
+ else:
+ index = pos[1] * size[0] + pos[0]
+ elif mode == 'quantitative-imaging-map':
+ index = pos[1] * size[0] + pos[0]
+ else:
+ raise TypeError('Mode %s is not known' % mode)
+ return index
+
+def pos_from_index(index, size, mode):
+ """
+ Generate the curve position in function of the curve index.
+ This can differ corresponding to the imaging mode used.
+
+ qi-map force-map
+ ... ...
+ ----------> ----------->
+ ----------> <-----------
+ ----------> ----------->
+ """
+ pos = [0, 0]
+ if mode == 'force-scan-map':
+ pos[1] = int(index/(size[0]))
+ if pos[1] % 2:
+ pos[0] = size[0] - (index) % (size[0]) - 1
+ else:
+ pos[0] = index % (size[0])
+ elif mode == 'quantitative-imaging-map':
+ pos[1] = int(index/(size[0]))
+ pos[0] = index % (size[0])
+ else:
+ raise TypeError('Mode %s is not known' % mode)
+ return pos
+
+def load_curve(fid, index, dtype):
+ """
+ Load the curve on the fly.
+
+ * Parameters :
+ fid : instance of file id (obtained when opening a file)
+
+ mode : str
+ The imaging mode used (see index_from_pos for more details).
+
+ dtype : str
+ 'trace_x', 'trace_y', 'retrace_x', 'retrace_y'
+
+ * Returns :
+ curve : array
+
+ """
+
+ header = get_header_zip(fid.open('header.properties'), htype='main')
+
+ c_index = index_from_pos(index,
+ size=[header['size_x'], header['size_y']],
+ mode=header['file_type'])
+ # read the shared header...
+ shared_header = get_header_zip(fid.open('shared-data/header.properties'),
+ htype='shared')
+
+ # Read the header for this pixel...
+ if c_index < 0:
+ return None
+ sh_name = 'index/%i/header.properties'%c_index
+ segment_header=get_header_zip(fid.open(sh_name), htype='index')
+ if 'retrace' in dtype:
+ sgmt_nbr = 1
+ elif 'trace' in dtype:
+ sgmt_nbr = 0
+ f_name = 'index/%i/segments/%i/'%(c_index, sgmt_nbr)
+ sgmt_header = get_header_zip(fid.open(f_name+'segment-header.properties'),
+ htype='segment')
+ if 'x' in dtype:
+ sgmt_type = CHANNEL_X
+ elif 'y' in dtype:
+ sgmt_type = 'vDeflection'
+ # Creates the conversion factors :
+ gf = _global_factor(shared_header, sgmt_type)
+ sgmt_name = sgmt_header[sgmt_type+'.fname']
+ sgmt_format = sgmt_header[sgmt_type + '.format']
+ curve = load_zip_curve(fid.open(f_name + sgmt_name),
+ dtype='signedinteger',
+ factor=gf)
+ #curve = curve - min(curve)
+ curve = curve*1e9
+ return curve[::-1]
+
+
+def load_zip_curve(fid, stype='raw', dtype='signedinteger', factor=[lambda x:x]):
+ '''
+ load the curve in the file.
+
+ * Parameters :
+ fid : file object
+ curve file extracted from the zip file.
+ stype : str
+ save type. 'raw', 'ascii'
+ default set to 'raw'
+ * Returns :
+ curve : list
+ list of point that are loaded.
+ '''
+ dtype = '>' + DTYPES[dtype]
+ array_str = fid.read()
+ array = numpy.fromstring(array_str, dtype=dtype)
+ test = 10
+ for item in factor:
+ array = item(array)
+ test = item(test)
+
+ return array
+def get_header_zip(header_file, htype='main', stop=None):
+ '''
+ Get the headers from zip file.
+
+ * Parameters :
+ header_file : file object
+ header file extracted from the zip file.
+ htype : str
+ header type. 'main', 'index' or 'segment'.
+ default set to 'main'
+
+ stop : str or None
+ if define, the execution will stop as soon as the information
+ wanted is found.
+ values are : 'position'
+
+ * Returns :
+ header : dict
+ dictionnary with the header with the usefull key - value pair.
+ Values that are not usefull for OpenFovea are lost.
+ '''
+ # Every header has its first line as a date
+ date = date_parser.parse(header_file.readline()[1:])
+ if htype == 'main':
+ __header = ZIP_MAIN_HEADER
+ elif htype == 'index':
+ __header = ZIP_INDEX_HEADER
+ elif htype == 'segment':
+ __header = ZIP_SEGMENT_HEADER
+ elif htype == 'shared':
+ __header = ZIP_SHARED_HEADER
+ header = {}
+ header['date'] = date
+
+ for line in header_file.readlines():
+ line_parse = line.split('=')
+ if htype == 'shared':
+ head_parse = line_parse[0].split('.')
+ if head_parse[1].isdigit():
+ canal_nb = head_parse[1]
+ head_parse[1] = 'xx'
+ line_parse[0] = '.'.join(head_parse)
+
+ param = __header[line_parse[0]]
+ if htype == 'shared':
+ head_parse = param[0].split('.')
+ if len(head_parse) > 1 and (head_parse[1] == 'xx' or head_parse[1].isdigit()):
+ head_parse[1] = canal_nb
+ head_parse = '.'.join(head_parse)
+ param[0] = head_parse
+ if 'time' in line_parse[0]:
+ line_parse[1] = ''.join(line_parse[1].split('\\'))
+ if line_parse[1] in ['NaN', 'NaN\n']:
+ header[param[0]] = numpy.nan
+ else:
+ header[param[0]] = param[1](line_parse[1])
+ del(param)
+ if htype == 'shared':
+ # We reorder the header, so that we have one 'name' list, which
+ # contains the name of the channels and the converters as a 'multiplier'
+ # list which contains the corresponding converters.
+ new_header = {}
+ channel_list = {}
+ for item in header:
+ splitted = item.split('.')
+ if len(splitted) > 1 and splitted[1].isdigit():
+ if not new_header.has_key(splitted[0]):
+ new_header[splitted[0]] = \
+ [1 for i in range(header['channel_count'])]
+ new_header[splitted[0]][eval(splitted[1])] = header[item]
+ if splitted[0] == 'name':
+ channel_list[splitted[1]]=header[item]
+ else:
+ new_header[item] = header[item]
+ keys = channel_list.keys()
+ keys.sort()
+ new_header['channel_list'] = [channel_list[i] for i in keys]
+ header = new_header
+ header_file.close()
+ return header
+
+def get_file_type(file_name):
+ if zipfile.is_zipfile(file_name):
+ ftype = 'zip'
+ else:
+ ftype = 'txt'
+ return ftype
+
+DTYPES = {'signedinteger' : 'i',
+ 'unsignedinteger' : 'I'}
+
+ZIP_MAIN_HEADER = {"jpk-data-file" : ['trash', lambda x: str(x).strip('\n')],
+"file-format-version" : ['trash', lambda x: str(x).strip('\n')],
+"file-format-features" : ['trash', lambda x: str(x).strip('\n')],
+"type" : ['file_type', lambda x: str(x).strip('\n')],
+"quantitative-imaging-map.indexes.type" : ['trash', eval],
+"quantitative-imaging-map.indexes.min" : ['index_min', eval],
+"quantitative-imaging-map.indexes.max" : ['index_max', eval],
+"quantitative-imaging-map.scan-number" : ['trash', eval],
+"quantitative-imaging-map.start-time" :
+ ['trash',
+ lambda x: date_parser.parse(x)],
+"quantitative-imaging-map.end-time" :
+ ['trash',
+ lambda x: date_parser.parse(x)],
+"quantitative-imaging-map.description.comment" :
+ ['trash', lambda x: str(x).strip('\n')],
+"quantitative-imaging-map.description.name" :
+ ['trash', lambda x: str(x).strip('\n')],
+"quantitative-imaging-map.description.probe" :
+ ['trash', lambda x: str(x).strip('\n')],
+"quantitative-imaging-map.description.user-name" :
+ ['trash', lambda x: str(x).strip('\n')],
+"quantitative-imaging-map.description.instrument" :
+ ['trash', lambda x: str(x).strip('\n')],
+"quantitative-imaging-map.description.source-software" :
+ ['trash', lambda x: str(x).strip('\n')],
+"quantitative-imaging-map.description.modification-software" :
+ ['trash', lambda x: str(x).strip('\n')],
+"quantitative-imaging-map.settings.force-settings.type" :
+ ['trash', lambda x: str(x).strip('\n')],
+"quantitative-imaging-map.settings.force-settings.relative-setpoint" :
+ ['trash', eval],
+"quantitative-imaging-map.settings.force-settings.control-settings.control-settings-type" :
+ ['trash', lambda x: str(x).strip('\n')],
+"quantitative-imaging-map.settings.force-settings.control-settings.next-line-additional-delay" :
+ ['trash', eval],
+"quantitative-imaging-map.settings.force-settings.control-settings.next-line-additional-retract" :
+ ['trash', eval],
+"quantitative-imaging-map.settings.force-settings.control-settings.max-retries-per-position" :
+ ['trash', eval],
+"quantitative-imaging-map.settings.force-settings.control-settings.max-bad-adjecent-pixels" :
+ ['trash', eval],
+# Next, we capitalize the first letter => 'false' -> 'False' or 'true' -> 'True'
+"quantitative-imaging-map.settings.force-settings.control-settings.closed-loop" :
+ ['trash', lambda x: eval(x.title())],
+"quantitative-imaging-map.settings.force-settings.control-settings.start-option.type" :
+ ['trash', lambda x: str(x).strip('\n')],
+"quantitative-imaging-map.settings.force-settings.control-settings.start-option.additional-retract" :
+ ['trash', eval],
+"quantitative-imaging-map.settings.force-settings.control-settings.start-option.start-time" :
+ ['trash', eval],
+"quantitative-imaging-map.settings.force-settings.control-settings.start-option.motion-time" :
+ ['trash', eval],
+"quantitative-imaging-map.settings.force-settings.control-settings.ttl-outputs.pins.list" :
+ ['trash', lambda x: str(x).strip('\n')],
+"quantitative-imaging-map.settings.force-settings.control-settings.baseline-adjust-settings.enabled" :
+ ['trash', lambda x: eval(x.title())],
+"quantitative-imaging-map.settings.force-settings.control-settings.baseline-adjust-settings.interval" :
+ ['trash', eval],
+"quantitative-imaging-map.settings.force-settings.extend.type" :
+ ['trash', lambda x: str(x).strip('\n')],
+"quantitative-imaging-map.settings.force-settings.extend.identifier.type" :
+ ['trash', lambda x: str(x).strip('\n')],
+"quantitative-imaging-map.settings.force-settings.extend.identifier.name" :
+ ['trash', lambda x: str(x).strip('\n')],
+"quantitative-imaging-map.settings.force-settings.extend.style" :
+ ['trash', lambda x: str(x).strip('\n')],
+"quantitative-imaging-map.settings.force-settings.extend.duration" :
+ ['trash', eval],
+"quantitative-imaging-map.settings.force-settings.extend.num-points" :
+ ['trash', eval],
+"quantitative-imaging-map.settings.force-settings.extend.z-start" :
+ ['trash', eval],
+"quantitative-imaging-map.settings.force-settings.extend.z-end" :
+ ['trash', eval],
+"quantitative-imaging-map.settings.force-settings.extend.setpoint" :
+ ['trash', eval],
+"quantitative-imaging-map.settings.force-settings.retract.type" :
+ ['trash', lambda x: str(x).strip('\n')],
+"quantitative-imaging-map.settings.force-settings.retract.identifier.type" :
+ ['trash', lambda x: str(x).strip('\n')],
+"quantitative-imaging-map.settings.force-settings.retract.identifier.name" :
+ ['trash', lambda x: str(x).strip('\n')],
+"quantitative-imaging-map.settings.force-settings.retract.style" :
+ ['trash', lambda x: str(x).strip('\n')],
+"quantitative-imaging-map.settings.force-settings.retract.duration" :
+ ['trash', eval],
+"quantitative-imaging-map.settings.force-settings.retract.num-points" :
+ ['trash', eval],
+"quantitative-imaging-map.settings.force-settings.retract.z-start" :
+ ['trash', eval],
+"quantitative-imaging-map.settings.force-settings.retract.z-end" :
+ ['trash', eval],
+"quantitative-imaging-map.settings.force-settings.retract.setpoint" :
+ ['trash', eval],
+"quantitative-imaging-map.settings.force-settings.data-description.comment" :
+ ['trash', lambda x: str(x).strip('\n')],
+"quantitative-imaging-map.settings.force-settings.data-description.name" :
+ ['trash', lambda x: str(x).strip('\n')],
+"quantitative-imaging-map.settings.force-settings.data-description.probe" :
+ ['trash', lambda x: str(x).strip('\n')],
+"quantitative-imaging-map.settings.force-settings.data-description.user-name" :
+ ['trash', lambda x: str(x).strip('\n')],
+"quantitative-imaging-map.settings.force-settings.data-description.instrument" :
+ ['trash', lambda x: str(x).strip('\n')],
+"quantitative-imaging-map.settings.force-settings.data-description.source-software" :
+ ['trash', lambda x: str(x).strip('\n')],
+"quantitative-imaging-map.settings.force-settings.data-description.modification-software" :
+ ['trash', lambda x: str(x).strip('\n')],
+"quantitative-imaging-map.position-pattern.type" :
+ ['trash', lambda x: str(x).strip('\n')],
+"quantitative-imaging-map.position-pattern.back-and-forth" :
+ ['trash', lambda x: eval(x.title())],
+"quantitative-imaging-map.position-pattern.grid.xcenter" :
+ ['mcenter_x', eval],
+"quantitative-imaging-map.position-pattern.grid.ycenter" :
+ ['mcenter_y', eval],
+"quantitative-imaging-map.position-pattern.grid.ulength" :
+ ['msize_x', eval],
+"quantitative-imaging-map.position-pattern.grid.vlength" :
+ ['msize_y', eval],
+"quantitative-imaging-map.position-pattern.grid.theta" :
+ ['trash', eval],
+"quantitative-imaging-map.position-pattern.grid.reflect" :
+ ['trash', lambda x: eval(x.title())],
+"quantitative-imaging-map.position-pattern.grid.unit.type" :
+ ['trash', lambda x: str(x).strip('\n')],
+"quantitative-imaging-map.position-pattern.grid.unit.unit" :
+ ['size_unit', lambda x: str(x).strip('\n')],
+"quantitative-imaging-map.position-pattern.grid.ilength" :
+ ['size_x', eval],
+"quantitative-imaging-map.position-pattern.grid.jlength" :
+ ['size_y', eval],
+"quantitative-imaging-map.feedback-mode.name" :
+ ['trash', lambda x: str(x).strip('\n')],
+"quantitative-imaging-map.spm-scanner-mode.type" :
+ ['trash', lambda x: str(x).strip('\n')],
+"quantitative-imaging-map.spm-scanner-mode.software-linearized" :
+ ['trash', lambda x: eval(x.title())],
+"quantitative-imaging-map.spm-scanner-mode.xy-scanner.scanner" :
+ ['trash', lambda x: str(x).strip('\n')],
+"quantitative-imaging-map.spm-scanner-mode.xy-scanner.name" :
+ ['trash', lambda x: str(x).strip('\n')],
+"quantitative-imaging-map.spm-scanner-mode.xy-scanner.fancy-name" :
+ ['trash', lambda x: str(x).strip('\n')],
+"quantitative-imaging-map.spm-scanner-mode.xy-scanner.description" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-map.indexes.type" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-map.indexes.min" :
+ ['index_min', eval],
+"force-scan-map.indexes.max" :
+ ['index_max', eval],
+"force-scan-map.scan-number" :
+ ['trash', eval],
+"force-scan-map.start-time" :
+ ['trash', lambda x: date_parser.parse(x)],
+"force-scan-map.end-time" :
+ ['trash', lambda x: date_parser.parse(x)],
+"force-scan-map.comment" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-map.description.comment" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-map.description.name" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-map.description.probe" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-map.description.user-name" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-map.description.instrument" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-map.description.source-software" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-map.description.modification-software" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-map.settings.type" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-map.settings.force-settings.type" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-map.settings.force-settings.extend-k-length" :
+ ['trash', eval],
+"force-scan-map.settings.force-settings.retract-k-length" :
+ ['trash', eval],
+"force-scan-map.settings.force-settings.extended-pause-k-length" :
+ ['trash', eval],
+"force-scan-map.settings.force-settings.retracted-pause-k-length" :
+ ['trash', eval],
+"force-scan-map.settings.force-settings.z-start-pause-option.type" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-map.settings.force-settings.z-end-pause-option.type" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-map.settings.force-settings.extend-scan-time" :
+ ['trash', eval],
+"force-scan-map.settings.force-settings.retract-scan-time" :
+ ['trash', eval],
+"force-scan-map.settings.force-settings.retracted-pause-time" :
+ ['trash', eval],
+"force-scan-map.settings.force-settings.extended-pause-time" :
+ ['trash', eval],
+"force-scan-map.settings.force-settings.data-description.comment" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-map.settings.force-settings.data-description.name" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-map.settings.force-settings.data-description.probe" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-map.settings.force-settings.data-description.user-name" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-map.settings.force-settings.data-description.instrument" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-map.settings.force-settings.data-description.source-software" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-map.settings.force-settings.data-description.modification-software" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-map.settings.force-settings.start-with-retract" :
+ ['trash', lambda x: eval(x.title())],
+"force-scan-map.settings.force-settings.control-settings-type" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-map.settings.force-settings.closed-loop" :
+ ['trash', lambda x: eval(x.title())],
+"force-scan-map.settings.force-settings.start-option.type" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-map.settings.force-settings.ttl-outputs.pins.list" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-map.settings.force-settings.pause-before-first.type" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-map.settings.force-settings.pause-before-first.identifier.type" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-map.settings.force-settings.pause-before-first.identifier.name" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-map.settings.force-settings.pause-before-first.style" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-map.settings.force-settings.pause-before-first.duration" :
+ ['trash', eval],
+"force-scan-map.settings.force-settings.pause-before-first.num-points" :
+ ['trash', eval],
+"force-scan-map.settings.force-settings.pause-before-first.pause-option.type" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-map.settings.force-settings.pause-before-first.height-limit" :
+ ['trash', eval],
+"force-scan-map.settings.force-settings.force-baseline-adjust-settings.enabled" :
+ ['trash', lambda x: eval(x.title())],
+"force-scan-map.settings.force-settings.force-baseline-adjust-settings.interval" :
+ ['trash', eval],
+"force-scan-map.settings.force-settings.force-baseline-adjust-settings.beginOfLine" :
+ ['trash', lambda x: eval(x.title())],
+"force-scan-map.settings.force-settings.force-baseline-adjust-settings.deadtimeBeforeSamples" :
+ ['trash', eval],
+"force-scan-map.settings.force-settings.force-baseline-adjust-settings.averageSamples" :
+ ['trash', eval],
+"force-scan-map.settings.force-settings.line-clock.active.extend" :
+ ['trash', lambda x: eval(x.title())],
+"force-scan-map.settings.force-settings.line-clock.active.retract" :
+ ['trash', lambda x: eval(x.title())],
+"force-scan-map.settings.force-settings.relative-setpoint" :
+ ['trash', eval],
+"force-scan-map.settings.force-settings.relative-z-start" :
+ ['trash', eval],
+"force-scan-map.settings.force-settings.relative-z-end" :
+ ['trash', eval],
+"force-scan-map.position-pattern.type" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-map.position-pattern.back-and-forth" :
+ ['trash', lambda x: eval(x.title())],
+"force-scan-map.position-pattern.grid.xcenter" :
+ ['mcenter_x', eval],
+"force-scan-map.position-pattern.grid.ycenter" :
+ ['mcenter_y', eval],
+"force-scan-map.position-pattern.grid.ulength" :
+ ['msize_x', eval],
+"force-scan-map.position-pattern.grid.vlength" :
+ ['msize_y', eval],
+"force-scan-map.position-pattern.grid.theta" :
+ ['trash', eval],
+"force-scan-map.position-pattern.grid.reflect" :
+ ['trash', lambda x: eval(x.title())],
+"force-scan-map.position-pattern.grid.unit.type" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-map.position-pattern.grid.unit.unit" :
+ ['size_unit', lambda x: str(x).strip('\n')],
+"force-scan-map.position-pattern.grid.ilength" :
+ ['size_x', eval],
+"force-scan-map.position-pattern.grid.jlength" :
+ ['size_y', eval],
+"force-scan-map.feedback-mode.name" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-map.spm-scanner-mode.type" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-map.spm-scanner-mode.software-linearized" :
+ ['trash', lambda x: eval(x.title())],
+"force-scan-map.spm-scanner-mode.xy-scanner.scanner" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-map.spm-scanner-mode.xy-scanner.name" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-map.spm-scanner-mode.xy-scanner.fancy-name" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-map.spm-scanner-mode.xy-scanner.description" :
+ ['trash', lambda x: str(x).strip('\n')],
+}
+
+ZIP_INDEX_HEADER = {
+"type" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-series.force-segments.count" :
+ ['count', eval],
+"force-scan-series.header.type" :
+ ['trash' , lambda x: str(x).strip('\n')],
+"force-scan-series.header.position.x" :
+ ['mpos_x', eval],
+"force-scan-series.header.position.y" :
+ ['mpos_y', eval],
+"force-scan-series.header.position-index" :
+ ['pos_index', eval],
+"force-scan-series.header.force-settings.type" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-series.header.force-settings.extend-k-length" :
+ ['trash', eval],
+"force-scan-series.header.force-settings.retract-k-length" :
+ ['trash', eval],
+"force-scan-series.header.force-settings.extended-pause-k-length" :
+ ['trash', eval],
+"force-scan-series.header.force-settings.retracted-pause-k-length" :
+ ['trash', eval],
+"force-scan-series.header.force-settings.z-start-pause-option.type" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-series.header.force-settings.z-end-pause-option.type" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-series.header.force-settings.extend-scan-time" :
+ ['trash', eval],
+"force-scan-series.header.force-settings.retract-scan-time" :
+ ['trash', eval],
+"force-scan-series.header.force-settings.retracted-pause-time" :
+ ['trash', eval],
+"force-scan-series.header.force-settings.extended-pause-time" :
+ ['trash', eval],
+"force-scan-series.header.force-settings.data-description.comment" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-series.header.force-settings.data-description.name" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-series.header.force-settings.data-description.probe" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-series.header.force-settings.data-description.user-name" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-series.header.force-settings.data-description.instrument" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-series.header.force-settings.data-description.source-software" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-series.header.force-settings.data-description.modification-software" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-series.header.force-settings.start-with-retract" :
+ ['trash', lambda x: eval(x.title())],
+"force-scan-series.header.force-settings.control-settings-type" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-series.header.force-settings.closed-loop" :
+ ['trash', lambda x: eval(x.title())],
+"force-scan-series.header.force-settings.start-option.type" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-series.header.force-settings.ttl-outputs.pins.list" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-series.header.force-settings.pause-before-first.type" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-series.header.force-settings.pause-before-first.identifier.type" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-series.header.force-settings.pause-before-first.identifier.name" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-series.header.force-settings.pause-before-first.style" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-series.header.force-settings.pause-before-first.duration" :
+ ['trash', eval],
+"force-scan-series.header.force-settings.pause-before-first.num-points" :
+ ['trash', eval],
+"force-scan-series.header.force-settings.pause-before-first.pause-option.type" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-series.header.force-settings.pause-before-first.height-limit" :
+ ['trash', eval],
+"force-scan-series.header.force-settings.force-baseline-adjust-settings.enabled" :
+ ['trash', lambda x: eval(x.title())],
+"force-scan-series.header.force-settings.force-baseline-adjust-settings.interval" :
+ ['trash', eval],
+"force-scan-series.header.force-settings.force-baseline-adjust-settings.beginOfLine" :
+ ['trash', lambda x: eval(x.title())],
+"force-scan-series.header.force-settings.force-baseline-adjust-settings.deadtimeBeforeSamples" :
+ ['trash', eval],
+"force-scan-series.header.force-settings.force-baseline-adjust-settings.averageSamples" :
+ ['trash', eval],
+"force-scan-series.header.force-settings.line-clock.active.extend" :
+ ['trash', lambda x: eval(x.title())],
+"force-scan-series.header.force-settings.line-clock.active.retract" :
+ ['trash', lambda x: eval(x.title())],
+"force-scan-series.header.force-settings.relative-setpoint" :
+ ['trash', eval],
+"force-scan-series.header.force-settings.relative-z-start" :
+ ['trash', eval],
+"force-scan-series.header.force-settings.relative-z-end" :
+ ['trash', eval],
+"force-scan-series.description.comment" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-series.description.name" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-series.description.probe" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-series.description.user-name" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-series.description.instrument" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-series.description.source-software" :
+ ['trash', lambda x: str(x).strip('\n')],
+"force-scan-series.description.modification-software" :
+ ['trash', lambda x: str(x).strip('\n')],
+'quantitative-imaging-series.force-segments.count' :
+ ['count', eval],
+'quantitative-imaging-series.header.type' :
+ ['trash', lambda x: str(x).strip('\n')],
+'quantitative-imaging-series.header.position.x' :
+ ['mpos_x', eval],
+'quantitative-imaging-series.header.position.y' :
+ ['mpos_y', eval],
+'quantitative-imaging-series.header.position-index' :
+ ['pos_index', eval],
+'quantitative-imaging-series.header.quantitative-imaging-settings.type' :
+ ['trash', lambda x: str(x).strip('\n')],
+'quantitative-imaging-series.header.quantitative-imaging-settings.relative-setpoint' :
+ ['trash', eval],
+'quantitative-imaging-series.header.quantitative-imaging-settings.control-settings.control-settings-type' :
+ ['trash', lambda x: str(x).strip('\n')],
+'quantitative-imaging-series.header.quantitative-imaging-settings.control-settings.next-line-additional-delay' :
+ ['trash', eval],
+'quantitative-imaging-series.header.quantitative-imaging-settings.control-settings.next-line-additional-retract' :
+ ['trash', eval],
+'quantitative-imaging-series.header.quantitative-imaging-settings.control-settings.max-retries-per-position' :
+ ['trash', eval],
+'quantitative-imaging-series.header.quantitative-imaging-settings.control-settings.max-bad-adjecent-pixels' :
+ ['trash', eval],
+'quantitative-imaging-series.header.quantitative-imaging-settings.control-settings.closed-loop' :
+ ['trash', lambda x: eval(x.title())],
+'quantitative-imaging-series.header.quantitative-imaging-settings.control-settings.start-option.type' :
+ ['trash', lambda x: str(x).strip('\n')],
+'quantitative-imaging-series.header.quantitative-imaging-settings.control-settings.start-option.additional-retract' :
+ ['trash', eval],
+'quantitative-imaging-series.header.quantitative-imaging-settings.control-settings.start-option.start-time' :
+ ['trash', eval],
+'quantitative-imaging-series.header.quantitative-imaging-settings.control-settings.start-option.motion-time' :
+ ['trash', eval],
+'quantitative-imaging-series.header.quantitative-imaging-settings.control-settings.ttl-outputs.pins.list' :
+ ['trash', lambda x: str(x).strip('\n')],
+'quantitative-imaging-series.header.quantitative-imaging-settings.control-settings.baseline-adjust-settings.enabled' :
+ ['trash', lambda x: eval(x.title())],
+'quantitative-imaging-series.header.quantitative-imaging-settings.control-settings.baseline-adjust-settings.interval' :
+ ['trash', eval],
+'quantitative-imaging-series.header.quantitative-imaging-settings.extend.type' :
+ ['trash', lambda x: str(x).strip('\n')],
+'quantitative-imaging-series.header.quantitative-imaging-settings.extend.identifier.type' :
+ ['trash', lambda x: str(x).strip('\n')],
+'quantitative-imaging-series.header.quantitative-imaging-settings.extend.identifier.name' :
+ ['trash', lambda x: str(x).strip('\n')],
+'quantitative-imaging-series.header.quantitative-imaging-settings.extend.style' :
+ ['trash', lambda x: str(x).strip('\n')],
+'quantitative-imaging-series.header.quantitative-imaging-settings.extend.duration' :
+ ['trash', eval],
+'quantitative-imaging-series.header.quantitative-imaging-settings.extend.num-points' :
+ ['trash', eval],
+'quantitative-imaging-series.header.quantitative-imaging-settings.extend.z-start' :
+ ['trash', eval],
+'quantitative-imaging-series.header.quantitative-imaging-settings.extend.z-end' :
+ ['trash', eval],
+'quantitative-imaging-series.header.quantitative-imaging-settings.extend.setpoint' :
+ ['trash', eval],
+'quantitative-imaging-series.header.quantitative-imaging-settings.retract.type' :
+ ['trash', lambda x: str(x).strip('\n')],
+'quantitative-imaging-series.header.quantitative-imaging-settings.retract.identifier.type' :
+ ['trash', lambda x: str(x).strip('\n')],
+'quantitative-imaging-series.header.quantitative-imaging-settings.retract.identifier.name' :
+ ['trash', lambda x: str(x).strip('\n')],
+'quantitative-imaging-series.header.quantitative-imaging-settings.retract.style' :
+ ['trash', lambda x: str(x).strip('\n')],
+'quantitative-imaging-series.header.quantitative-imaging-settings.retract.duration' :
+ ['trash', eval],
+'quantitative-imaging-series.header.quantitative-imaging-settings.retract.num-points' :
+ ['trash', eval],
+'quantitative-imaging-series.header.quantitative-imaging-settings.retract.z-start' :
+ ['trash', eval],
+'quantitative-imaging-series.header.quantitative-imaging-settings.retract.z-end' :
+ ['trash', eval],
+'quantitative-imaging-series.header.quantitative-imaging-settings.retract.setpoint' :
+ ['trash', eval],
+'quantitative-imaging-series.header.quantitative-imaging-settings.data-description.comment' :
+ ['trash', lambda x: str(x).strip('\n')],
+'quantitative-imaging-series.header.quantitative-imaging-settings.data-description.name' :
+ ['trash', lambda x: str(x).strip('\n')],
+'quantitative-imaging-series.header.quantitative-imaging-settings.data-description.probe' :
+ ['trash', lambda x: str(x).strip('\n')],
+'quantitative-imaging-series.header.quantitative-imaging-settings.data-description.user-name' :
+ ['trash', lambda x: str(x).strip('\n')],
+'quantitative-imaging-series.header.quantitative-imaging-settings.data-description.instrument' :
+ ['trash', lambda x: str(x).strip('\n')],
+'quantitative-imaging-series.header.quantitative-imaging-settings.data-description.source-software' :
+ ['trash', lambda x: str(x).strip('\n')],
+'quantitative-imaging-series.header.quantitative-imaging-settings.data-description.modification-software' :
+ ['trash', lambda x: str(x).strip('\n')],
+'quantitative-imaging-series.description.comment' :
+ ['trash', lambda x: str(x).strip('\n')],
+'quantitative-imaging-series.description.name' :
+ ['trash', lambda x: str(x).strip('\n')],
+'quantitative-imaging-series.description.probe' :
+ ['trash', lambda x: str(x).strip('\n')],
+'quantitative-imaging-series.description.user-name' :
+ ['trash', lambda x: str(x).strip('\n')],
+'quantitative-imaging-series.description.instrument' :
+ ['trash', lambda x: str(x).strip('\n')],
+'quantitative-imaging-series.description.source-software' :
+ ['trash', lambda x: str(x).strip('\n')],
+'quantitative-imaging-series.description.modification-software' :
+ ['trash', lambda x: str(x).strip('\n')],
+}
+
+ZIP_SEGMENT_HEADER = {
+#Fri Dec 16 13:09:06 CET 2011
+'force-segment-header.force-segment-header-info.*' :
+ ['trash', eval],
+'force-segment-header.type' :
+ ['trash', lambda x: str(x).strip('\n')],
+'force-segment-header.num-points' :
+ ['trash', lambda x: eval(x.title())],
+'force-segment-header.duration' :
+ ['trash', lambda x: eval(x.title())],
+'force-segment-header.time-stamp' :
+ ['trash', lambda x: date_parser.parse(x)],
+'force-segment-header.baseline.baseline' :
+ ['trash', lambda x: eval(x.title())],
+'force-segment-header.baseline.measured' :
+ ['trash', lambda x: eval(x.title())],
+'force-segment-header.force-scan-flags.series-done' :
+ ['trash', lambda x: eval(x.title())],
+'force-segment-header.force-scan-flags.done-scanning' :
+ ['trash', lambda x: eval(x.title())],
+'force-segment-header.force-scan-flags.aborted' :
+ ['trash', lambda x: eval(x.title())],
+'force-segment-header.force-scan-flags.data-segment' :
+ ['trash', lambda x: eval(x.title())],
+'force-segment-header.force-scan-flags.z-start-out-of-range' :
+ ['trash', lambda x: eval(x.title())],
+'force-segment-header.force-scan-flags.z-end-out-of-range' :
+ ['trash', lambda x: eval(x.title())],
+'force-segment-header.force-scan-flags.setpoint-out-of-range' :
+ ['trash', lambda x: eval(x.title())],
+'force-segment-header.force-scan-flags.surface-incorrect' :
+ ['trash', lambda x: eval(x.title())],
+'force-segment-header.force-scan-flags.tipsaver-limit-exceeded' :
+ ['trash', lambda x: eval(x.title())],
+'force-segment-header.environment.xy-scanner-position-map.xy-scanners.position-index' :
+ ['trash', eval],
+'force-segment-header.environment.xy-scanner-position-map.xy-scanner.tip-scanner.position.x' :
+ ['mpos_x', lambda x: eval(x.title())],
+'force-segment-header.environment.xy-scanner-position-map.xy-scanner.tip-scanner.position.y' :
+ ['mpos_y', lambda x: eval(x.title())],
+'force-segment-header.environment.xy-scanner-position-map.xy-scanner.motorstage.position.x' :
+ ['trash', lambda x: eval(x.title())],
+'force-segment-header.environment.xy-scanner-position-map.xy-scanner.motorstage.position.y' :
+ ['trash', lambda x: eval(x.title())],
+'channels.list' :
+ ['channel_list', lambda x: str(x).strip('\n').split(' ')],
+'channel.vDeflection.lcd-info.*' :
+ ['trash', lambda x: eval(x.title())],
+'channel.vDeflection.data.file.name' :
+ ['vDeflection.fname', lambda x: str(x).strip('\n')],
+'channel.vDeflection.data.file.format' :
+ ['vDeflection.format', lambda x: str(x).strip('\n')],
+'channel.vDeflection.data.num-points' :
+ ['vDeflection.num-points', lambda x: eval(x.title())],
+'channel.error.lcd-info.*' :
+ ['trash', lambda x: eval(x.title())],
+'channel.error.data.file.name' :
+ ['error.fname', lambda x: str(x).strip('\n')],
+'channel.error.data.file.format' :
+ ['error.format', lambda x: str(x).strip('\n')],
+'channel.error.data.num-points' :
+ ['error.num-points', lambda x: eval(x.title())],
+'channel.hDeflection.lcd-info.*' :
+ ['trash', lambda x: eval(x.title())],
+'channel.hDeflection.data.file.name' :
+ ['hDeflection.fname', lambda x: str(x).strip('\n')],
+'channel.hDeflection.data.file.format' :
+ ['hDeflection.format', lambda x: str(x).strip('\n')],
+'channel.hDeflection.data.num-points' :
+ ['hDeflection.num-points', lambda x: eval(x.title())],
+'channel.capacitiveSensorHeight.lcd-info.*' :
+ ['trash', lambda x: eval(x.title())],
+'channel.capacitiveSensorHeight.data.file.name' :
+ ['capacitiveSensorHeight.fname', lambda x: str(x).strip('\n')],
+'channel.capacitiveSensorHeight.data.file.format' :
+ ['capacitiveSensorHeight.format', lambda x: str(x).strip('\n')],
+'channel.capacitiveSensorHeight.data.num-points' :
+ ['capacitiveSensorHeight.num-points', lambda x: eval(x.title())],
+'channel.height.lcd-info.*' :
+ ['trash', lambda x: eval(x.title())],
+'channel.height.data.file.name' :
+ ['height.fname', lambda x: str(x).strip('\n')],
+'channel.height.data.file.format' :
+ ['height.format', lambda x: str(x).strip('\n')],
+'channel.height.data.num-points' :
+ ['height.num-points', lambda x: eval(x.title())],
+'channel.time.lcd-info.*' :
+ ['trash', lambda x: eval(x.title())],
+'channel.time.data.num-points' :
+ ['trash', lambda x: eval(x.title())],
+'channel.time.data.start' :
+ ['trash', lambda x: eval(x.title())],
+'channel.time.data.step' :
+ ['trash', lambda x: eval(x.title())],
+'channel.seriesTime.lcd-info.*' :
+ ['trash', lambda x: eval(x.title())],
+'channel.seriesTime.data.num-points' :
+ ['trash', lambda x: eval(x.title())],
+'channel.seriesTime.data.start' :
+ ['trash', lambda x: eval(x.title())],
+'channel.seriesTime.data.step' :
+ ['trash', lambda x: eval(x.title())],
+}
+ZIP_SHARED_HEADER = {
+'force-segment-header-infos.count' :
+ ['segment_count', eval],
+'lcd-infos.count' :
+ ['channel_count', eval],
+'force-segment-header-info.xx.type' :
+ ['trash', lambda x: str(x).strip('\n')],
+'force-segment-header-info.xx.approach-id' :
+ ['trash', lambda x: str(x).strip('\n')],
+'force-segment-header-info.xx.name.type' :
+ ['trash', lambda x: str(x).strip('\n')],
+'force-segment-header-info.xx.name.name' :
+ ['trash', lambda x: str(x).strip('\n')],
+'force-segment-header-info.xx.settings.type' :
+ ['trash', lambda x: str(x).strip('\n')],
+'force-segment-header-info.xx.settings.style' :
+ ['trash', lambda x: str(x).strip('\n')],
+'force-segment-header-info.xx.settings.feedback-mode.name' :
+ ['trash', lambda x: str(x).strip('\n')],
+'force-segment-header-info.xx.settings.segment-settings.type' :
+ ['trash', lambda x: str(x).strip('\n')],
+'force-segment-header-info.xx.settings.segment-settings.identifier.type' :
+ ['trash', lambda x: str(x).strip('\n')],
+'force-segment-header-info.xx.settings.segment-settings.identifier.name' :
+ ['trash', lambda x: str(x).strip('\n')],
+'force-segment-header-info.xx.settings.segment-settings.style' :
+ ['trash', lambda x: str(x).strip('\n')],
+'force-segment-header-info.xx.settings.segment-settings.duration' :
+ ['trash', eval],
+'force-segment-header-info.xx.settings.segment-settings.num-points' :
+ ['trash', eval],
+'force-segment-header-info.xx.settings.segment-settings.z-start' :
+ ['trash', eval],
+'force-segment-header-info.xx.settings.segment-settings.z-end' :
+ ['trash', eval],
+'force-segment-header-info.xx.settings.segment-settings.setpoint' :
+ ['trash', eval],
+'force-segment-header-info.xx.environment.xy-scanner-position-map.defined' :
+ ['trash', lambda x: eval(x.title())],
+'force-segment-header-info.xx.environment.xy-scanner-position-map.xy-scanners.active-xy-scanner.name' :
+ ['trash', lambda x: str(x).strip('\n')],
+'force-segment-header-info.xx.environment.xy-scanner-position-map.xy-scanner.tip-scanner.xy-scanner.scanner' :
+ ['trash', lambda x: str(x).strip('\n')],
+'force-segment-header-info.xx.environment.xy-scanner-position-map.xy-scanner.tip-scanner.xy-scanner.name' :
+ ['trash', lambda x: str(x).strip('\n')],
+'force-segment-header-info.xx.environment.xy-scanner-position-map.xy-scanner.tip-scanner.xy-scanner.fancy-name' :
+ ['trash', lambda x: str(x).strip('\n')],
+'force-segment-header-info.xx.environment.xy-scanner-position-map.xy-scanner.tip-scanner.xy-scanner.description' :
+ ['trash', lambda x: str(x).strip('\n')],
+'force-segment-header-info.xx.environment.xy-scanner-position-map.xy-scanner.tip-scanner.xy-scanner-mode.type' :
+ ['trash', lambda x: str(x).strip('\n')],
+'force-segment-header-info.xx.environment.xy-scanner-position-map.xy-scanner.tip-scanner.xy-scanner-mode.software-linearized' :
+ ['trash', lambda x: eval(x.title())],
+'force-segment-header-info.xx.environment.xy-scanner-position-map.xy-scanner.motorstage.xy-scanner.scanner' :
+ ['trash', lambda x: str(x).strip('\n')],
+'force-segment-header-info.xx.environment.xy-scanner-position-map.xy-scanner.motorstage.xy-scanner.name' :
+ ['trash', lambda x: str(x).strip('\n')],
+'force-segment-header-info.xx.environment.xy-scanner-position-map.xy-scanner.motorstage.xy-scanner.fancy-name' :
+ ['trash', lambda x: str(x).strip('\n')],
+'force-segment-header-info.xx.environment.xy-scanner-position-map.xy-scanner.motorstage.xy-scanner.description' :
+ ['trash', lambda x: str(x).strip('\n')],
+'force-segment-header-info.xx.environment.xy-scanner-position-map.xy-scanner.motorstage.xy-scanner-mode.type' :
+ ['trash', lambda x: str(x).strip('\n')],
+'force-segment-header-info.xx.environment.xy-scanner-position-map.xy-scanner.motorstage.xy-scanner-mode.software-linearized' :
+ ['trash', lambda x: eval(x.title())],
+'force-segment-header-info.xx.environment.xy-scanner-position-map.xy-scanners.list' :
+ ['trash', lambda x: str(x).strip('\n')],
+'lcd-info.xx.type' :
+ ['trash', lambda x: str(x).strip('\n')],
+'lcd-info.xx.channel.type' :
+ ['trash', lambda x: str(x).strip('\n')],
+'lcd-info.xx.channel.name' :
+ ['name.xx', lambda x: str(x).strip('\n')],
+'lcd-info.xx.unit.type' :
+ ['trash', lambda x: str(x).strip('\n')],
+'lcd-info.xx.unit.unit' :
+ ['trash', lambda x: str(x).strip('\n')],
+'lcd-info.xx.conversion-set.conversions.list' :
+ ['conversion_list.xx', lambda x: str(x).strip('\n')],
+'lcd-info.xx.conversion-set.conversions.default' :
+ ['trash', lambda x: str(x).strip('\n')],
+'lcd-info.xx.conversion-set.conversions.base' :
+ ['trash', lambda x: str(x).strip('\n')],
+'lcd-info.xx.conversion-set.conversion.volts.name' :
+ ['trash', lambda x: str(x).strip('\n')],
+'lcd-info.xx.conversion-set.conversion.volts.defined' :
+ ['trash', lambda x: eval(x.title())],
+'lcd-info.xx.conversion-set.conversion.distance.name' :
+ ['trash', lambda x: str(x).strip('\n')],
+'lcd-info.xx.conversion-set.conversion.distance.defined' :
+ ['trash', lambda x: eval(x.title())],
+'lcd-info.xx.conversion-set.conversion.distance.type' :
+ ['trash', lambda x: str(x).strip('\n')],
+'lcd-info.xx.conversion-set.conversion.distance.comment' :
+ ['trash', lambda x: str(x).strip('\n')],
+'lcd-info.xx.conversion-set.conversion.distance.base-calibration-slot' :
+ ['trash', lambda x: str(x).strip('\n')],
+'lcd-info.xx.conversion-set.conversion.distance.calibration-slot' :
+ ['trash', lambda x: str(x).strip('\n')],
+'lcd-info.xx.conversion-set.conversion.distance.scaling.type' :
+ ['trash', lambda x: str(x).strip('\n')],
+'lcd-info.xx.conversion-set.conversion.distance.scaling.style' :
+ ['trash', lambda x: str(x).strip('\n')],
+'lcd-info.xx.conversion-set.conversion.distance.scaling.offset' :
+ ['distance_offset', eval],
+'lcd-info.xx.conversion-set.conversion.distance.scaling.multiplier' :
+ ['distance_multiplier', eval],
+'lcd-info.xx.conversion-set.conversion.distance.scaling.unit.type' :
+ ['trash', lambda x: str(x).strip('\n')],
+'lcd-info.xx.conversion-set.conversion.distance.scaling.unit.unit' :
+ ['trash', lambda x: str(x).strip('\n')],
+'lcd-info.xx.conversion-set.conversion.force.name' :
+ ['trash', lambda x: str(x).strip('\n')],
+'lcd-info.xx.conversion-set.conversion.force.defined' :
+ ['trash', lambda x: eval(x.title())],
+'lcd-info.xx.conversion-set.conversion.force.type' :
+ ['trash', lambda x: str(x).strip('\n')],
+'lcd-info.xx.conversion-set.conversion.force.comment' :
+ ['trash', lambda x: str(x).strip('\n')],
+'lcd-info.xx.conversion-set.conversion.force.base-calibration-slot' :
+ ['trash', lambda x: str(x).strip('\n')],
+'lcd-info.xx.conversion-set.conversion.force.calibration-slot' :
+ ['trash', lambda x: str(x).strip('\n')],
+'lcd-info.xx.conversion-set.conversion.force.scaling.type' :
+ ['trash', lambda x: str(x).strip('\n')],
+'lcd-info.xx.conversion-set.conversion.force.scaling.style' :
+ ['trash', lambda x: str(x).strip('\n')],
+'lcd-info.xx.conversion-set.conversion.force.scaling.offset' :
+ ['force_offset.xx', eval],
+'lcd-info.xx.conversion-set.conversion.force.scaling.multiplier' :
+ ['spring_constant', eval],
+'lcd-info.xx.conversion-set.conversion.force.scaling.unit.type' :
+ ['trash', lambda x: str(x).strip('\n')],
+'lcd-info.xx.conversion-set.conversion.force.scaling.unit.unit' :
+ ['trash', lambda x: str(x).strip('\n')],
+'lcd-info.xx.conversion-set.conversion.absolute.name' :
+ ['trash', lambda x: str(x).strip('\n')],
+'lcd-info.xx.conversion-set.conversion.absolute.defined' :
+ ['trash', lambda x: eval(x.title())],
+'lcd-info.xx.conversion-set.conversion.absolute.type' :
+ ['trash', lambda x: str(x).strip('\n')],
+'lcd-info.xx.conversion-set.conversion.absolute.comment' :
+ ['trash', lambda x: str(x).strip('\n')],
+'lcd-info.xx.conversion-set.conversion.absolute.base-calibration-slot' :
+ ['trash', lambda x: str(x).strip('\n')],
+'lcd-info.xx.conversion-set.conversion.absolute.calibration-slot' :
+ ['trash', lambda x: str(x).strip('\n')],
+'lcd-info.xx.conversion-set.conversion.absolute.scaling.type' :
+ ['trash', lambda x: str(x).strip('\n')],
+'lcd-info.xx.conversion-set.conversion.absolute.scaling.style' :
+ ['trash', lambda x: str(x).strip('\n')],
+'lcd-info.xx.conversion-set.conversion.absolute.scaling.offset' :
+ ['force_offset.xx', eval],
+'lcd-info.xx.conversion-set.conversion.absolute.scaling.multiplier' :
+ ['force_offset.xx', eval],
+'lcd-info.xx.conversion-set.conversion.absolute.scaling.unit.type' :
+ ['trash', lambda x: str(x).strip('\n')],
+'lcd-info.xx.conversion-set.conversion.absolute.scaling.unit.unit' :
+ ['trash', lambda x: str(x).strip('\n')],
+'lcd-info.xx.conversion-set.conversion.nominal.name' :
+ ['trash', lambda x: str(x).strip('\n')],
+'lcd-info.xx.conversion-set.conversion.nominal.defined' :
+ ['trash', lambda x: eval(x.title())],
+'lcd-info.xx.conversion-set.conversion.nominal.type' :
+ ['trash', lambda x: str(x).strip('\n')],
+'lcd-info.xx.conversion-set.conversion.nominal.comment' :
+ ['trash', lambda x: str(x).strip('\n')],
+'lcd-info.xx.conversion-set.conversion.nominal.base-calibration-slot' :
+ ['trash', lambda x: str(x).strip('\n')],
+'lcd-info.xx.conversion-set.conversion.nominal.calibration-slot' :
+ ['trash', lambda x: str(x).strip('\n')],
+'lcd-info.xx.conversion-set.conversion.nominal.scaling.type' :
+ ['trash', lambda x: str(x).strip('\n')],
+'lcd-info.xx.conversion-set.conversion.nominal.scaling.style' :
+ ['trash', lambda x: str(x).strip('\n')],
+'lcd-info.xx.conversion-set.conversion.nominal.scaling.offset' :
+ ['nominal_offset.xx', eval],
+'lcd-info.xx.conversion-set.conversion.nominal.scaling.multiplier' :
+ ['nominal_multiplier.xx', eval],
+'lcd-info.xx.conversion-set.conversion.nominal.scaling.unit.type' :
+ ['trash', lambda x: str(x).strip('\n')],
+'lcd-info.xx.conversion-set.conversion.nominal.scaling.unit.unit' :
+ ['trash', lambda x: str(x).strip('\n')],
+'lcd-info.xx.conversion-set.conversion.calibrated.name' :
+ ['trash', lambda x: str(x).strip('\n')],
+'lcd-info.xx.conversion-set.conversion.calibrated.defined' :
+ ['trash', lambda x: eval(x.title())],
+'lcd-info.xx.conversion-set.conversion.calibrated.type' :
+ ['trash', lambda x: str(x).strip('\n')],
+'lcd-info.xx.conversion-set.conversion.calibrated.file' :
+ ['trash', lambda x: str(x).strip('\n')],
+'lcd-info.xx.conversion-set.conversion.calibrated.comment' :
+ ['trash', lambda x: str(x).strip('\n')],
+'lcd-info.xx.conversion-set.conversion.calibrated.base-calibration-slot' :
+ ['trash', lambda x: str(x).strip('\n')],
+'lcd-info.xx.conversion-set.conversion.calibrated.calibration-slot' :
+ ['trash', lambda x: str(x).strip('\n')],
+'lcd-info.xx.conversion-set.conversion.calibrated.scaling.type' :
+ ['trash', lambda x: str(x).strip('\n')],
+'lcd-info.xx.conversion-set.conversion.calibrated.scaling.style' :
+ ['trash', lambda x: str(x).strip('\n')],
+'lcd-info.xx.conversion-set.conversion.calibrated.scaling.offset' :
+ ['calibrated_offset.xx', eval],
+'lcd-info.xx.conversion-set.conversion.calibrated.scaling.multiplier' :
+ ['calibrated_multiplier.xx', eval],
+'lcd-info.xx.conversion-set.conversion.calibrated.scaling.unit.type' :
+ ['trash', lambda x: str(x).strip('\n')],
+'lcd-info.xx.conversion-set.conversion.calibrated.scaling.unit.unit' :
+ ['trash', lambda x: str(x).strip('\n')],
+'lcd-info.xx.conversion-set.conversion.elapsed.name' :
+ ['trash', lambda x: str(x).strip('\n')],
+'lcd-info.xx.conversion-set.conversion.elapsed.defined' :
+ ['trash', lambda x: eval(x.title())],
+'lcd-info.xx.encoder.type' :
+ ['encoder_type.xx', lambda x: str(x).strip('\n')],
+'lcd-info.xx.encoder.scaling.type' :
+ ['trash', lambda x: str(x).strip('\n')],
+'lcd-info.xx.encoder.scaling.style' :
+ ['trash', lambda x: str(x).strip('\n')],
+'lcd-info.xx.encoder.scaling.offset' :
+ ['encode_offset.xx', eval],
+'lcd-info.xx.encoder.scaling.multiplier' :
+ ['encode_multiplier.xx', eval],
+'lcd-info.xx.encoder.scaling.unit.type' :
+ ['trash', lambda x: str(x).strip('\n')],
+'lcd-info.xx.encoder.scaling.unit.unit' :
+ ['trash', lambda x: str(x).strip('\n')],
+}
diff --git a/openfovea/fovea_toolbox/file_util/nanoscope.py b/openfovea/fovea_toolbox/file_util/nanoscope.py
new file mode 100644
index 0000000..fd78e23
--- /dev/null
+++ b/openfovea/fovea_toolbox/file_util/nanoscope.py
@@ -0,0 +1,305 @@
+#! /usr/bin/python
+# -*- coding: utf-8 -*-
+'''
+This module is used to parse the files form Digital Instrument AFM
+'''
+__author__ = "Charles Roduit <charles.roduit at gmail.com>"
+__date__ = "01.07.2007"
+__license__ = "GNU Public License (GPL) version 3"
+__version__ = "0.1"
+
+import os
+from struct import unpack
+from datetime import datetime #strptime
+import zipfile
+
+import numpy as num
+
+from common import Variables, extract_num, extract_num_rev
+
+GLOB_VARS = Variables()
+
+
+def clean_header(header):
+ new_header = dict()
+ for item in GLOB_VARS.common_header:
+ try:
+ new_header[item] = header[item]
+ except KeyError:
+ # This key will keep the default value...
+ new_header[item] = GLOB_VARS.common_header[item]
+ return new_header
+
+def lect_header(file_name):
+ """
+ Cette fonction lit le fichier d'entete du bioscope
+ Elle demande le nom du fichier
+ Elle retourne un dictionnaire repetroriant toutes
+ les information utiles pour traiter les courbes
+ forces-volumes ou/et l'image topologique.
+
+ >>> header=lect_header(fileName)
+ """
+
+ file_id = open(file_name,'rb')
+ line_in_mem = file_id.readline()
+
+ one_image = 0
+ header = GLOB_VARS.fv_header
+ header_pos = 'None'
+ version = {8.0: 0,
+ 7.0: 0,
+ 6.1: 0}
+ if not '\*Force file list' in line_in_mem:
+ # This is not a nanoscoe file
+ raise TypeError
+ while not '\*File list end' in line_in_mem:
+ ###### Determiner la postition dans l'entete ######
+ if '\*Force file list' in line_in_mem:
+ header_pos = 'File Description'
+ elif '\*Equipment list' in line_in_mem:
+ header_pos = 'Equipment list'
+ elif '\*Ciao scan list' in line_in_mem:
+ header_pos = 'Scan list'
+ elif '\*Ciao force list' in line_in_mem:
+ header_pos = 'Force list'
+ elif '\*Ciao image list' in line_in_mem:
+ header_pos = 'Image list'
+ elif '\*Ciao force image lis' in line_in_mem:
+ header_pos = 'Force image'
+ elif header_pos == 'File Description':
+ if line_in_mem.find('\Version:') + 1:
+ header['di_version'] = extract_num_rev(line_in_mem)
+ if 6130000 < header['di_version'] < 6200000:
+ version[6.1] = 1
+ elif 7000000 < header['di_version'] < 8000000:
+ version[7.0] = 1
+ elif 8000000 < header['di_version'] < 9000000:
+ version[8.0] = 1
+ elif line_in_mem.find('\Date:') + 1:
+ #find the last digit
+ for place, item in enumerate(line_in_mem):
+ if item.isdigit():
+ last=place+1
+ date = line_in_mem[7:last]
+
+ header['date'] = datetime.strptime(date,
+ '%I:%M:%S %p %a %b %d %Y')
+
+ ######### Description de l'equipement ############
+ elif header_pos == 'Equipment list':
+ if line_in_mem.find('\@Sens. Zscan: V') + 1:
+ header['sens_z_scan'] = float(extract_num(line_in_mem))
+ elif (version[6.1] or version[7.0] or version[8.0]) and \
+ line_in_mem.find('\@Sens. Zsens: V') + 1:
+
+ header['sens_z_scan'] = float(extract_num(line_in_mem))
+ ######### des parametres du scan ##################
+ elif header_pos == 'Scan list':
+ if line_in_mem.find('\Operating mode') + 1:
+ if line_in_mem.find('Force Volume') + 1:
+ header['image_mode'] = 'Force Volume'
+ elif line_in_mem.find('\Scan size') + 1:
+ _ssize = extract_num(line_in_mem)
+ header['scan_size'] = (_ssize, _ssize)
+ elif (version[7.0] or version[8.0]) and line_in_mem.find('\Scan Size') + 1:
+ _ssize = extract_num(line_in_mem)
+ header['scan_size'] = (_ssize, _ssize)
+ elif line_in_mem.find('\@Sens. Deflection: V') + 1:
+ header['sensit_deflection'] = extract_num(line_in_mem)
+ elif (version[6.1] or version[7.0] or version[8.0] or version[6.1]) and \
+ line_in_mem.find('\@Sens. DeflSens: V') + 1:
+
+ header['sensit_deflection'] = extract_num(line_in_mem)
+
+ ####### Parametre de l'acquisition des courbes de force ######
+ elif header_pos == 'Force list':
+ if line_in_mem.find('\\force/line') + 1:
+ header['matrix_length'] = extract_num(line_in_mem)
+ #header['size_x'] = header['matrix_length']
+ #header['size_y'] = header['matrix_length']
+ header['size'] = (header['matrix_length'], header['matrix_length'])
+ ########## Parametres de l'image ########################
+ elif header_pos == 'Image list':
+ if line_in_mem.find('\Number of lines') + 1:
+ header['image_number_lines'] = extract_num(line_in_mem)
+ elif line_in_mem.find('\@2:Z scale') + 1:
+ header['image_scale'] = extract_num_rev(line_in_mem)
+ elif line_in_mem.find('\Samps/line') + 1:
+ header['image_samps_per_line'] = extract_num(line_in_mem)
+ elif line_in_mem.find('\Data length') + 1:
+ header['image_length'] = extract_num(line_in_mem)
+ elif line_in_mem.find('\Data offset') + 1:
+ if not one_image:
+ header['image_offset'] = extract_num(line_in_mem)
+ one_image = 1
+ elif line_in_mem.find('\Note:') + 1:
+ header['note'] = line_in_mem[7:]
+ while line_in_mem[-3:]=='\r\r\n':
+ line_in_mem = file_id.readline()
+ try:
+ line_in_mem = u'' + line_in_mem
+ header['note'] = header['note'] + line_in_mem
+ except UnicodeDecodeError:
+ line_in_mem = line_in_mem.decode('cp1251')
+ header['note'] = header['note'] + line_in_mem
+ header['note'] = header['note'].replace('\r', '')
+
+ ######### Parametre des courbes de force ###############
+ elif header_pos == 'Force image':
+ if line_in_mem.find('\Data offset:') + 1:
+ header['force_data_offset'] = extract_num(line_in_mem)
+
+ elif line_in_mem.find('\Data length:') + 1:
+ header['force_data_length'] = extract_num_rev(line_in_mem)
+
+ elif line_in_mem.find('\Bytes/pixel:') + 1:
+ header['force_bytes_per_pixel'] = extract_num_rev(line_in_mem)
+
+ elif line_in_mem.find('\Samps/line') + 1:
+ header['force_samples_per_curve'] = extract_num(line_in_mem)
+
+ elif line_in_mem.find('\Data type: FORCE') + 1:
+ pass
+ ## Dans la version originale, il y avait un NFJflag=1...
+ elif line_in_mem.find('\@4:Z scale: V') + 1:
+ #header['z_scale'] = extract_num_rev(file_id)
+ header['hard_z_scale'] = extract_num(line_in_mem, pos=1)
+
+ #elif line_in_mem.find('\@4:FV scale: V') + 1:
+ # header['fv_scale'] = extract_num_rev(file_id)
+
+ elif line_in_mem.find('\@4:Ramp size: V') + 1:
+ header['ramp_size'] = extract_num_rev(line_in_mem)
+
+ elif line_in_mem.find('\Spring constant') + 1:
+ header['spring_constant'] = extract_num_rev(line_in_mem)
+
+ elif (version[6.1] or version[7.0] or version[8.0] or version[6.1]) and \
+ line_in_mem.find('\Spring Constant:') + 1:
+ header['spring_constant'] = extract_num_rev(line_in_mem)
+
+ #####################################################
+ line_in_mem = file_id.readline()
+ header['x_factor'] = header['ramp_size'] * \
+ header['sens_z_scan'] / header['force_samples_per_curve']
+
+ if header['force_data_length'] != (2 * (header['matrix_length'] ** 2) *
+ header['force_bytes_per_pixel'] *
+ header['force_samples_per_curve']):
+ header['matrix_length'] = int(num.sqrt(header['force_data_length'] /
+ (header['force_bytes_per_pixel'] *
+ header['force_samples_per_curve'] *
+ 2)))
+# header['size'] = (header['matrix_length'], header['matrix_length'])
+# header['size_x'] = header['matrix_length']
+# header['size_y'] = header['matrix_length']
+ header['number_curves'] = header['matrix_length'] ** 2
+ return header
+def load_force_curves(file_name, header):
+ '''
+ This function reads the AFM file (file_name) with the help of header
+ information. The header is the dictionnary returned by the function
+ lect_header.It returns the force curves contained in the file in two
+ 3D numpy arrays. The first one contain the advance force curves and the
+ second one the retraction force curves.
+
+ >>> [im_3d_av,3d_re]=load_force_curves(file_name,header)
+ >>> print(im_3d_av[0,0,:]) # print the values of the first advance
+ >>> # force curve
+ '''
+
+ file_id = open(file_name, 'rb')
+ file_id.seek(header['force_data_offset'], 0)
+
+ curve_av = num.zeros([header['force_samples_per_curve']])
+ curve_re = num.zeros([header['force_samples_per_curve']])
+
+ im_3d_av = num.zeros([header['matrix_length'],
+ header['matrix_length'],
+ header['force_samples_per_curve']], num.float)
+
+ im_3d_re = num.zeros([header['matrix_length'],
+ header['matrix_length'],
+ header['force_samples_per_curve']], num.float)
+ pos_y = 0
+ pos_x = 0
+ while pos_y < header['matrix_length']:
+ while pos_x < header['matrix_length']:
+ # charger la courbe x, y
+ curve_av[:] = unpack(str(header['force_samples_per_curve']) + 'h',
+ file_id.read(2 * header['force_samples_per_curve']))
+ curve_re[:] = unpack(str(header['force_samples_per_curve']) + 'h',
+ file_id.read(2 * header['force_samples_per_curve']))
+ # mettre le minimun a 0
+ curve_av = curve_av - min(curve_av)
+ curve_re = curve_re - min(curve_re)
+ # Correction des echelles
+ # (http://web.mit.edu/cortiz/www/MultimodeInstructions.doc)
+ curve_av = curve_av * header['hard_z_scale'] * \
+ header['sensit_deflection']
+ curve_re = curve_re * header['hard_z_scale'] * \
+ header['sensit_deflection']
+
+ im_3d_av[pos_x, pos_y, ] = correct_curve_drop(curve_av)
+ im_3d_re[pos_x, pos_y, ] = correct_curve_drop(curve_re)
+ pos_x += 1
+ pos_x = 0
+ pos_y += 1
+ return [im_3d_av, im_3d_re]
+
+def correct_curve_drop(curve_y):
+ '''
+ Corrects the drops that occurs at the beginning of some force-distance
+ curves. This function corrects a bug from Veeco AFM.
+ '''
+ try:
+ first_non_zero = curve_y.nonzero()[0][-1]
+ except IndexError:
+ # The vector is full of zero, then do nothing
+ return curve_y
+ if not first_non_zero == len(curve_y)-1:
+ first_non_zero = curve_y.nonzero()[0][-1]
+ for indice in range(first_non_zero, len(curve_y)):
+ curve_y[indice] = curve_y[first_non_zero]
+ curve_y = curve_y - min(curve_y)
+ return curve_y
+
+def load_image(file_name, header):
+ '''
+ This function returns the piezo height image contained in the AFM file
+ as a numpy matrix
+
+ >>> imageMatrix=load_image(fileName,header)
+ '''
+ if header.has_key('image_offset') and header['image_offset'] is not None:
+ file_id = open(file_name, 'rb')
+ im_matrix = num.zeros([header['matrix_length'],
+ header['matrix_length']], num.float)
+ pos_y = 0
+ pos_x = 0
+ while pos_y < header['matrix_length']:
+ while pos_x < header['matrix_length']:
+ temp_offset = header['image_offset'] + (
+ 2 * pos_x * (header['image_number_lines'] /
+ header['matrix_length']) *
+ header['image_number_lines'] +
+ 2 * pos_y * (header['image_samps_per_line'] /
+ header['matrix_length']))
+ file_id.seek(temp_offset, 0)
+ im_matrix[pos_y, pos_x] = unpack('h', file_id.read(2))[0]
+ pos_x += 1
+ pos_x = 0
+ pos_y += 1
+ ## Scale the image
+ im_matrix = im_matrix * (header['image_scale'] *
+ header['sens_z_scan'] /
+ (2. ** (2 * 8)))
+ pos_x = 0
+ minimum = im_matrix.min()
+ im_matrix = im_matrix - minimum
+ else:
+ im_matrix = num.zeros([header['matrix_length'],
+ header['matrix_length']])
+ return im_matrix
+
diff --git a/openfovea/fovea_toolbox/file_util/succellus_import.py b/openfovea/fovea_toolbox/file_util/succellus_import.py
new file mode 100644
index 0000000..022b1b6
--- /dev/null
+++ b/openfovea/fovea_toolbox/file_util/succellus_import.py
@@ -0,0 +1,323 @@
+#!/usr/bin/env python
+
+from xml.dom import minidom
+import time
+import pylab
+import numpy
+
+def parse(file_name):
+ xmldoc=minidom.parse("C050614A.002.xml")
+ child=xmldoc.firstChild
+ FVTags=["fileName", "mainExpIndice", "date", "header", "image",
+ "pointOfContact", "topography", "StiffnessesList", "events",
+ "CurveX", "CurveYAv", "CurveYRe"]
+ dateTag=["year", "month", "day", "hour", "minute", "second"]
+ headerTag=["SensZScan", "OpMode", "ScanSize", "NFL", "ScanListScanRate",
+ "SensitDeflection", "ScanRate", "ForceListForwVeloc>",
+ "ForceListRevVeloc", "NumberCurvesPerLines",
+ "ForceListFVScanRate", "Z_SCAN_START", "Z_SCAN_SIZE", "TTD",
+ "ImageOffset", "ImageLength", "ImageSampsPerLine",
+ "ImageNumberLines", "ImageScale", "ForceOffset",
+ "NumberPointsPerCurves", "SpringConstant", "Z_SCALE",
+ "HARD_Z_SCALE", "FV_SCALE", "RAMP_SIZE"]
+ imageTag=["data"]
+ imageAtt=["Contrast"]
+ pocTag=["data"]
+ pocAtt=["Contrast","Method"]
+ topoTag=["data"]
+ topoAtt=["Contrast"]
+ stiffListAtt=["Contrast","Deep","GlassFit","HertzModel","SegmentNumber",
+ "TipCar-Radius","TipCar-SemiAngle"]
+ stiffListTag=["StiffnessMatrix"]
+ stiffMatrixAtt=["DeepNbr"]
+ stiffMatrixTag=["data"]
+ eventAtt=["Type"]
+ eventTag=["EventDetail","EventMatrix"]
+ eventDetAtt=["PosX","PosY","XValues","YValues","force","jumpSlope","maxX",
+ "maxY","minX","minY"]
+ EventMatrixTag=["data"]
+ CurveYAvTag=["data"]
+ CurveYReTag=["data"]
+ result=dict()
+
+def xmlExtractImage(elm):
+ nodeDic=dict()
+ attDic=dict()
+ for att in ["Contrast","Method","DeepNbr"]:
+ if elm.hasAttribute(att):
+ attDic[att]=elm.getAttribute(att)
+ nodeDic["attributes"]=attDic
+ matrix=[]
+ for data in elm.childNodes:
+ if data.nodeName=="data":
+ matrix.append([float(s) for s in
+ data.childNodes[0].data.split(",")[0:-1]])
+ nodeDic["matrix"]=numpy.array(matrix)
+ return nodeDic
+
+def xmlExtractVector(elm):
+ vectors=list()
+ for data in elm.childNodes:
+ if data.nodeName=="data":
+ vectors.append([float(s) for s in
+ data.childNodes[0].data.split(",")[0:-1]])
+ vectors=numpy.array(vectors)
+ return vectors
+
+def restructure_header(old_header, version):
+ header = dict()
+ if not version:
+ # we come from the succellus version
+ header['FVSCALE']=old_header['FV_SCALE']
+ header['ForceListFVScanRate']=old_header['ForceListFVScanRate']
+ ## miss ForceListForwVeloc
+ header['ForceListForwVeloc']=old_header['ForceListForwVeloc']
+ header['ForceListRevVeloc']=old_header['ForceListRevVeloc']
+ header['Force Data offset']=old_header['ForceOffset']
+ #header[]=old_header['HARD_Z_SCALE']
+ header['ImageLength']=old_header['ImageLength']
+ header['ImageNumberLines']=old_header['ImageNumberLines']
+ #header['ImageOffset']=old_header['ImageOffset']
+ header['ImageSampsPerLine']=old_header['ImageSampsPerLine']
+ header['ImageScale']=old_header['ImageScale']
+ #header['NFL']=old_header['NFL']
+ header['matrix_length']=old_header['NumberCurvesPerLines']
+ header['size_x'] = header['matrix_length']
+ header['size_y'] = header['matrix_length']
+ header['ImageSampsPerLine']=old_header['NumberPointsPerCurves']
+ header['image mode']=old_header['OpMode']
+ header['RAMPSIZE']=old_header['RAMP_SIZE']
+ header['ScanListScanRate']=old_header['ScanListScanRate']
+ #header['scan rate']=old_header['ScanRate']
+ header['scan size']=old_header['ScanSize']
+ header['sens z scan']=old_header['SensZScan']
+ header['sensit deflection']=old_header['SensitDeflection']
+ header['spring_constant']=old_header['SpringConstant']
+ header['TTD']=old_header['TTD']
+ header['ZSCALE']=old_header['Z_SCALE']
+ header['Z_SCAN_SIZE']=old_header['Z_SCAN_SIZE']
+ header['ZSCANSTART']=old_header['Z_SCAN_START']
+ header['matrix_length'] = int(header['matrix_length'])
+ header['size_x'] = int(header['size_x'])
+ header['size_y'] = int(header['size_y'])
+ header['spring_constant'] = float(header['spring_constant'])
+ return header
+
+def restructure_stiffness(old_stiffness, version):
+ stiffness = dict()
+ if not version:
+ for key in old_stiffness.keys():
+ if key == 'GlassFit':
+ stiffness['glass'] = -float(old_stiffness[key][0])
+ #print type(stiffness['glass'])
+ elif key == 'SegmentNumber':
+ stiffness['nbParts'] = int(old_stiffness[key])
+ #TODO introduce the stiffness tomography...
+ if not stiffness['nbParts']:
+ stiffness['nbParts'] = 4
+ elif key == 'Deep':
+ stiffness['sizeParts'] = float(old_stiffness[key])
+ else:
+ stiffness[key] = old_stiffness[key]
+ #print stiffness
+ return stiffness
+
+def restructure_event(old_event, version):
+ event = list()
+ if not version:
+ # we come from the succellus version
+ for old_item in old_event:
+ item = dict()
+ item['pos_x'] = old_item['PosX']-1
+ item['pos_y'] = old_item['PosY']-1
+ item['slice'] = slice(old_item['XValues'][0],
+ old_item['XValues'][-1])
+ item['min'] = None
+ item['max'] = None
+ item['slope'] = old_item['jumpSlope']
+ item['force'] = old_item['force']
+ event.append(item)
+ return event
+
+def restructure_curves(old_curve_matrix, version):
+ if not version:
+ #curve_matrix = old_curve_matrix[:,:,::-1]
+ pass
+ pass
+ #return curve_matrix
+
+def xmlExtractFV(fileName):
+ xmldoc=minidom.parse(fileName)
+ child=xmldoc.firstChild
+ FVTags=["fileName","mainExpIndice","date","header","image","pointOfContact",
+ "topography","StiffnessesList","events","CurveX","CurveYAv",
+ "CurveYRe"]
+ dateTag=["year","month","day","hour","minute","second"]
+ headerTag=["SensZScan","OpMode","ScanSize","NFL","ScanListScanRate",
+ "SensitDeflection","ScanRate","ForceListForwVeloc",
+ "ForceListRevVeloc","NumberCurvesPerLines","ForceListFVScanRate",
+ "Z_SCAN_START","Z_SCAN_SIZE","TTD","ImageOffset","ImageLength",
+ "ImageSampsPerLine","ImageNumberLines","ImageScale","ForceOffset",
+ "NumberPointsPerCurves","SpringConstant","Z_SCALE","HARD_Z_SCALE",
+ "FV_SCALE","RAMP_SIZE"]
+ imageTag=["data"]
+ imageAtt=["Contrast"]
+ pocTag=["data"]
+ pocAtt=["Contrast","Method"]
+ topoTag=["data"]
+ topoAtt=["Contrast"]
+ stiffListAtt=["Contrast","Deep","GlassFit","HertzModel","SegmentNumber",
+ "TipCar-Radius","TipCar-SemiAngle"]
+ stiffListTag=["StiffnessMatrix"]
+ stiffMatrixAtt=["DeepNbr"]
+ stiffMatrixTag=["data"]
+ eventAtt=["Type"]
+ eventTag=["EventDetail","EventMatrix"]
+ eventDetAtt=["PosX","PosY","XValues","YValues","force","jumpSlope","maxX",
+ "maxY","minX","minY"]
+ EventMatrixTag=["data"]
+ CurveYAvTag=["data"]
+ CurveYReTag=["data"]
+ result=dict()
+ if child.hasAttribute('openfovea:version'):
+ aex_version = child.getAttribute('openfovea:version')
+ else:
+ aex_version = 0
+ for elm in child.childNodes:
+ if elm.nodeName==FVTags[0]:#fileName
+ result["name"]=elm.childNodes[0].data
+
+ elif elm.nodeName==FVTags[1]:#mainExpIndice
+ result["indice"]=int(elm.childNodes[0].data)
+
+ elif elm.nodeName==FVTags[2]:#date
+ date=dict()
+ for dateElm in elm.childNodes:
+ if dateElm.nodeName in dateTag:
+ date[str(dateElm.nodeName)]=int(dateElm.childNodes[0].data)
+ result["date"]=date
+ del(date)
+
+ elif elm.nodeName==FVTags[3]:#header
+ header=dict()
+ for headerElm in elm.childNodes:
+ if headerElm.nodeName in headerTag:
+ header[str(headerElm.nodeName)]=headerElm.childNodes[0].data
+ header = restructure_header(header, aex_version)
+ result["header"]=header
+ del(header)
+
+ elif elm.nodeName==FVTags[4]:#Piezo Image
+ result['piezo_array']=xmlExtractImage(elm)['matrix']
+ elif elm.nodeName==FVTags[5]:#Point of contact
+ result["poc_array"]=xmlExtractImage(elm)['matrix']
+ elif elm.nodeName==FVTags[6]:#zero force image
+ result["topography"]=xmlExtractImage(elm)
+
+ elif elm.nodeName==FVTags[7]:#StiffnessesList
+ attDic=dict()
+ nodeDic=dict()
+ #TODO Pretty import values
+ for att in stiffListAtt:
+ if elm.hasAttribute(att):
+ attDic[att]=elm.getAttribute(att)
+ attDic = restructure_stiffness(attDic, aex_version)
+ nodeDic["properties"]=attDic
+ matrixList=[]
+ for childElm in elm.childNodes:
+ if childElm.nodeName=="StiffnessMatrix":
+ matrixList.append(xmlExtractImage(childElm))
+ matrix=[]
+ for depthMatrix in matrixList:
+ matrix.append(depthMatrix['matrix'])
+ nodeDic["array"]=numpy.array(matrix)
+ nodeDic["array"] = nodeDic["array"].transpose(2,1,0)
+ nodeDic["array"] = numpy.ma.MaskedArray(
+ nodeDic["array"],
+ mask = nodeDic["array"] == 0)
+ result["stiffness"]=nodeDic
+ del(attDic,nodeDic,matrix,matrixList)
+
+ elif elm.nodeName==FVTags[8]:#events
+ events_det=list()
+ #TODO Pretty import values
+ matrix = None
+ for childElm in elm.childNodes:
+ if childElm.nodeName=="EventDetail":
+ thisEvt=dict()
+ for att in eventDetAtt:
+ if childElm.hasAttribute(att):
+ thisEvt[att]=childElm.getAttribute(att)
+ if att in ["XValues","YValues"]:
+ thisEvt[att]=[float(s) for s in
+ thisEvt[att][0:-1].split(",")]
+ else:
+ thisEvt[att]=float(thisEvt[att])
+ events_det.append(thisEvt)
+ elif childElm.nodeName=="EventMatrix":
+ matrix=xmlExtractImage(childElm)
+ matrix=matrix["matrix"]
+ events=dict()
+ events_det = restructure_event(events_det, aex_version)
+ events["detail"]=events_det
+ events["matrix"]=matrix
+ result["events"]=events
+ del(events,matrix)
+
+ elif elm.nodeName==FVTags[9]:#CurveX
+ result["curve_x_array"]=[float(s) for s in
+ elm.childNodes[0].data.split(",")[0:-1]]
+ result['curve_x_array'] = numpy.asarray(result['curve_x_array'][::-1])
+
+ elif elm.nodeName==FVTags[10]:#CurveYAv
+ result["trace_array"]=xmlExtractVector(elm)
+ if result.has_key("header"):
+ nbLines=int(result["header"]["matrix_length"])
+ nbPoints=result["trace_array"].shape[1]
+ result["trace_array"]=result["trace_array"].reshape(nbLines,nbLines,nbPoints)
+ #result['trace_array'] = result['trace_array'].transpose(1,0,2)
+ #result['trace_array'] = restructure_curves(result['trace_array'],
+ # aex_version)
+
+ elif elm.nodeName==FVTags[11]:#CurveYRe
+ result["retrace_array"]=xmlExtractVector(elm)
+ if result.has_key("header"):
+ nbLines=int(result["header"]["matrix_length"])
+ nbPoints=result["retrace_array"].shape[1]
+ result["retrace_array"]=result["retrace_array"].reshape(nbLines,nbLines,nbPoints)
+ #result['retrace_array'] = result['retrace_array'].transpose(1,0,2)
+ #result['retrace_array'] = restructure_curves(result['retrace_array'],
+ # aex_version)
+ result['header']['date'] = result['date']
+ return result
+
+def xmlExtractExperiment(fileName):
+ xmldoc=minidom.parse(fileName)
+ child=xmldoc.firstChild
+ node=["date","auteur","description","NumberForceVolumeFiles","ForceVolumeNames"]
+ result=dict()
+ description=list()
+ result["ForceVolumeNames"]=list()
+ for elm in child.childNodes:
+ if elm.nodeName==node[0]:
+ result["date"]=time.strptime(elm.childNodes[0].data,'%d-%b-%Y')
+ elif elm.nodeName==node[1]:
+ result["author"]=elm.childNodes[0].data
+ elif elm.nodeName==node[2]:
+ description.append(elm.childNodes[0].data)
+ elif elm.nodeName==node[3]:
+ result["numberFiles"]=int(elm.childNodes[0].data)
+ elif elm.nodeName==node[4]:
+ for child in elm.childNodes:
+ if child.nodeName=='data':
+ result["ForceVolumeNames"].append(child.childNodes[0].data)
+ strDesc=str()
+ for line in description:
+ strDesc=strDesc+line+'\n'
+ result['comment']=strDesc[0:-1]
+ return result
+if __name__=="__main__":
+ result=xmlExtractExperiment("experiment.xml")
+
+ #result=xmlExtractFV("C050614A.002.xml")
+ print result
diff --git a/openfovea/fovea_toolbox/misc.py b/openfovea/fovea_toolbox/misc.py
new file mode 100644
index 0000000..dcf5729
--- /dev/null
+++ b/openfovea/fovea_toolbox/misc.py
@@ -0,0 +1,360 @@
+#! /usr/bin/python
+# -*- coding: utf-8 -*-
+'''
+This module contains the second stage posprocessing tools.
+'''
+__author__ = "Charles Roduit <charles.roduit at gmail.com>"
+__date__ = "28.12.2008"
+__license__ = "GNU Public License (GPL) version 3"
+__version__ = "0.1"
+
+from warnings import warn
+import os
+import csv
+
+import numpy
+import urllib2
+import datetime
+
+def remove_from_mask(data, mask):
+ """
+ Remove data from the mask.
+
+ Let's have a data of this kind :
+
+ >>> import numpy
+ >>> data = [{'pos_x' : 0, 'pos_y' : 0, 'item' : 0},\
+ {'pos_x' : 0, 'pos_y' : 1, 'item' : 1},\
+ {'pos_x' : 0, 'pos_y' : 2, 'item' : 2},\
+ {'pos_x' : 0, 'pos_y' : 3, 'item' : 3},\
+ {'pos_x' : 1, 'pos_y' : 3, 'item' : 4},\
+ {'pos_x' : 2, 'pos_y' : 0, 'item' : 5},\
+ {'pos_x' : 2, 'pos_y' : 3, 'item' : 6},\
+ ]
+
+ And let's have a mask of this kind :
+
+ >>> mask = numpy.array([[True, True, False, False],\
+ [True, True, False, False],\
+ [True, True, False, False],\
+ [True, True, False, False]])
+
+ Now, let's apply the remove from mask :
+
+ >>> new_list = remove_from_mask(data, mask)
+ """
+ # Data can be from event structure.
+ # In the data structure, we have info about the position on the array. We
+ # then exclude data that are in the mask. data['pos_x'] and data['pos_y']
+ new_list = []
+ for item in data:
+ if not mask[item['pos_x'], item['pos_y']]:
+ new_list.append(item)
+ return new_list
+
+def generate_mask(array, value):
+ mask = array < mdata
+ return mask
+
+def mask_bool(mask_1, mask_2, fct):
+ """
+ Make boolean operation on mask arrays.
+
+ Boolean operation can be AND, OR, XOR, NOT
+
+ >>> array_1 = [True, True, False, False]
+ >>> array_2 = [False, True, False, True]
+ >>> mask_bool(array_1, array_2, "AND")
+ array([False, True, False, False], dtype=bool)
+ >>> mask_bool(array_1, array_2, "OR")
+ array([ True, True, False, True], dtype=bool)
+ >>> mask_bool(array_1, array_2, "XOR")
+ array([ True, False, False, True], dtype=bool)
+ """
+
+ # If the two arrays does not have the same shape, raise exception
+ #if numpy.shape(mask_1) != numpy.shape(mask_2):
+ # raise ValueError, "operands could not be broadcast together with shapes {0} {1}".format(numpy.shape(mask_1), numpy.shape(mask_2))
+ #__mask_1 = check_bool(mask_1)
+ #__mask_2 = check_bool(mask_2)
+ if fct == "AND":
+ return numpy.logical_and(mask_1, mask_2)
+ elif fct == "OR":
+ return numpy.logical_or(mask_1, mask_2)
+ elif fct == "XOR":
+ return numpy.logical_xor(mask_1, mask_2)
+ else:
+ raise ValueError, "function %s is not known"%(fct)
+
+def check_bool(array):
+ """
+ Check if array is boolean and change it if not.
+ """
+
+ try:
+ mtype = array.dtype
+ except:
+ __array = numpy.array(array, dtype=bool)
+ else:
+ if mtype is not bool:
+ __array = numpy.array(array, dtype=bool)
+ else:
+ __array = array
+ return array
+
+def generate_array(data, dtype, shape):
+ """
+ Generates the array corresponding to the data. Data is a list of
+ dictionnary. These dictionnaries should, at least, contain the following
+ keys : 'pos_x' and 'pos_y' to inform on the position on the array and
+ dtype for the value that should come in the array (with some exeptions,
+ see parameters below).
+
+ Parameters :
+ data : list
+ List of dictionnaries. All dictionnaries should contain the
+ following keys :
+ 'pos_x' and 'pos_y' : position in the array
+ ttype : the threshold type
+ dtype : the data to insert in the array
+ dtype : str
+ The data type to insert in the array. This should be one key
+ present in the dictionnaries or :
+ 'count' : The number of data that are in each array position.
+ shape : tuple
+ The shape of the returned array.
+
+ Returns :
+ new_array : 2D numpy.array
+ The array generated with the parameters.
+ >>> data = [{'pos_x' : 0, 'pos_y' : 0, 'item' : 2},\
+ {'pos_x' : 0, 'pos_y' : 1, 'item' : 2},\
+ {'pos_x' : 0, 'pos_y' : 1, 'item' : 1},\
+ {'pos_x' : 0, 'pos_y' : 2, 'item' : 2},\
+ {'pos_x' : 0, 'pos_y' : 3, 'item' : 2},\
+ {'pos_x' : 1, 'pos_y' : 3, 'item' : 3},\
+ {'pos_x' : 2, 'pos_y' : 0, 'item' : 1},\
+ {'pos_x' : 2, 'pos_y' : 3, 'item' : 2},\
+ ]
+ >>> new_data = remove_from_threshold(data, 'item', 1)
+ >>> narray = generate_array(new_data, 'count', (4,4))
+ >>> print narray
+ [[ 1. 2. 1. 1.]
+ [ 0. 0. 0. 1.]
+ [ 1. 0. 0. 1.]
+ [ 0. 0. 0. 0.]]
+ """
+ new_array = numpy.zeros(shape)
+ for item in data:
+ res = False
+ if dtype == 'count':
+ new_array[item['pos_x'], item['pos_y']] += 1
+ else:
+ # Get the correct value to fill the array
+ if dtype in ['fit_length']:
+ try:
+ value = item[dtype][0]
+ except TypeError:
+ value = None
+ else:
+ value = item[dtype]
+ # If there is no value, nothing to do.
+ if value is None:
+ res = False
+ else:
+ res = value > new_array[item['pos_x'], item['pos_y']]
+ if res:
+ new_array[item['pos_x'], item['pos_y']] = value
+ return new_array
+
+def remove_from_threshold(data, atype, avalue):
+ """
+ Remove from the data the type that are below a value.
+
+ >>> import numpy
+ >>> data = [{'min' : -0.5, 'max' : 0,'loading_rate' : 0.1, 'dist' : 10 },\
+ {'min' : -0.6, 'max' : -0.1,'loading_rate' : 0.2, 'dist' : 11 },\
+ {'min' : -0.5, 'max' : 0,'loading_rate' : 0.15, 'dist' : 12 },\
+ {'min' : -0.4, 'max' : 0.1,'loading_rate' : 0.12, 'dist' : 9 },\
+ {'min' : -0.3, 'max' : 0.2,'loading_rate' : 0.11, 'dist' : 11 },\
+ {'min' : -0.5, 'max' : 0,'loading_rate' : 0.09, 'dist' : 10 },\
+ {'min' : -0.6, 'max' : -0.1,'loading_rate' : 0.11, 'dist' : 12 },\
+ ]
+ >>> new_list = remove_from_threshold(data, 'dist', 10)
+ >>> #new_list = remove_from_threshold(data, 'distance', 10)
+ >>> new_list = remove_from_threshold([], 'dist', 10)
+ """
+ if type(avalue) in [float, int]:
+ # before, avalue was a single string.
+ avalue = [avalue, None]
+ if data is None or len(data) == 0:
+ return data
+ if atype not in data[0].keys():
+ raise AttributeError, atype + ' not a valid key for your data.'
+ if atype in ['fit_length', 'fit_plength']:
+ # In those cases, the data is in a list where the first is the value and
+ # the second is the standard deviation like : [value, stdev]
+ try:
+ if avalue[0] is not None:
+ data = [item for item in data if item[atype][0] >= avalue[0]]
+ except TypeError:
+ pass
+ try:
+ if avalue[1] is not None:
+ data = [item for item in data if item[atype][0] <= avalue[1]]
+ except TypeError:
+ pass
+ else:
+ if avalue[0] is not None:
+ data = [item for item in data if item[atype] >= avalue[0]]
+ if avalue[1] is not None:
+ data = [item for item in data if item[atype] <= avalue[1]]
+ return data
+
+def flatten(data):
+ """
+ Flatten an image.
+
+ >>> import numpy
+ >>> data = numpy.array([range(10) for i in range(10)])
+ >>> flatten_data = flatten(data)
+
+ """
+ vect_x = numpy.arange(data.shape[1])
+ # Determine the slope of the drift.
+ power = [numpy.polyfit(vect_x, line, 1)[0] for line in data]
+ m_pow = numpy.mean(power)
+ # Correct the image with the average slope.
+ new_data = [line - m_pow * vect_x for line in data]
+ new_data = numpy.array(new_data)
+ return new_data
+
+def get_message(curr_id, app_name='OpenFovea', app_version='0.1a1'):
+ """
+ Get message from the server.
+ """
+ message = None
+ headers = {'User-Agent' : '%s/%s'%(app_name, app_version)}
+ if curr_id == datetime.datetime(1900, 1, 1):
+ # This is the first run.
+ # Display then the welcome dialog
+ _link = 'http://www.freesbi.ch/docs/openfovea/welcome'
+ else:
+ _link = 'http://www.freesbi.ch/docs/openfovea/message'
+ _req = urllib2.Request(_link, headers=headers)
+ try:
+ _url = urllib2.urlopen(_req, timeout=0.1)
+ except urllib2.URLError:
+ _url = None
+ if _url is not None:
+ _message = _url.readlines()
+ if _message[0][:2] == 'id':
+ # we had a page, but not the one expected...
+ _new_msgid = _message[0].strip('\n').split(' = ')[1]
+ if _new_msgid == 'today':
+ _new_msgid = datetime.datetime.today()
+ else:
+ _new_msgid = datetime.datetime.strptime(_new_msgid,
+ '%Y, %m, %d')
+ if _new_msgid > curr_id:
+ message = _message[0:]
+ curr_id = _new_msgid
+ _url.close()
+ return message, curr_id
+
+def ismasked(value):
+ '''
+ Return TRUE if the value is masked, of FALSE if not.
+ Value has to be a scalar.
+
+ >>> import numpy
+ >>> ismasked(1)
+ False
+ >>> test = numpy.ma.array([1,2,3,4], mask=[0,0,1,1])
+ >>> ismasked(test[0])
+ False
+ >>> ismasked(test[3])
+ True
+
+ Only scalar object can be tested with this function :
+ >>> ismasked(test)
+ Traceback (most recent call last):
+ ...
+ TypeError: Only scalar object can be tested this way.
+
+ '''
+ good_type = True
+ try:
+ len(value)
+ good_type = False
+ except:
+ pass
+ if not good_type:
+ raise TypeError, "Only scalar object can be tested this way."
+ try:
+ result = (type(value) == numpy.ma.core.MaskedConstant)
+ except AttributeError:
+ result = (type(value) == numpy.ma.core.MaskedArray)
+ return result
+
+def export_list(filename, list_array, header, preambule=None, transpose=False):
+ """
+ Export a list of array in csv.
+
+ >>> list = [range(10), range(10), range(20), range(20)]
+ >>> header = ['1st', '2nd', '3rd', '4th']
+ >>> export_list('test.csv', list, header, transpose=True)
+ """
+
+ # test that the length of all elements are the same.
+
+ length = [len(item) for item in list_array]
+ curves = complete_list(list_array)
+ if transpose:
+ curves = curves.transpose(1,0)
+
+ ## generate the filename
+ split_filename = os.path.splitext(filename)
+ if split_filename[1] in ['.csv', '.txt']:
+ filename = split_filename[0]
+ else:
+ # It is probably not an extention, then restore it
+ filename = split_filename[0] + split_filename[1]
+
+ filename = filename + '.csv'
+ old_masked_print_option = numpy.ma.masked_print_option.display()
+ numpy.ma.masked_print_option.set_display('')
+
+ # Write the file.
+ fid = open(filename, 'wb')
+ fcsv = csv.writer(fid)
+ if preambule:
+ fcsv.writerow(preambule)
+ fcsv.writerow(header)
+ for item in curves:
+ fcsv.writerow(item)
+ numpy.ma.masked_print_option.set_display(old_masked_print_option)
+
+def complete_list(list_array):
+ """
+ Complete a list of array in order that all elements has the same
+ length.
+
+ >>> list = [range(10), range(20)]
+ >>> new_list = complete_list(list)
+ """
+
+ max_length = max([len(item) for item in list_array])
+ diff = [max_length - len(item) for item in list_array]
+
+ mask = numpy.zeros((len(list_array), max_length))
+ array = numpy.zeros((len(list_array), max_length))
+ for item, itarray, itmask in zip(list_array, array, mask):
+ itarray[:len(item)] = item
+ itmask[len(item):] = 1
+ new_array = numpy.ma.array(array, mask=mask)
+ return new_array
+if __name__ == "__main__":
+
+ import doctest
+ doctest.testmod()
diff --git a/openfovea/fovea_toolbox/opengl.py b/openfovea/fovea_toolbox/opengl.py
new file mode 100644
index 0000000..6b31730
--- /dev/null
+++ b/openfovea/fovea_toolbox/opengl.py
@@ -0,0 +1,689 @@
+#!/usr/bin/env python
+# -*- coding: UTF-8 -*-
+
+"""
+ This opengl code was higly inspired from the pyopengl documentation, and
+ particularly on the NeHe tutorials, thank's a lot to them !
+"""
+from copy import deepcopy
+
+import numpy
+from OpenGL.GL import *
+from OpenGL.GLUT import *
+from OpenGL.GLU import *
+from gtk.gtkgl.apputils import *
+
+from matplotlib.pylab import pcolor
+import time
+
+from misc import ismasked
+
+class Topography(GLScene,
+ GLSceneButton,
+ GLSceneButtonMotion):
+ """
+ Create a 3D view of an array, the color is based on the color array and
+ the topography on the array.
+
+ To create a view, follow the following simple steps :
+
+ >>> import numpy
+ >>> array = numpy.random.randn(32, 32)
+ >>> color_array = numpy.random.randn(32, 32)
+ >>> Topography(array, color_array)
+ """
+
+ def __init__(self, array, color_array=None, clut=None):
+ GLScene.__init__(self,
+ gtk.gdkgl.MODE_RGB |
+ gtk.gdkgl.MODE_DEPTH |
+ gtk.gdkgl.MODE_DOUBLE)
+ self.window = 0 # the glut window
+ self.rotation = 0.0 # the rotation angle for the object
+ self.array = array
+ self.rot = [1, 1, 1]
+ self.zoom = 20
+ self.translate = [0, 0, 0]
+ # This is the Color Look-Up Table (CLUT)
+ if color_array is not None:
+ if clut is None:
+ clut = (array.min(), array.max())
+ self.clut = pcolor(numpy.array([[clut[0], clut[1]] for i in range(2)]))
+ else:
+ self.clut = None # Have to define it later...
+
+ self.mouse_eventpos_begin = [0, 0] # To record the position
+ # of the mouse during an event
+ self.slice = {'begin' : [0, 0, 0], # This is the portion of the array
+ 'end' : [-1, -1, -1]} # to display
+ ### Initialize the Window
+ # pass argument to init
+ if array is not None:
+ self.coordinate = make_coordinate(array, self.clut)
+ self.all_coordinate = deepcopy(self.coordinate)
+ glHint(GL_CLIP_VOLUME_CLIPPING_HINT_EXT, GL_FASTEST)
+
+ glutInit()
+ #glutCreateWindow('OpenGL Tutorial 1')
+ def init(self):
+ glEnable(GL_LIGHTING)
+ glEnable(GL_COLOR_MATERIAL)
+
+ glEnable(GL_LIGHT0)
+ glLightfv(GL_LIGHT0, GL_AMBIENT, (0.5, 0.5, 0.8))
+ glLightfv(GL_LIGHT0, GL_POSITION, (0, 2, 2, 0))
+ glDepthFunc(GL_LESS)
+ glEnable(GL_DEPTH_TEST)
+ def keyboard_perss(self, event):
+ print event
+ def button_press(self, width, height, event):
+ if event.state & gtk.gdk.BUTTON4_MASK:
+ print "ZOOM"
+ if event.state & gtk.gdk.BUTTON5_MASK:
+ print "UNZOOM"
+ if event.state & gtk.gdk.SCROLL_MASK:
+ print "Scroll"
+ self.mouse_eventpos_begin = [event.x, event.y]
+
+ def button_release(self, width, height, event):
+ pass
+ def button_motion(self, width, height, event):
+ if event.state & gtk.gdk.BUTTON1_MASK:
+ deg_factor = numpy.pi/180
+ rot_x = ((event.x - self.mouse_eventpos_begin[0]) / width) * 360
+ rot_y = ((event.y - self.mouse_eventpos_begin[1]) / height) * 360
+ self.rot[0] = (self.rot[0] + rot_y) % 360
+ self.rot[1] = (self.rot[1] + numpy.cos(self.rot[0] * deg_factor) * rot_x) % 360
+ self.rot[2] = (self.rot[2] - numpy.sin(self.rot[0] * deg_factor) * rot_x) % 360
+ if event.state & gtk.gdk.BUTTON2_MASK:
+ self.translate[0] = self.translate[0] + (
+ event.x - self.mouse_eventpos_begin[0]) / width
+ self.translate[1] = self.translate[1] - (
+ event.y - self.mouse_eventpos_begin[1]) / height
+ self.mouse_eventpos_begin = [event.x, event.y]
+ self.invalidate()
+ def rotate(self):
+ """
+ The rotation of the array
+ """
+
+ self.rotx += 2
+ if self.rotx > 360:
+ self.rotx = self.rotx - 360
+ self.roty += 2
+ if self.roty > 360:
+ self.roty = self.roty - 360
+ glutPostRedisplay()
+
+ def reshape(self, width, height):
+ glViewport(0, 0, width, height)
+ glMatrixMode(GL_PROJECTION)
+ glLoadIdentity()
+ if width > height:
+ w = float(width) / float(height)
+ glFrustum(-w, w, -1.0, 1.0, 5.0, 60.0)
+ else:
+ h = float(height) / float(width)
+ glFrustum(-1.0, 1.0, -h, h, 5.0, 60.0)
+ #glMatrixMode(GL_MODELVIEW)
+ def display(self, width, height):
+ """
+ The main drawing funtion
+ """
+ # Clear The Screen And The Depth Buffer
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
+ glLoadIdentity() # Reset The View
+ glMatrixMode(GL_PROJECTION)
+ # The text
+ glColor3f(1.0, 1, 1)
+ glRasterPos2f(-0.9,0.9)
+ to_display = "x : [%i : %i], y : [%i : %i], z : [%i : %i]"%(
+ self.slice['begin'][0], self.slice['end'][0],
+ self.slice['begin'][1], self.slice['end'][1],
+ self.slice['begin'][2], self.slice['end'][2],
+ )
+ for string in to_display:
+ glutBitmapCharacter(GLUT_BITMAP_8_BY_13, ord(string))
+ # Modify the projection matrix
+ gluPerspective(self.zoom, 1., 1., 500.0)
+ glTranslatef(0.0, 0.0, -4)
+ glRotate(self.rot[0],1.0,0.0,0.0)
+ glRotate(self.rot[1], 0.0, 1.0, 0.0)
+ glRotate(self.rot[2], 0.0, 0.0, 1.0)
+ glTranslate(self.translate[0], self.translate[1], self.translate[2])
+ # Every thing after is modified by the projection matrix
+ self.generate_vertex(self.coordinate)
+
+ glFlush()
+
+ def generate_vertex(self, coordinate):
+ glBegin(GL_QUADS)
+ for item in coordinate:
+ glNormal3f(item['normal'][0], item['normal'][1], item['normal'][2])
+ # Bottom left
+ glColor3f(item['color'][0][0],
+ item['color'][0][1],
+ item['color'][0][2])
+ glVertex3f(item['vertex_coord'][0][0],
+ item['vertex_coord'][0][1],
+ item['vertex_coord'][0][2])
+ # upper left
+ glColor3f(item['color'][1][0],
+ item['color'][1][1],
+ item['color'][1][2])
+ glVertex3f(item['vertex_coord'][1][0],
+ item['vertex_coord'][1][1],
+ item['vertex_coord'][1][2])
+ # bottom right
+ glColor3f(item['color'][3][0],
+ item['color'][3][1],
+ item['color'][3][2])
+ glVertex3f(item['vertex_coord'][3][0],
+ item['vertex_coord'][3][1],
+ item['vertex_coord'][3][2])
+ # upper right
+ glColor3f(item['color'][2][0],
+ item['color'][2][1],
+ item['color'][2][2])
+ glVertex3f(item['vertex_coord'][2][0],
+ item['vertex_coord'][2][1],
+ item['vertex_coord'][2][2])
+ glEnd()
+ glLineWidth(1.5)
+ glBegin(GL_LINES)
+ for item in coordinate:
+ glColor3f(0., 0., 0.)
+ glVertex3f(item['vertex_coord'][0][0],
+ item['vertex_coord'][0][1],
+ item['vertex_coord'][0][2])
+ glVertex3f(item['vertex_coord'][1][0],
+ item['vertex_coord'][1][1],
+ item['vertex_coord'][1][2])
+ glColor3f(0., 0., 0.)
+ glVertex3f(item['vertex_coord'][1][0],
+ item['vertex_coord'][1][1],
+ item['vertex_coord'][1][2])
+ glVertex3f(item['vertex_coord'][3][0],
+ item['vertex_coord'][3][1],
+ item['vertex_coord'][3][2])
+ glColor3f(0., 0., 0.)
+ glVertex3f(item['vertex_coord'][2][0],
+ item['vertex_coord'][2][1],
+ item['vertex_coord'][2][2])
+ glVertex3f(item['vertex_coord'][0][0],
+ item['vertex_coord'][0][1],
+ item['vertex_coord'][0][2])
+ glColor3f(0., 0., 0.)
+ glVertex3f(item['vertex_coord'][3][0],
+ item['vertex_coord'][3][1],
+ item['vertex_coord'][3][2])
+ glVertex3f(item['vertex_coord'][2][0],
+ item['vertex_coord'][2][1],
+ item['vertex_coord'][2][2])
+ glEnd()
+class Tomography(Topography):
+ def __init__(self, array, clut):
+ Topography.__init__(self, None)
+ self.array = array
+ if clut is None:
+ clut = (array.min(), array.max())
+ clut = numpy.array([[clut[0], clut[1]] for i in range(2)])
+ self.clut = pcolor(clut)
+ self.all_coordinate = make_tomo_coordinate(array, self.clut)
+ self._slice_it()
+ def slice_it(self, dirct, qty):
+ """
+ Slice the array in a certain direction in a certain quantity.
+
+ parameters :
+ dirct : str
+ The direction to slice.
+ 'x' to slice in x from the front.
+ 'X' to slice in x from the back.
+ 'y' to slice in y from the left.
+ 'Y' to slice in y from the right.
+ 'z' to slice in z from the top.
+ 'Z' to slice in z from the bottom.
+ """
+ if dirct == 'x' and (0 <= self.slice['begin'][0] +
+ qty <= self.array.shape[0]):
+ self.slice['begin'][0] += qty
+ elif dirct == 'X' and (0 <= self.slice['end'][0] +
+ qty <= self.array.shape[0]):
+ self.slice['end'][0] += qty
+ elif dirct == 'y' and (0 <= self.slice['begin'][1] +
+ qty <= self.array.shape[1]):
+ self.slice['begin'][1] += qty
+ elif dirct == 'Y' and (0 <= self.slice['end'][1] +
+ qty <= self.array.shape[1]):
+ self.slice['end'][1] += qty
+ elif dirct == 'z' and (0 <= self.slice['begin'][2] +
+ qty <= self.array.shape[2]):
+ self.slice['begin'][2] += qty
+ elif dirct == 'Z' and (0 <= self.slice['end'][2] +
+ qty <= self.array.shape[2]):
+ self.slice['end'][2] += qty
+ self._slice_it()
+ def _slice_it(self):
+ """
+ Slice the array.
+
+ For example : self.slice_it([0, 0, 4], [-1, -1, -1])
+ will slice the array in z by removing the 4 (0 to 3) first elements.
+
+ Parameters :
+ begin : array_like
+ begin[0] is the first x
+ begin[1] the first y
+ begin[2] the first z
+ end : array_like
+ end[0] is the last x
+ end[1] is the last y
+ end[2] is the last z
+
+ Retruns :
+ nothing. Just the self.coordinate are modified according to it.
+ """
+
+ factor = float(max(self.array.shape))
+ _begin = [0, 0, 0]
+ _end = [-1, -1, -1]
+ for i in range(3):
+ if self.slice['end'][i] == -1:
+ self.slice['end'][i] = self.array.shape[i]
+ _begin[i] = ((float(self.slice['begin'][i]) / factor) -
+ self.array.shape[i]/(2*factor))
+ _end[i] = ((float(self.slice['end'][i]) / factor) -
+ self.array.shape[i]/(2*factor))
+ self.coordinate = list()
+ for item in self.all_coordinate:
+ # find items that are outside the slice.
+ eliminate = 0
+ for vertex in item['vertex_coord']:
+ if not eliminate and (
+ vertex[0] < _begin[0] or vertex[0] > _end[0] or
+ vertex[1] < _begin[1] or vertex[1] > _end[1] or
+ vertex[2] < _begin[2] or vertex[2] > _end[2]):
+ eliminate = 1
+ if not eliminate:
+ self.coordinate.append(item)
+ # Add the borders...
+ borders = generate_array_borders(self.array,
+ self.slice['begin'], self.slice['end'],
+ self.clut)
+ self.coordinate = self.coordinate + \
+ [i for i in borders if i is not None]
+def make_tomo_coordinate(array, color_map):
+ """
+ This function creates the coordinate of the vertex and the corresponding
+ color.
+ The array is a 3D array which represent the space with pixels coordinate
+ corresponding to the location in the array and the color value coded in
+ that location.
+
+ Parameters :
+ array : numpy.ma.array
+ This is an array with mask. Masked values are transparent
+ pixels, otherwise, the pixel values represent the color.
+
+ Returns :
+ coordinate : list
+ List of vertex to display.
+ """
+ #array = array/array.max() # to be between 0 and 1.
+ #plot_array = numpy.array([[array.min(), array.max()] for i in range(2)])
+ #plot_array = pcolor(plot_array) # to have a cmap
+ coordinate = list()
+ #non_zero_indice = array.mask.nonzero()
+ for px, y, z in zip(array.mask.nonzero()[0],
+ array.mask.nonzero()[1],
+ array.mask.nonzero()[2]):
+ item = generate_border_vertex(array, px, y, z, color_map)
+ coordinate += [i for i in item if i is not None]
+
+ # generage the top face and bottom face:
+ [x_list, y_list] = numpy.meshgrid(range(array.shape[0]),
+ range(array.shape[1]))
+ for px, y in zip(x_list.flat, y_list.flat):
+ item = [generate_vertex(array, px, y, array.shape[2]-1, color_map),
+ generate_vertex(array, px, y, 0, color_map)]
+ coordinate += [i for i in item if i is not None]
+ # generate the front and back faces:
+ coord_list = numpy.meshgrid(range(array.shape[0]), range(array.shape[2]))
+ for px, z in zip(coord_list[0].flat, coord_list[1].flat):
+ item = [generate_vertex(array, px, array.shape[1]-1, z,
+ color_map, face = 'front'),
+ generate_vertex(array, px, 0, z,
+ color_map, face = 'front')]
+ coordinate += [i for i in item if i is not None]
+ # generate the right and left faces:
+ coord_list = numpy.meshgrid(range(array.shape[1]), range(array.shape[2]))
+ for y, z in zip(coord_list[0].flat, coord_list[1].flat):
+ item = [generate_vertex(array, array.shape[0]-1, y, z,
+ color_map, face = 'right') ,
+ generate_vertex(array, 0, y, z,
+ color_map, face = 'right')]
+ coordinate += [i for i in item if i is not None]
+ return coordinate
+def generate_array_borders(array, begin, end, color_map):
+ """
+ Generate the
+ """
+ #array = array/array.max() # to be between 0 and 1.
+ #plot_array = numpy.array([[array.min(), array.max()] for i in range(2)])
+ #plot_array = pcolor(plot_array)
+ coordinate = list()
+ if begin[0] != 0:
+ # regenerate the front face
+ coord_list = numpy.meshgrid(xrange(begin[1], end[1]),
+ xrange(begin[2], end[2]))
+ coordinate += [generate_vertex(array, begin[0], y, z,
+ color_map, face = 'right')
+ for y, z in zip(coord_list[0].flat, coord_list[1].flat)]
+ if end[0] != array.shape[0]:
+ # regenerate the back face
+ coord_list = numpy.meshgrid(xrange(begin[1], end[1]),
+ xrange(begin[2], end[2]))
+ coordinate += [generate_vertex(array, end[0], y, z,
+ color_map, face = 'right')
+ for y, z in zip(coord_list[0].flat, coord_list[1].flat)]
+ if begin[1] != 0:
+ # regenerate the left face
+ coord_list = numpy.meshgrid(xrange(begin[0], end[0]),
+ xrange(begin[2], end[2]))
+ coordinate += [generate_vertex(array, px, begin[1], z,
+ color_map, face = 'front')
+ for px, z in zip(coord_list[0].flat, coord_list[1].flat)]
+ if end[1] != array.shape[1]:
+ # regenerate the left face
+ coord_list = numpy.meshgrid(xrange(begin[0], end[0]),
+ xrange(begin[2], end[2]))
+ coordinate += [generate_vertex(array, px, end[1], z,
+ color_map, face = 'front')
+ for px, z in zip(coord_list[0].flat, coord_list[1].flat)]
+ if begin[2] != 0:
+ # regenerate the left bottom
+ coord_list = numpy.meshgrid(xrange(begin[0], end[0]),
+ xrange(begin[1], end[1]))
+ coordinate += [generate_vertex(array, px, y, begin[2],
+ color_map, face = 'floor')
+ for px, y in zip(coord_list[0].flat, coord_list[1].flat)]
+ if end[2] != array.shape[2]:
+ # regenerate the left top
+ coord_list = numpy.meshgrid(xrange(begin[0], end[0]),
+ xrange(begin[1], end[1]))
+ coordinate += [generate_vertex(array, px, y, end[2],
+ color_map, face = 'floor')
+ for px, y in zip(coord_list[0].flat, coord_list[1].flat)]
+ return coordinate
+def generate_border_vertex(array, px, y, z, color_map):
+ """
+ Generates the vertex that touch this one.
+ """
+ # The vertex is a 3d vertex composed on 6 faces
+ #
+ # (x, y+1, z+1)_______
+ # /· /| (x+1, y+1, z+1)
+ # / · / |
+ # (x, y, z+1) +------+ |
+ # | ·······+ (x+1, y+1, z)
+ # (x, y+1, z) | · | /
+ # |· |/
+ # +------+ (x+1, y, z)
+ # (x, y, z)
+ block = {'right' : px + 1 == array.shape[0],
+ 'left' : px == 0,
+ 'front' : y + 1 == array.shape[1],
+ 'back' : y == 0,
+ 'top' : z + 1 == array.shape[2],
+ 'bottom' : z == 0}
+ pixels = list()
+ factor = float(max(array.shape))
+ pos = [((float(px) / factor) - array.shape[0] / (2 * factor)),
+ ((float(y) / factor) - array.shape[1] / (2 * factor)),
+ ((float(z) / factor) - array.shape[2] / (2 * factor))]
+ pos_u = [((float(px + 1) / factor) - array.shape[0] / (2 * factor)),
+ ((float(y+1) / factor) - array.shape[1] / (2 * factor)),
+ ((float(z+1) / factor) - array.shape[2] / (2 * factor))]
+ pos_d = [((float(px - 1) / factor) - array.shape[0] / (2 * factor)),
+ ((float(y-1) / factor) - array.shape[1] / (2 * factor)),
+ ((float(z-1) / factor) - array.shape[2] / (2 * factor))]
+ if not block['bottom'] and not array.mask[px, y, z-1]:
+ # Bottom pixel exist... create the bottom face.
+ color = color_map.to_rgba(array[px, y, z-1])
+ pixels.append({'vertex_coord' : [[pos[0], pos[1], pos[2]],
+ [pos[0], pos_u[1], pos[2]],
+ [pos_u[0], pos[1], pos[2]],
+ [pos_u[0], pos_u[1], pos[2]]],
+ 'color' : [color, color, color, color],
+ 'normal' : NORMAL['bottom']})
+
+ if not block['right'] and not array.mask[px + 1, y, z]:
+ # Right pixel exist... create the right face.
+ color = color_map.to_rgba(array[px + 1, y, z])
+ pixels.append({'vertex_coord' : [[pos_u[0], pos[1], pos[2]],
+ [pos_u[0], pos_u[1], pos[2]],
+ [pos_u[0], pos[1], pos_u[2]],
+ [pos_u[0], pos_u[1], pos_u[2]]],
+ 'color' : [color, color, color, color],
+ 'normal' : NORMAL['right']})
+ if not block['front'] and not array.mask[px, y+1, z]:
+ # Back pixel exist... create the back face.
+ color = color_map.to_rgba(array[px, y+1, z])
+ pixels.append({'vertex_coord' : [[pos[0], pos_u[1], pos[2]],
+ [pos_u[0], pos_u[1], pos[2]],
+ [pos[0], pos_u[1], pos_u[2]],
+ [pos_u[0], pos_u[1], pos_u[2]]],
+ 'color' : [color, color, color, color],
+ 'normal' : NORMAL['back']
+ })
+ if not block['left'] and not array.mask[px-1, y, z]:
+ # Left pixel exist... create the left face.
+ color = color_map.to_rgba(array[px - 1, y, z])
+ pixels.append({'vertex_coord' : [[pos[0], pos[1], pos[2]],
+ [pos[0], pos_u[1], pos[2]],
+ [pos[0], pos[1], pos_u[2]],
+ [pos[0], pos_u[1], pos_u[2]]],
+ 'color' : [color, color, color, color],
+ 'normal' : NORMAL['left']})
+ if not block['back'] and not array.mask[px, y-1, z]:
+ # Front pixel exist... create the fron face.
+ color = color_map.to_rgba(array[px, y-1, z])
+ pixels.append({'vertex_coord' : [[pos[0], pos[1], pos[2]],
+ [pos_u[0], pos[1], pos[2]],
+ [pos[0], pos[1], pos_u[2]],
+ [pos_u[0], pos[1], pos_u[2]]],
+ 'color' : [color, color, color, color],
+ 'normal' : NORMAL['front']
+ })
+ if not block['top'] and not array.mask[px, y, z+1]:
+ # Top pixel exist... create the top face.
+ color = color_map.to_rgba(array[px, y, z+1])
+ pixels.append({'vertex_coord' : [[pos[0], pos[1], pos_u[2]],
+ [pos[0], pos_u[1], pos_u[2]],
+ [pos_u[0], pos[1], pos_u[2]],
+ [pos_u[0], pos_u[1], pos_u[2]]],
+ 'color' : [color, color, color, color],
+ 'normal' : NORMAL['top']})
+ return pixels
+def generate_vertex(array, px, y, z, color_map, face = 'floor'):
+ """
+ Generate the vertex in the defined position.
+ """
+ voxel = None
+ if not ismasked(array[px, y, z]):
+# if type(array[px, y, z]) not in [numpy.ma.core.MaskedArray, # old numpy
+# numpy.ma.core.MaskedConstant]:
+ factor = float(max(array.shape))
+ pos = [((float(px) / factor) - array.shape[0] / (2 * factor)),
+ ((float(y) / factor) - array.shape[1] / (2 * factor)),
+ ((float(z) / factor) - array.shape[2] / (2 * factor))]
+ pos_u = [((float(px + 1) / factor) - array.shape[0] / (2 * factor)),
+ ((float(y+1) / factor) - array.shape[1] / (2 * factor)),
+ ((float(z+1) / factor) - array.shape[2] / (2 * factor))]
+
+ # The vertex is a 3d vertex composed on 6 faces
+ #
+ # (x, y+1, z+1)_______
+ # /· /| (x+1, y+1, z+1)
+ # / · / |
+ # (x, y, z+1) +------+ |
+ # | ·······+ (x+1, y+1, z)
+ # (x, y+1, z) | · | /
+ # |· |/
+ # +------+ (x+1, y, z)
+ # (x, y, z)
+ # By convention, the face of the bottom has the color form the pixel x,
+ # y, z and the face from the top has the color of x, y, z+1.
+ # Then, if pixel in z+1 is masked, we do not render it.
+ # In order to not make twice the same face, we only consiter faces at
+ # bottom : [(x, y, z), (x+1, y, z), (x+1, y+1, z), (x, y+1, z)]
+ # front : [(x, y, z), (x+1, y, z), (x, y, z+1), (x+1, y, z+1)]
+ # right : [(x, y, z), (x, y+1, z), (x, y, z+1), (x, y+1, z+1),]
+ color = color_map.to_rgba(array[px, y, z])
+ if face == 'floor' :
+ if z+1 == array.shape[2]:
+ pos[2] = pos_u[2]
+ voxel = {'vertex_coord' : [[pos[0], pos[1], pos[2]],
+ [pos[0], pos_u[1], pos[2]],
+ [pos_u[0], pos[1], pos[2]],
+ [pos_u[0], pos_u[1], pos[2]]],
+ 'color' : [color, color, color, color],
+ 'normal' : NORMAL['bottom'],
+ }
+ elif face == 'front':
+ if y+1 == array.shape[1]:
+ pos[1] = pos_u[1]
+ voxel = {'vertex_coord' : [[pos[0], pos[1], pos[2]],
+ [pos_u[0], pos[1], pos[2]],
+ [pos[0], pos[1], pos_u[2]],
+ [pos_u[0], pos[1], pos_u[2]]],
+ 'color' : [color, color, color, color],
+ 'normal' : NORMAL['front']
+ }
+ elif face == 'right':
+ if px + 1 == array.shape[0]:
+ pos[0] = pos_u[0]
+ voxel = {'vertex_coord' : [[pos[0], pos[1], pos[2]],
+ [pos[0], pos_u[1], pos[2]],
+ [pos[0], pos[1], pos_u[2]],
+ [pos[0], pos_u[1], pos_u[2]]],
+ 'color' : [color, color, color, color],
+ 'normal' : NORMAL['right']}
+ return voxel
+def make_coordinate(array, color_map):
+ """
+ This function creates the coordinate of the vertex and the corresponding
+ color.
+
+ It returns a list of dictionnary.
+ """
+ #array = array / array.max() * 4
+# color_array = color_array / color_array.max()
+# plot_array = pcolor(color_array)
+ coordinate = list()
+ factor = float(max(array.shape))
+ for x in range(array.shape[0]):
+ for y in range(array.shape[1]):
+ z = array[px, y] / 10.
+ if type(color_array[px, y]) == numpy.ma.core.MaskedArray:
+ color = [1,1,1]
+ else:
+ color = color_map.to_rgba(color_array[px,y])
+ pos_x = float(x) / array.shape[0]
+ pos_y = float(y) / array.shape[1]
+ pos_x_u = float(x + 1) / array.shape[0]
+ pos_y_u = float(y + 1) / array.shape[1]
+ this_pixel = {'tex_coord' : [[pos_x, pos_y],
+ [pos_x, pos_y_u],
+ [pos_x_u, pos_y],
+ [pos_x_u, pos_y_u]],
+ 'vertex_coord' : [[px / factor - 0.5,
+ y / factor - 0.5,
+ z], # bottom left
+ [px / factor - 0.5,
+ (y + 1)/factor - 0.5,
+ z], # upper left
+ [(px + 1)/factor - 0.5,
+ y/factor - 0.5,
+ z], # bottom right
+ [(px + 1)/factor - 0.5,
+ (y + 1)/factor - 0.5,
+ z]], # upper right
+ 'color' : [color, color, color, color]}
+ coordinate.append(this_pixel)
+ if x > 0:
+ z_prev = array[px,-1, y] / 10
+ # we make a step pixel in x.
+ # The x and y are like pos_x and pos_y, pos_y_u
+ # The pos_z are from this pixel and from the next.
+ if type(color_array[px,-1,y]) == numpy.ma.core.MaskedArray:
+ color_prev = [1,1,1]
+ else:
+ color_prev = color_map.to_rgba(color_array[px,-1,y])
+ this_step = {'tex_coord' : [[pos_x, pos_y],
+ [pos_x, pos_y_u],
+ [pos_x, pos_y],
+ [pos_x, pos_y_u]],
+ 'vertex_coord' : [[px / factor - 0.5,
+ y / factor - 0.5,
+ z], # bottom left
+ [px / factor - 0.5,
+ (y + 1) / factor - 0.5,
+ z], # upper left
+ [px / factor - 0.5,
+ y/factor - 0.5,
+ z_prev], # bottom right
+ [px / factor - 0.5,
+ (y + 1) / factor - 0.5,
+ z_prev]], # upper right
+ 'color' : [color, color, color_prev, color_prev]}
+ coordinate.append(this_step)
+ if y > 0:
+ z_prev = array[px, y-1] / 10
+ # we make a step pixel in x.
+ # The x and y are like pos_x and pos_y, pos_y_u
+ # The pos_z are from this pixel and from the next.
+ if type(color_array[px,y-1]) == numpy.ma.core.MaskedArray:
+ color_prev = [1,1,1]
+ else:
+ color_prev = color_map.to_rgba(color_array[px,y-1])
+ this_step = {'tex_coord' : [[pos_x, pos_y],
+ [pos_x, pos_y],
+ [pos_x_u, pos_y],
+ [pos_x_u, pos_y]],
+ 'vertex_coord' : [[px / factor - 0.5,
+ y / factor - 0.5,
+ z], # bottom left
+ [px / factor - 0.5,
+ y / factor - 0.5,
+ z_prev], # upper left
+ [(px + 1)/factor - 0.5,
+ y / factor - 0.5,
+ z], # bottom right
+ [(px + 1)/factor - 0.5,
+ y / factor - 0.5,
+ z_prev]], # upper right
+ 'color' : [color, color_prev, color, color_prev]}
+ coordinate.append(this_step)
+
+ return coordinate
+NORMAL = {
+ 'top' : (0, 0, -1),
+ 'bottom' : (0, 0, 1),
+ 'left' : (0, -1, 0),
+ 'right' : (0, 1, 0),
+ 'front' : (-1, 0, 0),
+ 'back' : (1, 0, 0)
+}
+# Some api in the chain is translating the keystrokes to this octal string
+# so instead of saying: ESCAPE = 27, we use the following.
+ESCAPE = '\033'
+
+if __name__ == '__main__':
+ import numpy
+ SIZE = 32
+ ARRAY = numpy.random.randn(SIZE, SIZE)
+ TMP_ARRAY = numpy.random.randn(SIZE, SIZE)
+ COLOR_ARRAY = numpy.ma.array(TMP_ARRAY, mask = TMP_ARRAY < 0)
+ TEST = Topography(array, COLOR_ARRAY)
diff --git a/openfovea/fovea_toolbox/opengl_gtk.py b/openfovea/fovea_toolbox/opengl_gtk.py
new file mode 100644
index 0000000..5dae885
--- /dev/null
+++ b/openfovea/fovea_toolbox/opengl_gtk.py
@@ -0,0 +1,115 @@
+#!/usr/bin/env python
+# -*- coding: UTF-8 -*-
+"""
+ This module is based on teapot2 example from the python-gtkglext examples.
+"""
+
+import pygtk
+pygtk.require('2.0')
+from gtk.gtkgl.apputils import *
+
+from opengl import Topography, Tomography
+
+class TopoWindow(gtk.Window):
+ """
+ Displays the 3D array in a gtk window.
+
+ Parameters :
+ array : 2D numpy.array
+ Represents the height
+ color_array : 2D numpy.array
+ Represents value for the colorscale to plot in the surface.
+ """
+ def __init__(self, array, color_array=None, clut=None):
+ self.key = None
+ gtk.Window.__init__(self)
+ # Set self attributes.
+ self.set_title('3D view')
+ if sys.platform != 'win32':
+ self.set_resize_mode(gtk.RESIZE_IMMEDIATE)
+ self.set_reallocate_redraws(True)
+ self.connect('destroy', lambda quit: gtk.main_quit())
+ # Catch keyboard events
+ self.connect("key-press-event",self.on_window_key_press_event)
+ self.connect("key-release-event",self.on_window_key_release_event)
+ # Create the table.
+ self.table = gtk.Table(3, 3)
+ self.table.set_border_width(5)
+ self.table.set_col_spacings(5)
+ self.table.set_row_spacings(5)
+ self.table.show()
+ self.add(self.table)
+ # The scene and the
+ # GLArea widget to
+ # display it.
+ if array.ndim == 2:
+ self.topo = Topography(array, color_array, clut)
+ elif array.ndim ==3:
+ self.topo = Tomography(array, clut)
+ self.glarea = GLArea(self.topo)
+ self.glarea.connect("scroll_event", self.button_scroll)
+ self.glarea.set_size_request(300,300)
+ self.glarea.show()
+ self.table.attach(self.glarea, 0, 1, 0, 1)
+
+ def on_window_key_press_event(self, window, event):
+ if self.key is None and (64 < event.keyval < 123):
+ self.key = gtk.gdk.keyval_name(event.keyval)
+ def on_window_key_release_event(self, window, event):
+ self.key = None
+ def button_scroll(self, window, event):
+ if event.direction == gtk.gdk.SCROLL_UP:
+ if self.key == None:
+ self.topo.zoom = self.topo.zoom + self.topo.zoom/10.
+ elif self.key in ['x', 'X', 'y', 'Y', 'z', 'Z']:
+ self.topo.slice_it(self.key, -1)
+ elif event.direction == gtk.gdk.SCROLL_DOWN:
+ if self.key == None:
+ self.topo.zoom = self.topo.zoom - self.topo.zoom/10.
+ elif self.key in ['x', 'X', 'y', 'Y', 'z', 'Z']:
+ self.topo.slice_it(self.key, +1)
+ self.glarea.window.invalidate_rect(self.glarea.allocation, False)
+ def vchanged(self, vadj):
+ #self.topo.rotx = vadj.value
+ #self.glarea.window.invalidate_rect(self.glarea.allocation, False)
+ self.glarea.window.clear()
+ self.topo.slice_it([vadj.value, 0, 0], [-1, -1, -1])
+ #help(self.glarea.window.invalidate_rect)
+ self.glarea.window.invalidate_rect(self.glarea.allocation, False)
+ def hchanged(self, hadj):
+ self.topo.roty = hadj.value
+ self.glarea.window.invalidate_rect(self.glarea.allocation, False)
+ def zoom_changed(self, adj):
+ self.topo.zoom = adj.value
+ self.glarea.window.invalidate_rect(self.glarea.allocation, False)
+
+ def run(self):
+ self.show()
+ gtk.main()
+def main():
+ import numpy
+
+ size = 32
+ # Just create a beatifull gride...
+ def func3(x,y):
+ return (1- x/2 + x**5 + y**3)*numpy.exp(-x**2-y**2)
+ # make these smaller to increase the resolution
+ dx, dy = 6./size, 6./size#0.05, 0.05
+
+ x = numpy.arange(-3.0, 3.0, dx)
+ y = numpy.arange(-3.0, 3.0, dy)
+ X,Y = numpy.meshgrid(x, y)
+
+ array = func3(X, Y)
+
+ #color_array = numpy.ma.array(array, mask = array<-0.8)
+ #app = TopoWindow(array, color_array)
+ #app.run()
+ array3 = [array*i for i in range(10)]
+ array3 = numpy.array(array3).transpose(2,1,0)
+ array3 = numpy.ma.array(array3, mask = array3<-0.5)
+ clut = (0, 1)
+ app = TopoWindow(array3, clut=clut)
+ app.run()
+if __name__ == '__main__':
+ main()
diff --git a/openfovea/fovea_toolbox/post_proc.py b/openfovea/fovea_toolbox/post_proc.py
new file mode 100644
index 0000000..f0a3faf
--- /dev/null
+++ b/openfovea/fovea_toolbox/post_proc.py
@@ -0,0 +1,585 @@
+#! /usr/bin/python
+# -*- coding: utf-8 -*-
+'''
+This module contains the second stage posprocessing tools.
+'''
+__author__ = "Charles Roduit <charles.roduit at gmail.com>"
+__date__ = "28.12.2008"
+__license__ = "GNU Public License (GPL) version 3"
+__version__ = "0.1"
+
+from warnings import warn
+
+import numpy
+from scipy import stats
+
+from misc import ismasked
+
+def get_array_distance(source_array, distance):
+ r'''
+ Create a 3D array that contains the array series of the pixels distances
+ from each marked pixels.
+
+ for a distance of 1, the source and result arrays look like this :
+
+ ::
+
+ source_matrix result[:,:,0] result[:, :, 1]
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 1 0 0 0 1 0 1 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 1 0 0 0 0 0 0 0 0 1 0 1 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ '''
+
+ [pos_x, pos_y] = source_array.nonzero()
+ all_drops = drop_seed(source_array, distance)
+ all_single_drops = numpy.empty((source_array.shape[0],
+ source_array.shape[1],
+ len(pos_x)))
+ all_single_seeds = numpy.empty_like(all_single_drops)
+
+ for item in range(len(pos_x)):
+ this_seed = numpy.zeros(source_array.shape)
+ this_seed[pos_x[item], pos_y[item]] = 1
+ this_drop = drop_seed(this_seed, distance)
+ all_single_drops[:, :, item] = this_drop * all_drops
+ all_single_seeds[:, :, item] = this_seed
+ return [all_single_seeds, all_single_drops]
+
+def drop_seed(seed_matrix, size):
+ '''
+ This function returns a similar matrix as dist_seed, except that it takes
+ care of the overlapping
+ '''
+
+ drop_matrix = numpy.zeros_like(seed_matrix)
+ for dist in range(1, size):
+ drop_matrix = drop_matrix + dist_seed(seed_matrix, dist, where = 'xy')
+
+ negative_matrix = numpy.logical_not(drop_matrix + seed_matrix)
+ drop_matrix = drop_matrix + dist_seed(seed_matrix, size)
+ border_matrix = drop_matrix * negative_matrix
+
+ return border_matrix
+
+def dist_seed(seed_matrix, distance, where = 'x'):
+ '''
+ This function returns a daughter matrix that contains the position of pixels
+ at the given distance from the non-zero pixel of the source matrix.
+
+ ::
+
+ Source Result
+ 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0
+ 0 0 1 0 0 ==> 0 1 0 1 0
+ 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0
+
+ '''
+
+ [pos_x, pos_y] = seed_matrix.nonzero()
+ dist_matrix = numpy.zeros_like(seed_matrix)
+ if where == 'x':
+ to_left = pos_x - distance
+ to_right = pos_x + distance
+ # test if the pixels are in or outside the matrix
+ limit_left = ( to_left >= 0 ) * ( to_left < len(seed_matrix) )
+ limit_right = ( to_right >= 0 ) * ( to_right < len(seed_matrix) )
+ # Take the pixels that are in the matrix
+ pos_x_left = to_left[limit_left]
+ pos_x_right = to_right[limit_right]
+ pos_y_left = pos_y[limit_left]
+ pos_y_right = pos_y[limit_right]
+
+ # Generate the matrix
+ dist_matrix[pos_x_left, pos_y_left] = 1
+ dist_matrix[pos_x_right, pos_y_right] = 1
+ elif where == 'xy':
+ for elm_nbr in range(len(pos_x)):
+ this_x = pos_x[elm_nbr] + distance
+ this_y = pos_y[elm_nbr]
+ if this_x < dist_matrix.shape[0] and this_y < dist_matrix.shape[1]:
+ dist_matrix[this_x, this_y] = 1
+ # from 0 to pi/2
+ while this_y < pos_y[elm_nbr] + distance:
+ if (this_x < dist_matrix.shape[0] and this_x >= 0 and
+ this_y < dist_matrix.shape[1] and this_y >= 0):
+ dist_matrix[this_x, this_y] = 1
+ this_y = this_y + 1
+ if numpy.sqrt((this_x - pos_x[elm_nbr]) ** 2 +
+ (this_y - pos_y[elm_nbr]) ** 2) > distance :
+ this_x = this_x - 1
+ if numpy.sqrt((this_x - pos_x[elm_nbr]) ** 2 +
+ (this_y - pos_y[elm_nbr]) ** 2) > distance :
+ this_y = this_y - 1
+ # from pi/2 to pi
+ while this_x > pos_x[elm_nbr] - distance:
+ if (this_x < dist_matrix.shape[0] and this_x >= 0 and
+ this_y < dist_matrix.shape[1] and this_y >= 0):
+ dist_matrix[this_x, this_y] = 1
+ this_x = this_x - 1
+ if numpy.sqrt((this_x - pos_x[elm_nbr]) ** 2 +
+ (this_y - pos_y[elm_nbr]) ** 2) > distance :
+ this_y = this_y - 1
+ if numpy.sqrt((this_x - pos_x[elm_nbr]) ** 2 +
+ (this_y - pos_y[elm_nbr]) ** 2) > distance :
+ this_x = this_x + 1
+ # from pi to 3pi/2
+ while this_y > pos_y[elm_nbr] - distance:
+ if (this_x < dist_matrix.shape[0] and this_x >= 0 and
+ this_y < dist_matrix.shape[1] and this_y >= 0):
+ dist_matrix[this_x, this_y] = 1
+ this_y = this_y - 1
+ if numpy.sqrt((this_x - pos_x[elm_nbr]) ** 2 +
+ (this_y - pos_y[elm_nbr]) ** 2) > distance :
+ this_x = this_x + 1
+ if numpy.sqrt((this_x - pos_x[elm_nbr]) ** 2 +
+ (this_y - pos_y[elm_nbr]) ** 2) > distance :
+ this_y = this_y + 1
+ # from 3pi/2 to 2pi
+ while this_x < pos_x[elm_nbr] + distance:
+ if (this_x < dist_matrix.shape[0] and this_x >= 0 and
+ this_y < dist_matrix.shape[1] and this_y >= 0):
+ dist_matrix[this_x, this_y] = 1
+ this_x = this_x + 1
+ if numpy.sqrt((this_x - pos_x[elm_nbr]) ** 2 +
+ (this_y - pos_y[elm_nbr]) ** 2) > distance :
+ this_y = this_y + 1
+ if numpy.sqrt((this_x - pos_x[elm_nbr]) ** 2 +
+ (this_y - pos_y[elm_nbr]) ** 2) > distance :
+ this_x = this_x - 1
+
+ #while this_y
+ pass
+ return dist_matrix
+
+def rel_values(value_array, dot_array, max_dist = 3, coord=False):
+ '''
+ This function computes the relative values at some points defined by the
+ dot_array.
+
+ dot_array = n * m array that contains the position where to compute relative
+ values.
+
+ value_array = n * m array that contains the values.
+
+ max_dist = integer, to which distance to compute (= 3 by default)
+
+ Output :
+
+ result[dot_nb, rel_val] = result[0, 1] is the absolute value of the first
+ dot
+
+ result[1:max_dist, 1] is the relative values of
+ the first dot
+ '''
+ if type(value_array) == numpy.ma.core.MaskedArray:
+ #print type(dot_array)
+ seed_array = dot_array * (1 - value_array.mask)
+ else:
+ seed_array = dot_array
+ if coord:
+ # we record the coordinate of the events.
+ coord_val = []
+ # we compute the distance matrices...
+ # First val is the absolute value.
+ result = numpy.empty((max_dist + 1, seed_array.astype(bool).sum()))
+ ret_array = []
+ for distance in range(max_dist + 1):
+ [single_dot, rel_array] = get_array_distance(seed_array, distance)
+ for dot_nb in range(single_dot.shape[2]):
+ # we compute the value at the dot position...
+ mask_dot = single_dot[:, :, dot_nb].astype(bool)
+ dot_val = value_array[mask_dot].sum()
+ #print type(dot_val)
+ #print "distance %i, dot number %i, dot val %f"%(distance, dot_nb, dot_val)
+ if not distance:
+ # we want the first value to be the value at the position
+ result[0, dot_nb] = dot_val
+ ret_array.append(single_dot)
+ if coord:
+ coord_val.append([mask_dot.nonzero()[0][0],
+ mask_dot.nonzero()[1][0]])
+ else:
+ # ... and at the distant positions ...
+ mask_rel = rel_array[:, :, dot_nb].astype(bool)
+ dist_val = value_array[mask_rel].sum()
+ # ... with the number of distant positions ...
+ try:
+ count_dist = len(value_array[mask_rel])
+ # ... and finally the relative value :
+ # First exclude the masked and negative datas
+ if ismasked(dist_val) and dist_val.mask:
+ rel_value = numpy.nan
+ elif dot_val <= 0 or dist_val <= 0:
+ rel_value = numpy.nan
+ else:
+ # to finally compute the relative values.
+ rel_value = numpy.log(dot_val / dist_val * count_dist)
+ except ZeroDivisionError:
+ rel_value = numpy.inf
+ if rel_value == numpy.inf:
+ result[distance, dot_nb] = numpy.nan
+ else:
+ result[distance, dot_nb] = rel_value
+ ret_array.append(rel_array)
+ # we mask the nan
+ result = numpy.ma.masked_array(result, mask = numpy.isnan(result))
+ if coord:
+ return result, coord_val
+ else:
+ return result#, result_summarize
+def add_stiffness_to_event(event_list, rel_stiff, coordinate):
+ for event in event_list:
+ try:
+ __index = coordinate.index([event['pos_x'], event['pos_y']])
+ except ValueError:
+ event['Stiffness'] = numpy.ma.array([], mask=[])
+ else:
+ event['Stiffness'] = rel_stiff[:, __index]
+ return event_list
+def rand_dot(dot_array, nb_dot = None):
+ '''
+ Creates an array with dots. The dots are placed randomly, with the only
+ constrain to place in a free position, given by the input dot array.
+ '''
+ nb_init_dot = len(dot_array.nonzero()[0])
+
+ if nb_dot == None:
+ nb_dot = nb_init_dot
+ if nb_dot > dot_array.size - nb_init_dot:
+ warn('Not enough place to generate random events.', RuntimeWarning)
+ # have to reach an error...
+ return numpy.zeros_like(dot_array)
+ result_array = numpy.zeros(dot_array.shape)
+ while result_array.sum() < nb_dot:
+ pos_x = numpy.floor(dot_array.shape[0] * numpy.random.rand())
+ pos_y = numpy.floor(dot_array.shape[1] * numpy.random.rand())
+ if not(dot_array[pos_x, pos_y] or result_array[pos_x, pos_y]):
+ result_array[pos_x, pos_y] = 1
+ return result_array
+
+def resize_array(old_array, new_shape):
+ '''
+ Create a new array with the given size and with the data in old array.
+ '''
+ # The new arrays are made of NaN to differentiate data from empty blocks
+ new_array = numpy.zeros(new_shape, numpy.float) * numpy.nan
+ # We reposition the points in the new array.
+ for pos_y in range(old_array.shape[1]):
+ for pos_x in range(old_array.shape[0]):
+ curve_nb = pos_x + pos_y * old_array.shape[0]
+ n_pos_x = (curve_nb) % new_shape[0]
+ n_pos_y = (curve_nb) / new_shape[0]
+ new_array[n_pos_x, n_pos_y] = old_array[pos_x, pos_y]
+ return new_array
+
+def kde_fit(data, nbr_div = 128):
+ '''
+ Automatically detects best gaussian fits for the data.
+
+ For example, let's create a data set of to normal random values :
+
+ >>> import pylab
+
+ >>> data1 = numpy.random.randn(100)
+ >>> data = numpy.zeros(200)
+ >>> data[:100] = data1+1
+ >>> data[100:] = data1+5
+
+ Now, let's compute the kde fit :
+
+ >>> [fit, maxima, minima] = kde_fit(data)
+
+ And we can plot the result :
+
+ >>> [x, y, patch] = pylab.hist(data)
+ >>> conversion_factor = max(x)/max(fit[1])
+ >>> figure = pylab.figure()
+ >>> axis = figure.add_subplot(111)
+ >>> pl = pylab.plot(fit[0], fit[1] * conversion_factor)
+ >>> axis.hold(True)
+ >>> pl = pylab.plot(maxima[0], maxima[1] * conversion_factor, 'go')
+ >>> pl = pylab.plot(minima[0], minima[1] * conversion_factor, 'ro')
+ >>> figure.savefig('figure/kde_fit.png')
+
+ The resulting graph will be an hisogram of the data set with the kde fit
+ plotted in foreground. Two gree dots represents the maximas and the red dot
+ represent the minima.
+ '''
+ kde = stats.kde.gaussian_kde(data)
+ min_data = min(data)
+ max_data = max(data)
+ nbr_step = (max_data - min_data) / nbr_div
+ coord = numpy.arange(min_data, max_data, nbr_step)
+ gaussian_fit = kde(coord)
+ # After the gaussian fit, we want to finds the maximums
+ deriv = numpy.diff(gaussian_fit)
+ #print deriv
+ extrema = (abs(deriv) == deriv) * 1
+ extrema = numpy.diff(extrema)
+ #print extremums
+ maxima = numpy.nonzero(extrema == -1)[0] + 1 # add one to correspond to
+ minima = numpy.nonzero(extrema == 1)[0] + 1 # the maximum
+ return [[coord, gaussian_fit],
+ [coord[maxima], numpy.ones(len(maxima)) * gaussian_fit[maxima]],
+ [coord[minima], numpy.ones(len(minima)) * gaussian_fit[minima]]]
+def compute_gauss(data, minima, maxima):
+ '''
+ compute the characteristic of the distribution : mean, std, and number of
+ elements.
+ '''
+ mean, std, count = [], [], []
+ position = []
+ if not len(minima) or minima[0] > maxima[0]:
+ position.append(min(data))
+ for minimum in minima:
+ position.append(minimum)
+ if not len(minima) or minima[-1] < maxima[-1]:
+ position.append(max(data))
+ for begin, end in zip(position[:-1], position[1:]):
+ index = (data >= begin) * (data < end)
+ if len(data[index]):
+ try:
+ mean.append(numpy.mean(data[index]))
+ std.append(numpy.std(data[index]))
+ count.append(len(data[index]))
+ except TypeError:
+ # In windows, numpy.nanmax seems to not work in this case...
+ index = index * numpy.array((numpy.isnan(data)-1), dtype=bool)
+ mean.append(numpy.mean(data[index]))
+ std.append(numpy.std(data[index]))
+ count.append(len(data[index]))
+ else:
+ # Array is empty...
+ mean.append(numpy.nan)
+ std.append(numpy.nan)
+ count.append(len(data[index]))
+ return [mean, std, count]
+def find_gauss_fit(data, nbr_div = 128):
+ '''
+ Automatically detects best gaussian fits for the data, and retun the
+ gaussian properties of each detected peak.
+
+ For example, let's create a data set of to normal random values :
+
+ >>> import pylab
+
+ >>> data1 = numpy.random.randn(100)
+ >>> data = numpy.zeros(200)
+ >>> data[:100] = data1+1
+ >>> data[100:] = data1+5
+
+ Now, we can compute the gauss fit of these data :
+
+ >>> fit = find_gauss_fit(data)
+
+ And plot the result :
+
+ >>> [x, y, patch] = pylab.hist(data)
+ >>> conversion_factor = max(x)/max(fit['kde'][1])
+ >>> figure = pylab.figure()
+ >>> axis = figure.add_subplot(111)
+ >>> pl = axis.hist(data)
+ >>> axis.hold(True)
+ >>> pl = axis.plot(fit['kde'][0], fit['kde'][1] * conversion_factor)
+ >>> pl = axis.plot(fit['maxima'][0], fit['maxima'][1] * conversion_factor, 'go')
+ >>> pl = axis.plot(fit['mean'], fit['maxima'][1] * conversion_factor, 'ro')
+ >>> pl = axis.errorbar(fit['mean'], fit['maxima'][1] * conversion_factor, xerr=fit['std'], fmt=None, ecolor='red')
+ >>> figure.savefig('figure/gauss_fit.png')
+
+ The resulting plot shows the data as an histogram with the kde fit in the
+ foreground.
+ The detected maxima are shown as two green dots and the mean as red dot with
+ the horizontal error bar describing the std deviation.
+ '''
+ data = data[numpy.isfinite(data)]
+ if len(data):
+ [fit, maxima, minima] = kde_fit(data, nbr_div)
+ [mean_lst, std_lst, count_lst] = compute_gauss(data, minima[0], maxima[0])
+ else:
+ [fit, maxima, minima, mean_lst, std_lst, count_lst] = [[] for i in range(6)]
+
+ return {'kde' : fit, 'mean' : mean_lst, 'std' : std_lst, 'count' :
+ count_lst, 'maxima' : maxima}
+
+def tomography_array(stiffness, floor=None):
+ """
+ Generate the stiffness tomography array.
+
+ >>> raw_array = numpy.mgrid[0:5, 0:5, 0:5][2]
+ >>> floor = numpy.mgrid[0:5, 0:5, 0:5][1][0]
+ >>> tomo = tomography_array(raw_array, floor)
+ >>> print tomo
+ [[[4.0 3.0 2.0 1.0 0.0 -- -- -- --]
+ [4.0 3.0 2.0 1.0 0.0 -- -- -- --]
+ [4.0 3.0 2.0 1.0 0.0 -- -- -- --]
+ [4.0 3.0 2.0 1.0 0.0 -- -- -- --]
+ [4.0 3.0 2.0 1.0 0.0 -- -- -- --]]
+ <BLANKLINE>
+ [[-- 4.0 3.0 2.0 1.0 0.0 -- -- --]
+ [-- 4.0 3.0 2.0 1.0 0.0 -- -- --]
+ [-- 4.0 3.0 2.0 1.0 0.0 -- -- --]
+ [-- 4.0 3.0 2.0 1.0 0.0 -- -- --]
+ [-- 4.0 3.0 2.0 1.0 0.0 -- -- --]]
+ <BLANKLINE>
+ [[-- -- 4.0 3.0 2.0 1.0 0.0 -- --]
+ [-- -- 4.0 3.0 2.0 1.0 0.0 -- --]
+ [-- -- 4.0 3.0 2.0 1.0 0.0 -- --]
+ [-- -- 4.0 3.0 2.0 1.0 0.0 -- --]
+ [-- -- 4.0 3.0 2.0 1.0 0.0 -- --]]
+ <BLANKLINE>
+ [[-- -- -- 4.0 3.0 2.0 1.0 0.0 --]
+ [-- -- -- 4.0 3.0 2.0 1.0 0.0 --]
+ [-- -- -- 4.0 3.0 2.0 1.0 0.0 --]
+ [-- -- -- 4.0 3.0 2.0 1.0 0.0 --]
+ [-- -- -- 4.0 3.0 2.0 1.0 0.0 --]]
+ <BLANKLINE>
+ [[-- -- -- -- 4.0 3.0 2.0 1.0 0.0]
+ [-- -- -- -- 4.0 3.0 2.0 1.0 0.0]
+ [-- -- -- -- 4.0 3.0 2.0 1.0 0.0]
+ [-- -- -- -- 4.0 3.0 2.0 1.0 0.0]
+ [-- -- -- -- 4.0 3.0 2.0 1.0 0.0]]]
+ """
+ # The piezo array contain the height of the tip at the end of the
+ # indentation. It correspond then to the last slice of the stiffness
+ # array.
+ if floor is None:
+ floor = numpy.zeros(stiffness.shape[0:2])
+ max_floor = floor.max()
+
+ new_array = numpy.zeros((stiffness.shape[0], stiffness.shape[1],
+ max_floor + stiffness.shape[2])) * numpy.nan
+ new_mask = numpy.ones((stiffness.shape[0], stiffness.shape[1],
+ max_floor + stiffness.shape[2]))
+ if type(stiffness) == numpy.ma.core.MaskedArray:
+ data = stiffness.data
+ mask = stiffness.mask
+ else:
+ data = stiffness
+ mask = numpy.zeros_like(stiffness)
+
+ max_depth = stiffness.shape[2]
+ for x in range(stiffness.shape[0]):
+ for y in range(stiffness.shape[1]):
+ try:
+ last_data = numpy.nonzero(mask[x, y])[0][0]
+ except IndexError:
+ last_data = len(mask[x, y]) - 1
+ begin_pos = floor[x,y]
+ end_pos = last_data + begin_pos + 1
+ try:
+ new_array[x, y, begin_pos:end_pos] = data[x, y][last_data::-1]
+ except ValueError as error:
+ print "Error in post_proc line 437:"
+ print x, y
+ print begin_pos, end_pos
+ print new_array[x, y, begin_pos:end_pos].shape
+ print data[x, y][last_data::-1].shape
+ print error
+ new_mask[x, y, begin_pos:end_pos] = mask[x, y][last_data::-1]
+
+ # Keep only the usefull informations. Array was build too large, but we want
+ # to return a well sized array
+ first_empty = 0
+ nbr_empty = 1
+ for depth in range(new_mask.shape[2]):
+ if new_mask[:, :, depth].all():
+ if depth == first_empty + nbr_empty:
+ nbr_empty += 1
+ else:
+ first_empty = depth
+ nbr_empty = 1
+ if first_empty == 0 and nbr_empty == 1:
+ # This mean there is no empty slice...
+ first_empty = new_array.shape[2]
+ ##
+ ## Stores the result in a matrix that supports the mask, to get rid
+ ## of the nan values
+ ##
+ return numpy.ma.array(new_array[:, :, :first_empty],
+ mask=new_mask[:, :, :first_empty])
+
+def generate_path(pos_a, pos_b):
+ """
+ Generate a linear path between the point a and the point b.
+
+ >>> generate_path([0, 0], [1, 1])
+ array([[ 0., 1.],
+ [ 0., 1.]])
+
+ >>> generate_path([0, 0], [1, 5])
+ array([[ 0., 0., 0., 1., 1., 1.],
+ [ 0., 1., 2., 3., 4., 5.]])
+
+ >>> generate_path([0, 0], [0, 5])
+ array([[ 0., 0., 0., 0., 0., 0.],
+ [ 0., 1., 2., 3., 4., 5.]])
+
+ >>> generate_path([0, 0], [5, 0])
+ array([[ 0., 1., 2., 3., 4., 5.],
+ [ 0., 0., 0., 0., 0., 0.]])
+
+ >>> generate_path([2, 2], [8, 5])
+ array([[ 2., 3., 4., 5., 6., 7., 8.],
+ [ 2., 3., 3., 4., 4., 5., 5.]])
+ """
+ pos_a[0], pos_a[1], pos_b[0], pos_b[1] = \
+ int(pos_a[0]), int(pos_a[1]), int(pos_b[0]), int(pos_b[1])
+ if pos_b[0] == pos_a[0]:
+ if pos_a[1] > pos_b[1]:
+ pos_a, pos_b = pos_b, pos_a
+ _length = pos_b[1] - pos_a[1] + 1
+ _y = range(pos_a[1], pos_b[1] + 1)
+ _x = [pos_b[0]] * _length
+ elif pos_b[1] == pos_a[1]:
+ if pos_a[0] > pos_b[0]:
+ pos_a, pos_b = pos_b, pos_a
+ _length = pos_b[0] - pos_a[0] + 1
+ _x = range(pos_a[0], pos_b[0] + 1)
+ _y = [pos_b[1]] * _length
+ else:
+ _a = float(pos_b[1] - pos_a[1]) / (pos_b[0] - pos_a[0])
+ _b = pos_a[1] - _a * pos_a[0]
+
+ if abs(pos_b[0] - pos_a[0]) > abs(pos_b[1] - pos_a[1]):
+ if pos_a[0] > pos_b[0]:
+ pos_a, pos_b = pos_b, pos_a
+ # x0 -> x1 is bigger to y0 -> y1
+ _x = range(pos_a[0], pos_b[0] + 1)
+ _y = [round(_a * item + _b) for item in _x]
+ else:
+ if pos_a[1] > pos_b[1]:
+ pos_a, pos_b = pos_b, pos_a
+ # y0 -> y1 is bigger than x0 -> x1
+ _y = range(pos_a[1], pos_b[1] + 1)
+ _x = [round((item - _b) / _a) for item in _y]
+ return numpy.asarray([_x, _y], dtype=float)
+
+def generate_slice(path, array):
+ """
+ Generate the slice of the array along the path.
+
+ >>> array = [range(10)]*10
+ >>> path = generate_path([0, 0], [0, 4])
+ >>> generate_slice(path, array)
+ [0, 1, 2, 3, 4]
+
+ >>> path = generate_path([0, 1], [4, 1])
+ >>> generate_slice(path, array)
+ [1, 1, 1, 1, 1]
+ """
+ return [array[int(_x)][int(_y)] for _x, _y in zip(path[0], path[1])]
+
+if __name__ == "__main__":
+
+ try:
+ matplotlib.__version__
+ except NameError:
+ import matplotlib
+ matplotlib.use('Agg')
+ from matplotlib import pylab
+ import doctest
+ doctest.testmod()
diff --git a/openfovea/glade/dialog_experiment_properties.glade b/openfovea/glade/dialog_experiment_properties.glade
new file mode 100644
index 0000000..2671aca
--- /dev/null
+++ b/openfovea/glade/dialog_experiment_properties.glade
@@ -0,0 +1,771 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk+" version="2.16"/>
+ <!-- interface-naming-policy project-wide -->
+ <object class="GtkWindow" id="ExpProp">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="title" translatable="yes">Properties</property>
+ <property name="default_width">300</property>
+ <property name="default_height">400</property>
+ <property name="icon_name">preferences-desktop</property>
+ <signal name="destroy" handler="destroy" swapped="no"/>
+ <child>
+ <object class="GtkVBox" id="vbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkNotebook" id="notebook1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child>
+ <object class="GtkVBox" id="vbox2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkHBox" id="hbox2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Author :</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="text_author">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="primary_icon_sensitive">True</property>
+ <property name="secondary_icon_sensitive">True</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame_description">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">automatic</property>
+ <property name="vscrollbar_policy">automatic</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkTextView" id="text_description">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes"><b>Description</b></property>
+ <property name="use_markup">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="General">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">General</property>
+ </object>
+ <packing>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkFrame" id="frame_time_lapse">
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkAlignment" id="alignment3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkVBox" id="vbox5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkHBox" id="hbox4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label_inject">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Injection time</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="entry_injection_index">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="primary_icon_sensitive">True</property>
+ <property name="secondary_icon_sensitive">True</property>
+ <property name="adjustment">adjust_injection_index</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes"><b>Time Lapse</b></property>
+ <property name="use_markup">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame_system">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkAlignment" id="alignment2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkVBox" id="vbox4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkHBox" id="hbox3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label_spring">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Spring constant</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="entry_spring_constant">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="primary_icon_sensitive">True</property>
+ <property name="secondary_icon_sensitive">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Glass stiffness</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="spin_stiff_glass">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="primary_icon_sensitive">True</property>
+ <property name="secondary_icon_sensitive">True</property>
+ <property name="adjustment">adjust_deflection_sensitivity</property>
+ <property name="digits">3</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox6">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkHBox" id="plot_area">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="box_nav_tool">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes"><b>System</b></property>
+ <property name="use_markup">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="Config">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Configuration</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="tab_plot">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkFrame" id="frame1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkAlignment" id="alignment4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkTable" id="table1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="n_rows">2</property>
+ <property name="n_columns">2</property>
+ <child>
+ <object class="GtkSpinButton" id="spin_save_plot_width">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="primary_icon_sensitive">True</property>
+ <property name="secondary_icon_sensitive">True</property>
+ <signal name="value-changed" handler="on_spin_save_plot_width_value_changed" swapped="no"/>
+ </object>
+ <packing>
+ <property name="x_options"></property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="spin_save_plot_height">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="primary_icon_sensitive">True</property>
+ <property name="secondary_icon_sensitive">True</property>
+ <signal name="value-changed" handler="on_spin_save_plot_height_value_changed" swapped="no"/>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options"></property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label8">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Width</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="x_options"></property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label9">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Height</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options"></property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label7">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Saved size</property>
+ <property name="use_markup">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="Linewidth">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkAlignment" id="alignment5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkVBox" id="vbox7">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkHBox" id="hbox6">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkSpinButton" id="spin_linewidth">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="primary_icon_sensitive">True</property>
+ <property name="secondary_icon_sensitive">True</property>
+ <property name="digits">1</property>
+ <property name="numeric">True</property>
+ <signal name="value-changed" handler="on_spin_linewidth_value_changed" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label12">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Linewidth</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="check_bw_plot">
+ <property name="label" translatable="yes">Gray scale plot.</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_action_appearance">False</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label11">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Plot Options</property>
+ <property name="use_markup">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="Linewidth1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkAlignment" id="alignment6">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkVBox" id="vbox9">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkCheckButton" id="check_plot_hist_gauss">
+ <property name="label" translatable="yes">Display fit on histograms</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_action_appearance">False</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="histo_y_rel">
+ <property name="label" translatable="yes">Relative y axis</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_action_appearance">False</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label14">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Histogram Options</property>
+ <property name="use_markup">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkAlignment" id="alignment_group">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkTable" id="table_group">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="n_rows">2</property>
+ <property name="n_columns">3</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label10">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Group</property>
+ <property name="use_markup">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label6">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Graph</property>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkButton" id="button_validate">
+ <property name="label">gtk-ok</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_action_appearance">False</property>
+ <property name="use_stock">True</property>
+ <signal name="clicked" handler="on_button_validate_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_cancel">
+ <property name="label">gtk-cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_action_appearance">False</property>
+ <property name="use_stock">True</property>
+ <signal name="clicked" handler="on_button_cancel_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <object class="GtkAdjustment" id="adjust_deflection_sensitivity">
+ <property name="upper">100</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="adjust_injection_index">
+ <property name="upper">100</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+</interface>
diff --git a/openfovea/glade/dialog_flatten.glade b/openfovea/glade/dialog_flatten.glade
new file mode 100644
index 0000000..90addf0
--- /dev/null
+++ b/openfovea/glade/dialog_flatten.glade
@@ -0,0 +1,142 @@
+<?xml version="1.0"?>
+<interface>
+ <requires lib="gtk+" version="2.16"/>
+ <!-- interface-naming-policy project-wide -->
+ <object class="GtkDialog" id="FlattenDialog">
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes">Flatten Image</property>
+ <property name="default_width">400</property>
+ <property name="default_height">300</property>
+ <property name="icon">../Icon/openfovea.png</property>
+ <property name="type_hint">normal</property>
+ <property name="has_separator">False</property>
+ <child internal-child="vbox">
+ <object class="GtkVBox" id="dialog-vbox4">
+ <property name="visible">True</property>
+ <property name="spacing">2</property>
+ <child>
+ <object class="GtkTable" id="table3">
+ <property name="visible">True</property>
+ <property name="n_rows">2</property>
+ <property name="n_columns">2</property>
+ <child>
+ <object class="GtkLabel" id="label14">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Original</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="x_options"></property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label15">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Flatten</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="x_options"></property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="view_before_flatten">
+ <property name="visible">True</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="view_after_flatten">
+ <property name="visible">True</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="xalign">0.029999999329447746</property>
+ <property name="label" translatable="yes">Do not forget to recompute the point of contact
+to propagate the flattening to the other images
+of the current file.</property>
+ <property name="wrap">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child internal-child="action_area">
+ <object class="GtkHButtonBox" id="dialog-action_area4">
+ <property name="visible">True</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="button_apply">
+ <property name="label">gtk-apply</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_cancel">
+ <property name="label">gtk-cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="1">button_apply</action-widget>
+ <action-widget response="0">button_cancel</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/openfovea/glade/openfovea.glade b/openfovea/glade/openfovea.glade
new file mode 100644
index 0000000..3748546
--- /dev/null
+++ b/openfovea/glade/openfovea.glade
@@ -0,0 +1,4628 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk+" version="2.16"/>
+ <object class="GtkWindow" id="MainWindow">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="title" translatable="yes">Open Fovea</property>
+ <property name="default_width">255</property>
+ <property name="default_height">256</property>
+ <property name="icon">../Icon/openfovea.png</property>
+ <signal name="destroy" handler="on_MainWindow_destroy" swapped="no"/>
+ <child>
+ <object class="GtkVBox" id="vbox6">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkMenuBar" id="menubar1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkMenuItem" id="menu_file">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_action_appearance">False</property>
+ <property name="label" translatable="yes">_File</property>
+ <property name="use_underline">True</property>
+ <child type="submenu">
+ <object class="GtkMenu" id="menu1">
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkImageMenuItem" id="new_experiment">
+ <property name="label">gtk-new</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_action_appearance">False</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ <signal name="activate" handler="on_new_experiment_activate" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="open_experiment">
+ <property name="label">gtk-open</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_action_appearance">False</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ <signal name="activate" handler="on_open_experiment_activate" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="save_experiment">
+ <property name="label">gtk-save</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_action_appearance">False</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ <signal name="activate" handler="on_save_experiment_activate" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="save_experiment_as">
+ <property name="label">gtk-save-as</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_action_appearance">False</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ <signal name="activate" handler="on_save_experiment_as_activate" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSeparatorMenuItem" id="<séparateur>">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="export_item">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_action_appearance">False</property>
+ <property name="label" translatable="yes">export</property>
+ <property name="use_underline">True</property>
+ <child type="submenu">
+ <object class="GtkMenu" id="menu_export">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkMenuItem" id="menu_export_stiffness">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="tooltip_text" translatable="yes">Export the stiffness data
+in csv files.</property>
+ <property name="use_action_appearance">False</property>
+ <property name="label" translatable="yes">Stiffness</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_menu_export_stiffness_activate" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="menu_export_average_stiffness_tomography">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="tooltip_text" translatable="yes">Export the average
+stiffness tomography
+in csv file.</property>
+ <property name="use_action_appearance">False</property>
+ <property name="label" translatable="yes">Average stiffness
+tomography</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_menu_export_average_stiffness_tomography_activate" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="menu_export_map">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="tooltip_text" translatable="yes">Export the maps
+as an image.</property>
+ <property name="use_action_appearance">False</property>
+ <property name="label" translatable="yes">Maps</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_menu_export_map_activate" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="menu_export_curve">
+ <property name="label" translatable="yes">Current curve</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="tooltip_text" translatable="yes">Export the current curve
+to a csv file.</property>
+ <property name="use_action_appearance">False</property>
+ <property name="image">image4</property>
+ <property name="use_stock">False</property>
+ <signal name="activate" handler="on_menu_export_curve_activate" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="menu_export_event_prop">
+ <property name="label" translatable="yes">Event properties</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="tooltip_text" translatable="yes">Export the event properties
+position, force, loading-rate, ...
+in a csv file.</property>
+ <property name="use_action_appearance">False</property>
+ <property name="image">image3</property>
+ <property name="use_stock">False</property>
+ <signal name="activate" handler="on_menu_export_event_prop_activate" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="menu_export_evt_nbr">
+ <property name="label" translatable="yes">nbr event per curves</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="tooltip_text" translatable="yes">Exports the number of event
+per curves in a csv file.</property>
+ <property name="use_action_appearance">False</property>
+ <property name="image">image5</property>
+ <property name="use_stock">False</property>
+ <signal name="activate" handler="on_menu_export_evt_nbr_activate" swapped="no"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSeparatorMenuItem" id="separatormenuitem1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="show_header">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_action_appearance">False</property>
+ <property name="label" translatable="yes">show header</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_show_header_activate" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="quitter1">
+ <property name="label">gtk-quit</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_action_appearance">False</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ <signal name="activate" handler="on_MainWindow_destroy" swapped="no"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="menu_edit">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_action_appearance">False</property>
+ <property name="label" translatable="yes">_Edition</property>
+ <property name="use_underline">True</property>
+ <child type="submenu">
+ <object class="GtkMenu" id="menu2">
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkImageMenuItem" id="couper1">
+ <property name="label">gtk-cut</property>
+ <property name="can_focus">False</property>
+ <property name="use_action_appearance">False</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="copier1">
+ <property name="label">gtk-copy</property>
+ <property name="can_focus">False</property>
+ <property name="use_action_appearance">False</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="coller1">
+ <property name="label">gtk-paste</property>
+ <property name="can_focus">False</property>
+ <property name="use_action_appearance">False</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="supprimer1">
+ <property name="label">gtk-delete</property>
+ <property name="can_focus">False</property>
+ <property name="use_action_appearance">False</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="ReloadModules1">
+ <property name="label">Reload modules</property>
+ <property name="can_focus">False</property>
+ <property name="use_action_appearance">False</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="exp_prop">
+ <property name="label">gtk-preferences</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="tooltip_text" translatable="yes">Edit the experiment
+properties</property>
+ <property name="use_action_appearance">False</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ <signal name="activate" handler="on_exp_prop_activate" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="group">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="tooltip_text" translatable="yes">Manage the groups</property>
+ <property name="use_action_appearance">False</property>
+ <property name="label" translatable="yes">group</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_group_activate" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="menu_mask">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="tooltip_text" translatable="yes">Create or modify mask for
+the arrays.</property>
+ <property name="use_action_appearance">False</property>
+ <property name="label" translatable="yes">mask</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_menu_mask_activate" swapped="no"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="menuitem3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_action_appearance">False</property>
+ <property name="label" translatable="yes">_Options</property>
+ <property name="use_underline">True</property>
+ <child type="submenu">
+ <object class="GtkMenu" id="menu3">
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkCheckMenuItem" id="apply_on_all">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_action_appearance">False</property>
+ <property name="label" translatable="yes">apply on all</property>
+ <property name="use_underline">True</property>
+ <signal name="toggled" handler="on_apply_on_all_toggled" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="compare_exp">
+ <property name="label" translatable="yes">compare experiment</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_action_appearance">False</property>
+ <property name="image">image2</property>
+ <property name="use_stock">False</property>
+ <signal name="activate" handler="on_compare_exp_activate" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="flatten_piezo">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="tooltip_text" translatable="yes">Open a dialog to flatten
+the piezo image.</property>
+ <property name="use_action_appearance">False</property>
+ <property name="label" translatable="yes">flatten image</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_flatten_activate" swapped="no"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="menuitem4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_action_appearance">False</property>
+ <property name="label" translatable="yes">_Aide</property>
+ <property name="use_underline">True</property>
+ <child type="submenu">
+ <object class="GtkMenu" id="menu4">
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkMenuItem" id="about">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_action_appearance">False</property>
+ <property name="label" translatable="yes">À _propos</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_about_activate" swapped="no"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkNotebook" id="notebook1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="scrollable">True</property>
+ <property name="enable_popup">True</property>
+ <child>
+ <object class="GtkVBox" id="vbox_display">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkHBox" id="navigation_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkTable" id="navigation_table">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="n_rows">3</property>
+ <property name="n_columns">3</property>
+ <child>
+ <object class="GtkButton" id="button_next">
+ <property name="label">gtk-go-forward</property>
+ <property name="width_request">88</property>
+ <property name="height_request">32</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text">Go to the right position in the scan</property>
+ <property name="use_action_appearance">False</property>
+ <property name="use_stock">True</property>
+ <signal name="clicked" handler="on_button_next_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_up">
+ <property name="label">gtk-go-up</property>
+ <property name="width_request">112</property>
+ <property name="height_request">32</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text">Go to a upper position in the scan</property>
+ <property name="use_action_appearance">False</property>
+ <property name="use_stock">True</property>
+ <signal name="clicked" handler="on_button_up_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_prev">
+ <property name="label">gtk-go-back</property>
+ <property name="width_request">88</property>
+ <property name="height_request">32</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="has_tooltip">True</property>
+ <property name="tooltip_text">Go to the left position in the scan</property>
+ <property name="use_action_appearance">False</property>
+ <property name="use_stock">True</property>
+ <signal name="clicked" handler="on_button_prev_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_down">
+ <property name="label">gtk-go-down</property>
+ <property name="width_request">112</property>
+ <property name="height_request">32</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="has_tooltip">True</property>
+ <property name="tooltip_text">Go to a lower position in the scan</property>
+ <property name="use_action_appearance">False</property>
+ <property name="use_stock">True</property>
+ <signal name="clicked" handler="on_button_down_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="position_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label_pos_x">
+ <property name="width_request">38</property>
+ <property name="height_request">17</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">0</property>
+ <property name="justify">center</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVSeparator" id="vseparator1">
+ <property name="width_request">16</property>
+ <property name="height_request">32</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_pos_y">
+ <property name="width_request">38</property>
+ <property name="height_request">17</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">0</property>
+ <property name="justify">center</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVSeparator" id="vseparator2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="list_file_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0</property>
+ <property name="label" translatable="yes">List of files</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="list_file_combo_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <signal name="changed" handler="on_list_file_combo_box_changed" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHSeparator" id="hseparator1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkFrame" id="frame1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="label_yalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="curve_display_list">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkVBox" id="vbox3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkCheckButton" id="check_display_fc_trace">
+ <property name="label" translatable="yes">Forward force curve</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_action_appearance">False</property>
+ <property name="use_underline">True</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ <signal name="clicked" handler="on_check_display_fc_trace_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="check_display_fc_retrace">
+ <property name="label" translatable="yes">Retraction force curve</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_action_appearance">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ <signal name="clicked" handler="on_check_display_fc_retrace_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="check_display_poc">
+ <property name="label" translatable="yes">Point of contact</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_action_appearance">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ <signal name="clicked" handler="on_check_display_poc_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="check_display_event">
+ <property name="label" translatable="yes">Event on curve</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_action_appearance">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ <signal name="clicked" handler="on_check_display_event_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="check_display_indent">
+ <property name="label" translatable="yes">Indentation curve</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_action_appearance">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ <signal name="clicked" handler="on_check_display_indent_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0</property>
+ <property name="label" translatable="yes"><b>Window to display</b></property>
+ <property name="use_markup">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="array_display_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkButton" id="button_piezzo_array">
+ <property name="label" translatable="yes">Piezo Height</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_action_appearance">False</property>
+ <signal name="clicked" handler="on_button_piezzo_array_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_topography_array">
+ <property name="label" translatable="yes">Topography</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_action_appearance">False</property>
+ <signal name="clicked" handler="on_button_topography_array_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_stiffness_array">
+ <property name="label" translatable="yes">Stiffness</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_action_appearance">False</property>
+ <signal name="clicked" handler="on_button_stiffness_array_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_event_array">
+ <property name="label" translatable="yes">Event </property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_action_appearance">False</property>
+ <signal name="clicked" handler="on_button_event_array_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_event_force_array">
+ <property name="label" translatable="yes">Event force</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_action_appearance">False</property>
+ <signal name="clicked" handler="on_button_event_force_array_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_event_dist_array">
+ <property name="label" translatable="yes">Event distance</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="tooltip_text" translatable="yes">Unbinding event rupture distance.</property>
+ <property name="use_action_appearance">False</property>
+ <signal name="clicked" handler="on_button_event_dist_array_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">5</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_event_length_array">
+ <property name="label" translatable="yes">Event length</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="tooltip_text" translatable="yes">Unbinding event length as computed
+with the event fit model.
+
+If not computed, go to the "Parameters" tab,
+select an event fit model and recompute
+the event detection.</property>
+ <property name="use_action_appearance">False</property>
+ <signal name="clicked" handler="on_button_event_length_array_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">6</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_stiffness_tomo">
+ <property name="label" translatable="yes"> Stiffness
+tomography</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="tooltip_text" translatable="yes">Display the detailed stiffness
+of the object with projection in
+x, y and z.</property>
+ <property name="use_action_appearance">False</property>
+ <signal name="clicked" handler="on_button_stiffness_tomo_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">7</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_mean_stiffness_tomo">
+ <property name="label" translatable="yes">Mean Stiffness
+ tomography</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="tooltip_text" translatable="yes">Display the average stiffness
+in function of the depth.</property>
+ <property name="use_action_appearance">False</property>
+ <signal name="clicked" handler="on_button_mean_stiffness_tomo_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">8</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_opengl">
+ <property name="label" translatable="yes">OpenGL</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_action_appearance">False</property>
+ <signal name="clicked" handler="on_button_opengl_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">9</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame_mosaic">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkVBox" id="vbox4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkButton" id="button_mosaic_piezo">
+ <property name="label" translatable="yes">Piezo Height</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_action_appearance">False</property>
+ <signal name="clicked" handler="on_button_mosaic_piezo_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_mosaic_topography">
+ <property name="label" translatable="yes">Topography</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_action_appearance">False</property>
+ <signal name="clicked" handler="on_button_mosaic_topography_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_mosaic_stiffness">
+ <property name="label" translatable="yes">Stiffness</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_action_appearance">False</property>
+ <signal name="clicked" handler="on_button_mosaic_stiffness_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_mosaic_event">
+ <property name="label" translatable="yes">Event</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_action_appearance">False</property>
+ <signal name="clicked" handler="on_button_mosaic_event_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_mosaic_event_force">
+ <property name="label" translatable="yes">Event force</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_action_appearance">False</property>
+ <signal name="clicked" handler="on_button_mosaic_event_force_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label30">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes"><b>Mosaic</b></property>
+ <property name="use_markup">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="tab_fill">False</property>
+ <property name="reorderable">True</property>
+ <property name="detachable">True</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0</property>
+ <property name="label" translatable="yes">Display</property>
+ </object>
+ <packing>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="compute_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkHBox" id="Allboxes">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkFrame" id="frame_stiffness">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <child>
+ <object class="GtkAlignment" id="alignment2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkVBox" id="stiffness_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">2</property>
+ <child>
+ <object class="GtkHBox" id="deep_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkEntry" id="entry_number_deep">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="width_chars">5</property>
+ <property name="text" translatable="yes">4</property>
+ <property name="xalign">0.5</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label7">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes"> segment(s) of </property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="entry_size_deep">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="width_chars">5</property>
+ <property name="text" translatable="yes">50</property>
+ <property name="xalign">0.5</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label9">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">nm.</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_compute_stiffness">
+ <property name="label" translatable="yes">Compute Stiffness</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_action_appearance">False</property>
+ <property name="use_underline">True</property>
+ <signal name="clicked" handler="on_button_compute_stiffness_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="check_compute_poc">
+ <property name="label" translatable="yes">re-compute point of contact</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_action_appearance">False</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ <signal name="toggled" handler="on_check_compute_poc_toggled" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label13">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes"><b>Stiffness</b></property>
+ <property name="use_markup">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame_event">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <child>
+ <object class="GtkAlignment" id="alignment3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkVBox" id="event_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">1</property>
+ <child>
+ <object class="GtkHBox" id="hbox1">
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label26">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Detection : </property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="combo_event_detect">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox5">
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label27">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Threshold : </property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="combo_event_thresh">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_detect_events">
+ <property name="label" translatable="yes">Detect Events</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_action_appearance">False</property>
+ <property name="use_underline">True</property>
+ <signal name="clicked" handler="on_button_detect_events_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox16">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkButton" id="button_regenerate_random">
+ <property name="label" translatable="yes">Regenerate random</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_action_appearance">False</property>
+ <signal name="clicked" handler="on_button_regenerate_random_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="spin_nbr_rand_event">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="tooltip_text" translatable="yes">Number of random event</property>
+ <property name="invisible_char">●</property>
+ <property name="adjustment">adj_nbr_rand_event</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label16">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes"><b>Events</b></property>
+ <property name="use_markup">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkFrame" id="frame_rel_stiffness">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <child>
+ <object class="GtkAlignment" id="alignment1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkVBox" id="vbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkHBox" id="hbox6">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkSpinButton" id="max_rel_stiffness">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="adjustment">adj_rel_stiff_max_dist</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label17">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">pixel distance</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_compute_rel_stiffness">
+ <property name="label" translatable="yes">Compute relative stiffness</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_action_appearance">False</property>
+ <signal name="clicked" handler="on_button_compute_rel_stiffness_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label11">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes"><b>Relative stiffness</b></property>
+ <property name="use_markup">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ <property name="reorderable">True</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label14">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0</property>
+ <property name="label" translatable="yes">Compute</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="parameter_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkVBox" id="vbox13">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkFrame" id="frame5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment11">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkVBox" id="vbox15">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkHBox" id="hbox2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label25">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Model : </property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="combo_hertz_model">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="tooltip_text" translatable="yes">Change the model of your cantilever tip.
+
+Please modify the tip size according to the model.
+
+* Sphere model : radius of the tip [nm]
+ typical value : 30
+* Cone model : semi-opening angle [rad]
+ typical value : 0.35</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox29">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label_tip_size">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Tip size : </property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="spin_carac">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="tooltip_text" translatable="yes">Change the caracteristic of
+your cantilever tip.</property>
+ <property name="invisible_char">●</property>
+ <property name="adjustment">adj_tip_size</property>
+ <property name="digits">2</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox25">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label43">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Poisson ratio : </property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="spin_poisson_ratio">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="adjustment">adj_poisson_ratio</property>
+ <property name="digits">2</property>
+ <property name="update_policy">if-valid</property>
+ <signal name="value-changed" handler="on_spin_poisson_ratio_value_changed" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox32">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label59">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Method : </property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="combo_stiff_method">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="has_tooltip">True</property>
+ <property name="tooltip_markup" translatable="yes">Change the method of computing the stiffness.
+
+* Raw : fits the raw data with the corresponding model.
+* Extrema : makes a linear fit between the two extrem
+ points in the slice. The indentation axis is
+ modified according to the used model.</property>
+ <property name="tooltip_text" translatable="yes">Change the method of computing the stiffness.
+
+* Raw : fits the raw data with the corresponding model.
+* Extrema : makes a linear fit between the two extrem
+ points in the slice. The indentation axis is
+ modified according to the used model.</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label39">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes"><b>Stiffness</b></property>
+ <property name="use_markup">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox7">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkFrame" id="frame4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment10">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkVBox" id="vbox12">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkButton" id="button_event_calib">
+ <property name="label" translatable="yes">Adjust sensitivity</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_action_appearance">False</property>
+ <signal name="clicked" handler="on_button_event_calib_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="ev_fit_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="evt_fit_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Event fit model : </property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="combo_ev_fit_model">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <signal name="changed" handler="on_combo_ev_fit_model_changed" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox24">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label38">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xpad">9</property>
+ <property name="label" translatable="yes">Event distance threshold</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="spin_ev_threshold_dist">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="digits">2</property>
+ <signal name="value-changed" handler="on_spin_ev_threshold_dist_value_changed" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox31">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label57">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xpad">9</property>
+ <property name="label" translatable="yes">Event length threshold</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="spin_ev_length_thresh_min">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="digits">2</property>
+ <signal name="value-changed" handler="on_spin_ev_length_thresh_min_value_changed" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="spin_ev_length_thresh_max">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="digits">2</property>
+ <signal name="value-changed" handler="on_spin_ev_length_thresh_max_value_changed" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label22">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes"><b>Events</b></property>
+ <property name="use_markup">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame_PoC_param">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment6">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkVBox" id="vbox5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkButton" id="button_poc_calib">
+ <property name="label" translatable="yes">Adjust sensitivity</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_action_appearance">False</property>
+ <signal name="clicked" handler="on_button_poc_calib_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="combo_poc_method">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <signal name="changed" handler="on_combo_poc_method_changed" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="check_limit_poc_slide">
+ <property name="label" translatable="yes">Limit sliding</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_action_appearance">False</property>
+ <property name="draw_indicator">True</property>
+ <signal name="toggled" handler="on_check_limit_poc_slide_toggled" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox28">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label44">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Fit segment size</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="spin_fit_seg_size">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="adjustment">adj_fit_seg_size</property>
+ <property name="digits">2</property>
+ <signal name="value-changed" handler="on_spin_fit_seg_size_value_changed" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label18">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes"><b>Point of contact</b></property>
+ <property name="use_markup">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label15">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0</property>
+ <property name="label" translatable="yes">Parameters</property>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="Result_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child>
+ <object class="GtkViewport" id="viewport1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="resize_mode">queue</property>
+ <child>
+ <object class="GtkHBox" id="hbox17">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkVBox" id="vbox_ev_force">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label_force">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Force</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_ev_force_hist">
+ <property name="label" translatable="yes">Histogram</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_action_appearance">False</property>
+ <signal name="clicked" handler="on_button_ev_force_hist_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox10">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkVBox" id="force_hist_min">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label12">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">min</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="ev_force_hist_min">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="digits">2</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="force_hist_max">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label8">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">max</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="ev_force_hist_max">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="digits">2</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="force_hist_col_size">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label6">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">col size</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="ev_force_hist_size">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="digits">3</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVSeparator" id="vseparator3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox_ev_lr">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label_lr">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Loading rate</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_ev_lr_hist">
+ <property name="label" translatable="yes">Histogram</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_action_appearance">False</property>
+ <signal name="clicked" handler="on_button_ev_lr_hist_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox8">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkVBox" id="lr_hist_min">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label20">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">min</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="ev_lr_hist_min">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="digits">2</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="lr_hist_max">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label21">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">max</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="ev_lr_hist_max">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="digits">2</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="lr_hist_col_size">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label10">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">col size</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="ev_lr_hist_size">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="digits">3</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVSeparator" id="vseparator4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox_ev_dist">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label_dist">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Distance</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_ev_dist_hist">
+ <property name="label" translatable="yes">Histogram</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_action_appearance">False</property>
+ <signal name="clicked" handler="on_button_ev_dist_hist_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox18">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkVBox" id="lr_hist_min1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label35">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">min</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="ev_dist_hist_min">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="digits">2</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="lr_hist_max1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label36">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">max</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="ev_dist_hist_max">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="digits">2</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="lr_hist_col_size1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label37">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">col size</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="ev_dist_hist_size">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="digits">3</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVSeparator" id="vseparator5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">5</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox_ev_dist1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label_stiff">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Stiffness</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_ev_stiff_hist">
+ <property name="label" translatable="yes">Histogram</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_action_appearance">False</property>
+ <signal name="clicked" handler="on_button_ev_stiff_hist_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox20">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkVBox" id="lr_hist_min2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label40">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">min</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="ev_stiff_hist_min">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="lr_hist_max2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label41">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">max</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="ev_stiff_hist_max">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="lr_hist_col_size2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label42">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">col size</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="ev_stiff_hist_size">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="check_stiff_ev_vs_notev">
+ <property name="label" translatable="yes">Event VS Not Event
+(No group for this)</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_action_appearance">False</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">6</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVSeparator" id="vseparator6">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">7</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox_ev_dist2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label_rel_stiff">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Relative Stiffness</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_ev_relstiff_hist">
+ <property name="label" translatable="yes">Histogram</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_action_appearance">False</property>
+ <signal name="clicked" handler="on_button_ev_relstiff_hist_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox21">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkVBox" id="rel_stiff_hist_min">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label45">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">min</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="ev_relstiff_hist_min">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="digits">2</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="rel_stiff_hist_max">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label46">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">max</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="ev_relstiff_hist_max">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="digits">2</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox22">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkVBox" id="relstiff_hist_col_size">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label47">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">col size</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="ev_relstiff_hist_size">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="digits">3</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox11">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label_dis">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">distance</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="ev_relstiff_hist_dist">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="adjustment">spin_hist_rel_dist</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">8</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox_ev_length">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label_ev_length">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Length</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_ev_length_hist">
+ <property name="label" translatable="yes">Histogram</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_action_appearance">False</property>
+ <signal name="clicked" handler="on_button_ev_length_hist_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox23">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkVBox" id="length_hist_min">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label51">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">min</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="ev_length_hist_min">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="digits">2</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="length_hist_max">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label52">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">max</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="ev_length_hist_max">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="digits">2</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="length_hist_col_size">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label53">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">col size</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="ev_length_hist_size">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="digits">3</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">9</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVSeparator" id="vseparator7">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">10</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox_ev_plength">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label_ev_plength">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Persistent Length</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_ev_plength_hist">
+ <property name="label" translatable="yes">Histogram</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_action_appearance">False</property>
+ <signal name="clicked" handler="on_button_ev_plength_hist_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox30">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkVBox" id="plength_hist_min">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label54">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">min</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="ev_plength_hist_min">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="digits">2</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="plength_hist_max">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label55">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">max</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="ev_plength_hist_max">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="digits">2</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="plength_hist_col_size">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label56">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">col size</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="ev_plength_hist_size">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="digits">3</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">11</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVSeparator" id="vseparator8">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">12</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHSeparator" id="hseparator2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment8">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkHBox" id="hbox19">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkVBox" id="vbox8">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkButton" id="button_scatter_Force_LR">
+ <property name="label" translatable="yes">Force vs Loading rate</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_action_appearance">False</property>
+ <signal name="clicked" handler="on_button_scatter_Force_LR_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_scatter_force_dist">
+ <property name="label" translatable="yes">Force vs Distance</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_action_appearance">False</property>
+ <signal name="clicked" handler="on_button_scatter_force_dist_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_scatter_force_stiffness">
+ <property name="label" translatable="yes">Force vs Stiffness</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_action_appearance">False</property>
+ <signal name="clicked" handler="on_button_scatter_force_stiffness_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox9">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkButton" id="button_scatter_LR_Dist">
+ <property name="label" translatable="yes">Loading rate vs Distance</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_action_appearance">False</property>
+ <signal name="clicked" handler="on_button_scatter_LR_Dist_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_scatter_LR_Stiffness">
+ <property name="label" translatable="yes">Loading rate vs Stiffness</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_action_appearance">False</property>
+ <signal name="clicked" handler="on_button_scatter_LR_Stiffness_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_scatter_Dist_Stiffness">
+ <property name="label" translatable="yes">Distance vs Stiffness</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_action_appearance">False</property>
+ <signal name="clicked" handler="on_button_scatter_Dist_Stiffness_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox10">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkButton" id="button_scatter_Force_RelStiff">
+ <property name="label" translatable="yes">Force vs Rel stiff</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_action_appearance">False</property>
+ <signal name="clicked" handler="on_button_scatter_Force_RelStiff_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_scatter_LR_RelStiff">
+ <property name="label" translatable="yes">Loading rate vs Rel stiff</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_action_appearance">False</property>
+ <signal name="clicked" handler="on_button_scatter_LR_RelStiff_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_scatter_Dist_RelStiff">
+ <property name="label" translatable="yes">Distance vs Rel stiff</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_action_appearance">False</property>
+ <signal name="clicked" handler="on_button_scatter_Dist_RelStiff_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label34">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes"><b>Scatter plots</b></property>
+ <property name="use_markup">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_regenerate_event_properties">
+ <property name="label">gtk-refresh</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_action_appearance">False</property>
+ <property name="use_stock">True</property>
+ <signal name="clicked" handler="on_button_regenerate_event_properties_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Ev. Histo.</property>
+ </object>
+ <packing>
+ <property name="position">3</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="Tab_Plot">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkFrame" id="frameRelativeStiffness">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <child>
+ <object class="GtkAlignment" id="alignment4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkVBox" id="vbox2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkHBox" id="hbox11">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label24">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">pixel distance</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="spin_rel_stiff_plot_dist">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="tooltip_text" translatable="yes">Set the pixel distance of the
+relative stiffness to plot</property>
+ <property name="invisible_char">●</property>
+ <property name="adjustment">adj_plot_rel_dist</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox12">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label28">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Exclude </property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="spin_range_rel_stiff">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="adjustment">adj_rel_stiff_limite</property>
+ <property name="digits">2</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label29">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes"> times stiffer or softer.</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label_plot_rel_stiff">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes"><b>Relative stiffness</b></property>
+ <property name="use_markup">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frameNbrEvtTL">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <child>
+ <object class="GtkAlignment" id="alignment7">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkHBox" id="hbox15">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkButton" id="button_plot_nbr_event_tl">
+ <property name="label" translatable="yes">Show</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_action_appearance">False</property>
+ <signal name="clicked" handler="on_button_plot_nbr_event_tl_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="labelNbr_evt">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes"><b>Number events</b></property>
+ <property name="use_markup">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">4</property>
+ <property name="tab_fill">False</property>
+ <property name="reorderable">True</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label_note_plot">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Plot</property>
+ </object>
+ <packing>
+ <property name="position">4</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkTable" id="table1">
+ <property name="can_focus">False</property>
+ <property name="n_rows">2</property>
+ <property name="n_columns">2</property>
+ <child>
+ <object class="GtkTable" id="table2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="n_rows">2</property>
+ <property name="n_columns">2</property>
+ <child>
+ <object class="GtkHBox" id="hbox7">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label31">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">z slice</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="spinbutton1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="x_options"></property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox13">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label32">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">x slice</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="spinbutton2">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options"></property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox14">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label33">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">y slice</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="spinbutton5">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options"></property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_compute_tomo">
+ <property name="label" translatable="yes">reconstruct tomo</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="tooltip_text" translatable="yes">Tomography volume has
+to be reconstructed each
+time the stiffness is
+computed</property>
+ <property name="use_action_appearance">False</property>
+ <signal name="clicked" handler="on_button_compute_tomo_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="x_options"></property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="display_z_slice">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkHBox" id="display_x_slice">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="display_y_slice">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">5</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label_tomo">
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Tomography</property>
+ </object>
+ <packing>
+ <property name="position">5</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox14">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkHBox" id="hbox26">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkVBox" id="vbox_ev_force1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label_ym">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Young modulus</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_young_modulus_hist">
+ <property name="label" translatable="yes">Histogram</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_action_appearance">False</property>
+ <signal name="clicked" handler="on_button_young_modulus_hist_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox27">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkVBox" id="box_ym_hist_min">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label48">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">min</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="spin_ym_hist_min">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="digits">2</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="box_ym_hist_max">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label49">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">max</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="spin_ym_hist_max">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="digits">2</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="box_ym_hist_col_size">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label50">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">col size</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="spin_ym_hist_size">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="digits">3</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox16">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label58">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Depth Nb</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="spin_ym_hist_depth_nb">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="position">6</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label_stiffnes">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Mechanical prop.</property>
+ </object>
+ <packing>
+ <property name="position">6</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkProgressBar" id="progressbar_general">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox9">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_switch_state">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <object class="GtkAboutDialog" id="aboutdialog">
+ <property name="can_focus">False</property>
+ <property name="border_width">5</property>
+ <property name="window_position">center</property>
+ <property name="icon">../Icon/openfovea.png</property>
+ <property name="type_hint">normal</property>
+ <property name="program_name">OpenFovea</property>
+ <property name="version">0.1a162</property>
+ <property name="comments" translatable="yes">OpenFovea is an open source software
+written in the Laboratory of living matter (EPFL, CH)
+
+Please cite the original article that present
+OpenFovea if you use this software.</property>
+ <property name="website">http://www.freesbi.ch/en/openfovea</property>
+ <property name="website_label" translatable="yes">Home page</property>
+ <property name="license" translatable="yes">This package is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version.
+
+This package is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with this package; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA</property>
+ <property name="authors">Charles Roduit</property>
+ <property name="logo">../Icon/CellFovea.png</property>
+ <property name="wrap_license">True</property>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">2</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox" id="dialog-action_area1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="events">GDK_BUTTON_PRESS_MASK | GDK_STRUCTURE_MASK</property>
+ <property name="layout_style">end</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ </child>
+ </object>
+ <object class="GtkAdjustment" id="adj_fit_seg_size">
+ <property name="upper">1</property>
+ <property name="value">0.29999999999999999</property>
+ <property name="step_increment">0.01</property>
+ <property name="page_increment">0.10000000000000001</property>
+ </object>
+ <object class="GtkAdjustment" id="adj_injection_index">
+ <property name="upper">100</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="adj_nbr_rand_event">
+ <property name="upper">100</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="adj_plot_rel_dist">
+ <property name="upper">100</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ <signal name="value-changed" handler="on_adj_plot_rel_dist_value_changed" swapped="no"/>
+ </object>
+ <object class="GtkAdjustment" id="adj_poisson_ratio">
+ <property name="upper">0.5</property>
+ <property name="value">0.29999999999999999</property>
+ <property name="step_increment">0.01</property>
+ <property name="page_increment">0.10000000000000001</property>
+ </object>
+ <object class="GtkAdjustment" id="adj_rel_stiff_limite">
+ <property name="upper">100</property>
+ <property name="step_increment">0.10000000000000001</property>
+ <property name="page_increment">0.5</property>
+ <signal name="value-changed" handler="on_adj_rel_stiff_limite_value_changed" swapped="no"/>
+ </object>
+ <object class="GtkAdjustment" id="adj_rel_stiff_max_dist">
+ <property name="upper">100</property>
+ <property name="value">3</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="adj_tip_size">
+ <property name="upper">100</property>
+ <property name="value">20</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkListStore" id="choose_event_detection">
+ <columns>
+ <!-- column-name gchararray1 -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">Noise (Default)</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Fuzzy</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkListStore" id="choose_hertz_model">
+ <columns>
+ <!-- column-name gchararray1 -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">Cone</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Sphere</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkListStore" id="choose_poc_model">
+ <columns>
+ <!-- column-name gchararray1 -->
+ <column type="gchararray"/>
+ </columns>
+ </object>
+ <object class="GtkListStore" id="choose_threshold_event">
+ <columns>
+ <!-- column-name gchararray1 -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">Fixed</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Auto</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkDialog" id="dialog_compare_experiment">
+ <property name="can_focus">False</property>
+ <property name="border_width">5</property>
+ <property name="type_hint">normal</property>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="dialog-vbox2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">2</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox" id="dialog-action_area2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="button_compare_exp_validate">
+ <property name="label">gtk-ok</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_action_appearance">False</property>
+ <property name="use_stock">True</property>
+ <property name="yalign">0.46000000834465027</property>
+ <signal name="clicked" handler="on_button_compare_exp_validate_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_compare_exp_cancel">
+ <property name="label">gtk-cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_action_appearance">False</property>
+ <property name="use_stock">True</property>
+ <signal name="clicked" handler="on_button_compare_exp_cancel_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox1_compare_exp">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkHBox" id="hbox1_compare_exp">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">15</property>
+ <child>
+ <object class="GtkLabel" id="comp_exp_filename">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">No file loaded</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="open_exp_compare">
+ <property name="label" translatable="yes">Choose file</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_action_appearance">False</property>
+ <signal name="clicked" handler="on_open_exp_compare_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkAlignment" id="alignment9">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkTable" id="table1_compare_exp">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="n_rows">3</property>
+ <property name="n_columns">2</property>
+ <child>
+ <object class="GtkLabel" id="label_experiment">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Experiment</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label19">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">label</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_main_filename">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">No file loaded</property>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_comp_filename">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">No file loaded</property>
+ </object>
+ <packing>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="entry_main_label">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="entry_comp_label">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes"><b>Legend label</b></property>
+ <property name="use_markup">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="padding">13</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox2_compare_exp">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="label23">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Plot type :</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="combo_plot_color_type">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="model">list_plot_color_type</property>
+ <child>
+ <object class="GtkCellRendererText" id="cellrenderertext1"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="0">button_compare_exp_validate</action-widget>
+ <action-widget response="0">button_compare_exp_cancel</action-widget>
+ </action-widgets>
+ </object>
+ <object class="GtkImage" id="image1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="stock">gtk-missing-image</property>
+ </object>
+ <object class="GtkImage" id="image2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="stock">gtk-zoom-fit</property>
+ </object>
+ <object class="GtkImage" id="image3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0.50999999046325684</property>
+ <property name="yalign">0.51999998092651367</property>
+ <property name="pixbuf">../Icon/event.png</property>
+ </object>
+ <object class="GtkImage" id="image4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xpad">3</property>
+ <property name="pixbuf">../Icon/export_curve.png</property>
+ </object>
+ <object class="GtkImage" id="image5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="pixbuf">../Icon/two_evt.png</property>
+ </object>
+ <object class="GtkListStore" id="list_plot_color_type">
+ <columns>
+ <!-- column-name gchararray1 -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">Color</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Black and White</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkAdjustment" id="spin_hist_rel_dist">
+ <property name="upper">100</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+</interface>
diff --git a/openfovea/glade/plot.glade b/openfovea/glade/plot.glade
new file mode 100644
index 0000000..fa050ad
--- /dev/null
+++ b/openfovea/glade/plot.glade
@@ -0,0 +1,104 @@
+<?xml version="1.0"?>
+<interface>
+ <requires lib="gtk+" version="2.16"/>
+ <!-- interface-naming-policy project-wide -->
+ <object class="GtkWindow" id="WindowPlot">
+ <property name="title" translatable="yes">Force Curve</property>
+ <property name="default_width">300</property>
+ <property name="default_height">300</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="icon">../Icon/openfovea.png</property>
+ <child>
+ <object class="GtkVBox" id="vboxPlot">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkHBox" id="hbox9">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkVBox" id="boxPlot">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVScrollbar" id="scrollbar_depth">
+ <property name="orientation">vertical</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="customizeBar">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkVBox" id="vbox12">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkHScrollbar" id="scrollbar_max"/>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHScrollbar" id="scrollbar_min"/>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_modify_plot">
+ <property name="label" translatable="yes">color scale</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="box_nav_tool">
+ <property name="visible">True</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/openfovea/glade/stiffness_plot.glade b/openfovea/glade/stiffness_plot.glade
new file mode 100644
index 0000000..000c02e
--- /dev/null
+++ b/openfovea/glade/stiffness_plot.glade
@@ -0,0 +1,163 @@
+<?xml version="1.0"?>
+<interface>
+ <requires lib="gtk+" version="2.16"/>
+ <!-- interface-naming-policy project-wide -->
+ <object class="GtkWindow" id="WindowPlot">
+ <property name="default_width">400</property>
+ <property name="default_height">500</property>
+ <child>
+ <object class="GtkVBox" id="vbox1">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkHBox" id="hbox1">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkHBox" id="hbox13">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkLabel" id="label32">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">x slice</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="spin_x">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="adjustment">adjust_x</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox14">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkLabel" id="label33">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">y slice</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="spin_y">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="adjustment">adjust_y</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox7">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkLabel" id="label31">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">z slice</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="spin_z">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="adjustment">adjust_z</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="boxPlot">
+ <property name="visible">True</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHScrollbar" id="scrollbar_max"/>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHScrollbar" id="scrollbar_min"/>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <object class="GtkAdjustment" id="adjust_z">
+ <property name="upper">100</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="adjust_x">
+ <property name="upper">100</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="adjust_y">
+ <property name="upper">100</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+</interface>
diff --git a/openfovea/openfovea_gui.py b/openfovea/openfovea_gui.py
new file mode 100644
index 0000000..06ca15c
--- /dev/null
+++ b/openfovea/openfovea_gui.py
@@ -0,0 +1,2999 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+ Module for the GTK gui of OpenFovea.
+"""
+
+import gtk
+import os.path
+import sys
+import locale
+import threading
+import time
+import datetime
+from glib import GError
+
+import numpy
+#####################
+## Graphic library ##
+#####################
+import plot_gtk
+# for script installation
+from pkg_resources import resource_filename
+######################
+## Internal library ##
+######################
+from common import APP_NAME, APP_VERSION
+import classes
+from classes import NotReadyError
+from plot_generic import PlotProperties, PlotData
+
+try:
+ from fovea_toolbox.opengl_gtk import TopoWindow as TopoGL
+except ImportError:
+ # OpenGL is not installed.
+ OPENGL_INSTALLED = False
+except RuntimeError:
+ # OpenGL cannot be loaded...
+ OPENGL_INSTALLED = False
+else:
+ OPENGL_INSTALLED = True
+
+import dialog_experiment_properties
+from dialog_export_map import MapDisplay
+from dialog_mask_properties import MaskProperties
+from dialog_flatten import FlattenDialog
+from simple_window_gtk import FileGroup
+from fovea_toolbox import misc
+
+DEBUG = 0
+
+
+def combo_box_set_list(combox, item_list, default=0):
+ """
+ Sets the combobox list
+ """
+ preexist = 0
+ # Reinitialize or create the model
+ model = combox.get_model()
+ if model:
+ model.clear()
+ preexist = 1
+ else:
+ model = gtk.ListStore(str)
+
+ for item in item_list:
+ model.append([item])
+ # Pack it if it doesn't preexist
+ if not preexist:
+ combox.set_model(model)
+ cell = gtk.CellRendererText()
+ combox.pack_start(cell, True)
+ combox.add_attribute(cell, 'text', 0)
+ # And set the first item active
+ combox.set_active(default)
+
+
+class OpenFoveaMainGui(object):
+ """
+ OpenFovea GTK Gui.
+
+ Contains all the buttons and dialog to handle experiment post
+ processing.
+ """
+
+ def __init__(self, arg=None):
+ # Defaults parameters
+ if os.name == 'posix':
+ _home = os.environ['HOME']
+ elif os.name == 'nt':
+ _home = os.environ['HOMEPATH']
+ self.config = {'current': os.getcwd(),
+ 'home': _home,
+ 'config': os.path.join(
+ _home,
+ '.config',
+ 'OpenFovea.conf'),
+ 'folder-fv': os.getcwd(),
+ 'folder-aex': os.getcwd(),
+ 'folder-export': os.getcwd(),
+ 'msgid': datetime.datetime(1900, 1, 1),
+ 'linewidth': 1.5, # The width of the line in the
+ # curve plots.
+ 'connect-server': None}
+ self.apply_on_all = 0
+ # try to recover preferences...
+ if os.path.isfile(self.config['config']):
+ config_fid = open(self.config['config'])
+ for line in config_fid.readlines():
+ splitted_line = line.strip('\n').split(' = ')
+ if splitted_line[0] == 'msgid':
+ self.config[splitted_line[0]] = \
+ datetime.datetime.strptime(splitted_line[1],
+ '%Y, %m, %d')
+ elif splitted_line[0] == 'linewidth':
+ self.config[splitted_line[0]] = float(splitted_line[1])
+ elif splitted_line[0] == 'connect-server':
+ self.config[splitted_line[0]] = eval(splitted_line[1])
+ elif splitted_line[0] in self.config.keys():
+ self.config[splitted_line[0]] = \
+ splitted_line[1]
+ if 'folder-export' not in self.config:
+ self.config['folder-export'] = os.getcwd()
+ # Get authorization for connection
+ if self.config['connect-server'] is None:
+ answer = ask_message(['Allow internet connection ?',
+ '<b>Do you allow OpenFovea to connect server ?</b>\n\n' +
+ 'The connection is usefull to display message\n' +
+ 'about new release, or important bugs.'])
+ if answer == gtk.RESPONSE_YES:
+ self.config['connect-server'] = True
+ elif answer == gtk.RESPONSE_NO:
+ self.config['connect-server'] = False
+ ###
+ # Get message from the OpenFovea website in order to display news.
+ # This will be used for update announce or important news.
+ if self.config['connect-server']:
+ message, new_id = misc.get_message(self.config['msgid'],
+ APP_NAME, APP_VERSION)
+ if message is not None:
+ display_message(message[1:])
+ self.config['msgid'] = new_id
+ ###
+ # Initialize the gui
+ ###
+ builder = gtk.Builder()
+ try:
+ glade_filename = resource_filename('openfovea',
+ 'glade/openfovea.glade')
+ except NotImplementedError:
+ # We are in a windows executable...
+ glade_filename = os.path.join(os.path.dirname(sys.executable),
+ ('openfovea/glade/openfovea.glade'))
+ builder.add_from_file(glade_filename)
+ self.exp_filename = None
+ self.window = builder.get_object("MainWindow")
+ self.about_dialog_win = builder.get_object('aboutdialog')
+ self.about_dialog = builder.get_object("aboutdialog")
+ self.properties_dialog = None
+ self.compare_exp_dialog = \
+ builder.get_object('dialog_compare_experiment')
+ # connect signals
+ builder.connect_signals(self)
+ ## The variables
+ self.plot_properties = PlotProperties()
+ self.plot_properties.color_type = 'color'
+ self.data_hist = PlotData()
+ self.experiment = None
+ self.temp_exp_compare = None
+ self.experiment_compare = None
+ self.force_volume = None
+ self.widget = {
+ 'progressbar': builder.get_object("progressbar_general"),
+ 'check_display_fc_trace': builder.get_object(
+ 'check_display_fc_trace'),
+ 'check_display_fc_retrace': builder.get_object(
+ 'check_display_fc_retrace'),
+ 'check_display_poc': builder.get_object(
+ 'check_display_poc'),
+ 'check_display_indent': builder.get_object(
+ 'check_display_indent'),
+ 'check_display_event': builder.get_object(
+ 'check_display_event'),
+ 'check_limit_poc_slide': builder.get_object(
+ 'check_limit_poc_slide'),
+ 'check_compute_poc': builder.get_object('check_compute_poc'),
+ 'check_stiff_ev_vs_notev': builder.get_object(
+ 'check_stiff_ev_vs_notev'),
+ 'combo_list_files': builder.get_object('list_file_combo_box'),
+ 'combo_hertz_model': builder.get_object('combo_hertz_model'),
+ 'combo_stiff_method': builder.get_object('combo_stiff_method'),
+ 'combo_event_detect': builder.get_object('combo_event_detect'),
+ 'combo_event_thresh': builder.get_object('combo_event_thresh'),
+ 'combo_poc_method': builder.get_object('combo_poc_method'),
+ 'combo_ev_fit_model': builder.get_object(
+ 'combo_ev_fit_model'),
+ 'entry_number_deep': builder.get_object("entry_number_deep"),
+ 'entry_size_deep': builder.get_object('entry_size_deep'),
+ 'entry_glass_slope': builder.get_object('entry_glass_slope'),
+ 'label_pos_x': builder.get_object('label_pos_x'),
+ 'label_pos_y': builder.get_object('label_pos_y'),
+ 'label_switch_state': builder.get_object('label_switch_state'),
+ 'spin_ev_force_hist_min': builder.get_object('ev_force_hist_min'),
+ 'spin_ev_force_hist_max': builder.get_object('ev_force_hist_max'),
+ 'spin_ev_force_hist_size': builder.get_object(
+ 'ev_force_hist_size'),
+ 'spin_max_rel_stiffness': builder.get_object('max_rel_stiffness'),
+ 'spin_ev_lr_hist_min': builder.get_object('ev_lr_hist_min'),
+ 'spin_ev_lr_hist_max': builder.get_object('ev_lr_hist_max'),
+ 'spin_ev_lr_hist_size': builder.get_object('ev_lr_hist_size'),
+ 'spin_ev_dist_hist_min': builder.get_object('ev_dist_hist_min'),
+ 'spin_ev_dist_hist_max': builder.get_object('ev_dist_hist_max'),
+ 'spin_ev_dist_hist_size': builder.get_object('ev_dist_hist_size'),
+ 'spin_ev_length_hist_min': builder.get_object(
+ 'ev_length_hist_min'),
+ 'spin_ev_length_hist_max': builder.get_object(
+ 'ev_length_hist_max'),
+ 'spin_ev_length_hist_size': builder.get_object(
+ 'ev_length_hist_size'),
+ 'spin_ev_plength_hist_min': builder.get_object(
+ 'ev_plength_hist_min'),
+ 'spin_ev_plength_hist_max': builder.get_object(
+ 'ev_plength_hist_max'),
+ 'spin_ev_plength_hist_size': builder.get_object(
+ 'ev_plength_hist_size'),
+ 'spin_ev_stiff_hist_min': builder.get_object('ev_stiff_hist_min'),
+ 'spin_ev_stiff_hist_max': builder.get_object('ev_stiff_hist_max'),
+ 'spin_ev_stiff_hist_size': builder.get_object(
+ 'ev_stiff_hist_size'),
+ 'spin_ev_relstiff_hist_min': builder.get_object(
+ 'ev_relstiff_hist_min'),
+ 'spin_ev_relstiff_hist_max': builder.get_object(
+ 'ev_relstiff_hist_max'),
+ 'spin_ev_relstiff_hist_size': builder.get_object(
+ 'ev_relstiff_hist_size'),
+ 'spin_ev_relstiff_hist_dist': builder.get_object(
+ 'ev_relstiff_hist_dist'),
+ 'spin_rel_stiff_plot_dist': builder.get_object(
+ 'spin_rel_stiff_plot_dist'),
+ 'spin_nbr_rand_event': builder.get_object('spin_nbr_rand_event'),
+ 'spin_ev_threshold_dist': builder.get_object(
+ 'spin_ev_threshold_dist'),
+ 'spin_poisson_ratio': builder.get_object('spin_poisson_ratio'),
+ # Option menu
+ 'apply_on_all': builder.get_object('apply_on_all'),
+ # Parameters tab
+ 'spin_fit_seg_size': builder.get_object('spin_fit_seg_size'),
+ 'spin_ev_length_thresh_min': builder.get_object(
+ 'spin_ev_length_thresh_min'),
+ 'spin_ev_length_thresh_max': builder.get_object(
+ 'spin_ev_length_thresh_max'),
+ # Mechanical prop.
+ 'spin_young_modulus_hist_min': builder.get_object(
+ 'spin_ym_hist_min'),
+ 'spin_young_modulus_hist_max': builder.get_object(
+ 'spin_ym_hist_max'),
+ 'spin_young_modulus_hist_size': builder.get_object(
+ 'spin_ym_hist_size'),
+ 'spin_tip_carac': builder.get_object('spin_carac'),
+ 'spin_ym_hist_depth_nb': builder.get_object(
+ 'spin_ym_hist_depth_nb'),
+ }
+ self.signal = {
+ ### Dictionnary that contains the signal from the object.
+ ### This is auto - generated with :
+ ### widgetname_fctname
+ }
+ self.widget_exp_compare = {
+ 'comp_exp_filename': builder.get_object('comp_exp_filename'),
+ 'label_main_filename': builder.get_object('label_main_filename'),
+ 'label_comp_filename': builder.get_object('label_comp_filename'),
+ 'entry_main_label': builder.get_object('entry_main_label'),
+ 'entry_comp_label': builder.get_object('entry_comp_label'),
+ 'combo_plot_color_type': builder.get_object(
+ 'combo_plot_color_type'),
+ }
+ if not OPENGL_INSTALLED:
+ builder.get_object('button_opengl').hide()
+ #####
+ ## Compute tab
+ #####
+ combo_box_set_list(self.widget['combo_event_detect'],
+ ['Noise', 'Fuzzy'])
+ combo_box_set_list(self.widget['combo_event_thresh'],
+ ['Fixed', 'Auto (Noise)'])
+ #####
+ ## Parameter tab
+ #####
+ combo_box_set_list(self.widget['combo_poc_method'],
+ ['deriv', 'curve_fit'])
+ self.widget['combo_poc_method'].set_active(1)
+
+ adjustment = gtk.Adjustment(value=0, lower=0, upper=1000,
+ step_incr=1, page_incr=10, page_size=0)
+ self.widget['spin_ev_threshold_dist'].set_adjustment(adjustment)
+
+ adjustment = gtk.Adjustment(value=0, lower=0, upper=2000,
+ step_incr=1, page_incr=10, page_size=0)
+ self.widget['spin_ev_length_thresh_min'].set_adjustment(adjustment)
+
+ adjustment = gtk.Adjustment(value=0, lower=0, upper=1000,
+ step_incr=1, page_incr=10, page_size=0)
+ self.widget['spin_ev_length_thresh_max'].set_adjustment(adjustment)
+
+ combo_box_set_list(self.widget['combo_hertz_model'],
+ ['Cone', 'Sphere'])
+ combo_box_set_list(self.widget['combo_stiff_method'],
+ ['Raw', 'Linear', 'Extrema', 'Median'])
+ self.widget['combo_hertz_model'].set_active(1)
+ self.widget['combo_stiff_method'].set_active(0)
+ self.widget['spin_tip_carac'].set_value(40)
+ self.widget['spin_poisson_ratio'].set_value(0.3)
+ combo_box_set_list(self.widget['combo_ev_fit_model'],
+ ['None', 'wlc', 'fjc'])
+ #####
+ ## Event tab
+ #####
+
+ # The windows used for the plots
+ self.win_plot = {'Curve Plot': 0,
+ 'Indent Plot': 0,
+ 'Topography Map': 0,
+ 'Piezo Height': 0,
+ 'Stiffness Map': 0,
+ 'Event Map': 0,
+ 'Event Force Map': 0,
+ 'Event Distance Map': 0,
+ 'Event Length Map': 0,
+ 'Stiffness Tomo': 0,
+ 'Average Stiffness Tomo': 0,
+ 'Mosaic Piezo': 0,
+ 'Mosaic Topo': 0,
+ 'Mosaic Stiffness': 0,
+ 'Mosaic Event': 0,
+ 'Mosaic Event Force': 0,
+ 'Hist Event Force': 0,
+ 'Hist Event LR': 0,
+ 'Hist Event Dist': 0,
+ 'Hist Event Stiff': 0,
+ 'Hist Event Rel Stiff': 0,
+ 'Hist Event Length': 0,
+ 'Hist Event Persistent Length': 0,
+ 'TL Rel Stiffness': 0,
+ 'TL Nbr Event': 0,
+ 'Scatter_FvsLR': 0,
+ 'Scatter_FvsDist': 0,
+ 'Scatter_FvsStiff': 0,
+ 'Scatter_LRvsDist': 0,
+ 'Scatter_LRvsStiff': 0,
+ 'Scatter_DistvsStiff': 0,
+ 'Scatter_FvsRelStiff': 0,
+ 'Scatter_LRvsRelStiff': 0,
+ 'Scatter_DistvsRelStiff': 0,
+ 'Slice_on_map': 0,
+ 'Hist Young Modulus': 0,
+ }
+ if arg is not None:
+ self.on_open_experiment_activate(arg)
+ ##########################################################################
+ # Misc part of the window
+
+ # bottom
+ def display_progressbar(self, fraction, text=''):
+ """
+ Display the progress bar.
+ """
+ if fraction == None:
+ self.widget['progressbar'].set_property('visible', True)
+ self.widget['progressbar'].set_fraction(0)
+ self.widget['progressbar'].set_text('')
+ elif type(fraction) != str:
+ while gtk.events_pending():
+ gtk.main_iteration()
+ if not self.widget['progressbar'].get_property('visible'):
+ self.widget['progressbar'].set_property('visible', True)
+ self.widget['progressbar'].set_fraction(fraction)
+ self.widget['progressbar'].set_text(text)
+ self.widget['progressbar'].show()
+ elif fraction == 'pulse':
+ while gtk.events_pending():
+ gtk.main_iteration()
+ self.widget['progressbar'].pulse()
+ self.widget['progressbar'].set_text(text)
+ self.widget['progressbar'].show()
+
+ def update_switch(self):
+ """
+ Update switches in the lower part of the GUI.
+ """
+ # generate the text
+ text = ''
+ _fvid = self.experiment.file['current']
+ for key in self.experiment.switch_dict:
+ text += key + ' : ' + str(self.experiment.get_switch(key)) + ' | '
+ self.widget['label_switch_state'].set_text(text)
+ _thresh = self.experiment.file['list'][_fvid].get_switch('ev_thresh')
+ self.widget['spin_ev_threshold_dist'].set_value(_thresh)
+ self.widget['spin_poisson_ratio'].set_value(
+ self.experiment.file['list'][_fvid].get_parameter('poisson_ratio'))
+ _thresh = self.experiment.file['list'][_fvid].get_switch(
+ 'ev_fit_length')
+ if _thresh[0] is not None:
+ self.widget['spin_ev_length_thresh_max'].set_value(_thresh[0])
+ else:
+ self.widget['spin_ev_length_thresh_max'].set_value(0)
+ if _thresh[1] is not None:
+ self.widget['spin_ev_length_thresh_max'].set_value(_thresh[1])
+ else:
+ self.widget['spin_ev_length_thresh_max'].set_value(0)
+
+ def update_hist(self, label, max_val, min_val=0):
+ """
+ Update the histogram properties.
+ """
+ meth_name = 'on_button_%s_clicked' % label
+ # First disconnect the signal from the widget. Otherwise, it will plot
+ # automatically. This is not wanted in case of loading new files, ...
+ for item in ['min', 'max', 'size']:
+ widget_name = 'spin_%s_%s' % (label, item)
+ signal_name = '%s_min_%s' % (widget_name, meth_name)
+ if signal_name in self.signal:
+ self.widget[widget_name].disconnect(self.signal[signal_name])
+ widget_name = 'spin_%s_max' % label
+ self.widget[widget_name].set_adjustment(
+ gtk.Adjustment(value=max_val,
+ lower=min_val,
+ upper=max_val * 10,
+ step_incr=max_val / 100,
+ page_incr=max_val / 25,
+ page_size=0))
+ self.widget[widget_name].set_text(str(max_val))
+
+ widget_name = 'spin_%s_min' % label
+ self.widget[widget_name].set_adjustment(
+ gtk.Adjustment(value=min_val,
+ lower=min_val,
+ upper=max_val,
+ step_incr=max_val / 100,
+ page_incr=max_val / 25,
+ page_size=0))
+ self.widget[widget_name].set_text(str(min_val))
+
+ widget_name = 'spin_%s_size' % label
+ self.widget[widget_name].set_adjustment(
+ gtk.Adjustment(value=max_val / 10,
+ lower=0,
+ upper=max_val,
+ step_incr=max_val / 1000,
+ page_incr=max_val / 250,
+ page_size=0))
+ self.widget[widget_name].set_text(str(max_val / 10))
+ meth = getattr(self, meth_name)
+ # Last : connect signal from the spins
+ for item in ['min', 'max', 'size']:
+ widget_name = 'spin_%s_%s' % (label, item)
+ signal_name = '%s_min_%s' % (widget_name, meth_name)
+ self.signal[signal_name] = \
+ self.widget[widget_name].connect('value_changed', meth)
+
+ def update(self, what):
+ """
+ Update the GUI.
+ """
+ if what == 'show gride position':
+ self.widget['label_pos_x'].set_text(str(self.force_volume.pos_x))
+ self.widget['label_pos_y'].set_text(str(self.force_volume.pos_y))
+ try:
+ self.win_plot['Piezo Height'].mark_pos(
+ self.force_volume.pos_x,
+ self.force_volume.pos_y)
+ except (KeyError, StandardError):
+ pass
+ try:
+ self.win_plot['Stiffness Map'].mark_pos(
+ self.force_volume.pos_x,
+ self.force_volume.pos_y)
+ except (KeyError, StandardError):
+ pass
+ try:
+ self.win_plot['Event Map'].mark_pos(
+ self.force_volume.pos_x,
+ self.force_volume.pos_y)
+ except (KeyError, StandardError):
+ pass
+ try:
+ self.win_plot['Topography Map'].mark_pos(
+ self.force_volume.pos_x,
+ self.force_volume.pos_y)
+ except (KeyError, StandardError):
+ pass
+ elif what == 'show glass slope':
+ self.widget['entry_glass_slope'].set_text(
+ str(self.force_volume.stiffness['glass']))
+ elif what == 'gui':
+ if self.force_volume is None:
+ return
+ self.widget['spin_max_rel_stiffness'].set_value(
+ self.experiment.parameters['rel_stiff_dist'])
+ self.widget['entry_number_deep'].set_text(
+ str(self.force_volume.stiffness['nb_parts']))
+ self.widget['entry_size_deep'].set_text(
+ str(self.force_volume.stiffness['size_parts']))
+ self.widget['spin_nbr_rand_event'].set_value(
+ self.force_volume.event['nbr_random'])
+ self.widget['spin_fit_seg_size'].set_value(
+ self.force_volume.stiffness['poc_len_slice'])
+ if self.force_volume.stiffness['hertz_model'] == 'Cone':
+ self.widget['combo_hertz_model'].set_active(0)
+ elif self.force_volume.stiffness['hertz_model'] == 'Sphere':
+ self.widget['combo_hertz_model'].set_active(1)
+ if self.force_volume.stiffness['fit_method'] == 'Raw':
+ self.widget['combo_stiff_method'].set_active(0)
+ elif self.force_volume.stiffness['fit_method'] == 'Linear':
+ self.widget['combo_stiff_method'].set_active(1)
+ elif self.force_volume.stiffness['fit_method'] == 'Extrema':
+ self.widget['combo_stiff_method'].set_active(2)
+ elif self.force_volume.stiffness['fit_method'] == 'Median':
+ self.widget['combo_stiff_method'].set_active(3)
+ self.widget['spin_tip_carac'].set_value(
+ self.force_volume.stiffness['point_carac'])
+ if self.force_volume.header['event_fit_model'] == None:
+ self.widget['combo_ev_fit_model'].set_active(0)
+ elif self.force_volume.header['event_fit_model'] == 'wlc':
+ self.widget['combo_ev_fit_model'].set_active(1)
+ elif self.force_volume.header['event_fit_model'] == 'fjc':
+ self.widget['combo_ev_fit_model'].set_active(2)
+
+ # Update switch apply_on_all.
+ if self.experiment.get_switch('all') == 'Yes':
+ self.widget['apply_on_all'].set_active(True)
+ elif self.experiment.get_switch('all') == 'No':
+ self.widget['apply_on_all'].set_active(False)
+ self.update('hist entry')
+ elif what == 'hist entry':
+ ##
+ ## Histograms
+ ##
+ try:
+ max_dist_hist = self.force_volume.trace_x[-1]
+ except TypeError:
+ max_dist_hist = 10000
+ self.update_hist('ev_force_hist', max_val=1.)
+ self.update_hist('ev_lr_hist', max_val=1.)
+
+ self.update_hist('ev_dist_hist',
+ max_val=max_dist_hist)
+ self.update_hist('ev_relstiff_hist', max_val=10., min_val=-10.)
+ # Take the maximum stiffness
+ stiff_max = 1.e8
+ exp = 7
+ for fvo in self.experiment.file['list']:
+ if fvo.get_array('Stiffness') is not None:
+ try:
+ stiff_max = fvo.get_array('Stiffness').max()
+ except ValueError:
+ pass
+ if stiff_max:
+ try:
+ exp = int(numpy.log10(stiff_max)) + 1
+ except ValueError:
+ exp = 7
+ else:
+ exp = 7
+ self.update_hist('ev_stiff_hist', max_val=10. ** exp)
+ self.update_hist('young_modulus_hist', max_val=10. ** exp)
+ self.update_hist('ev_length_hist',
+ max_val=max_dist_hist * 2)
+ self.update_hist('ev_plength_hist',
+ max_val=max_dist_hist * 2)
+ #fvo.get_array('Stiffness').shape[2]
+ # update max depth in for the plot.
+ spin_depth = ['spin_ym_hist_depth_nb']
+ meth_name = 'on_button_young_modulus_hist_clicked'
+ if fvo.get_array('Stiffness') is not None:
+ for item in spin_depth:
+ # disconnect the widget from the signal
+ signal_name = '%s_min_%s' % (meth_name, item)
+ if signal_name in self.signal:
+ self.widget[item].disconnect(self.signal[signal_name])
+ self.widget[item].set_adjustment(
+ gtk.Adjustment(
+ value=0,
+ lower=0,
+ upper=fvo.get_array('Stiffness').shape[2],
+ step_incr=1,
+ page_incr=1,
+ page_size=0))
+ # and reconnect it
+ meth = getattr(self, meth_name)
+ # Last : connect signal from the spins
+ self.signal[signal_name] = \
+ self.widget[item].connect('value_changed', meth)
+
+ elif what == 'fv display prop':
+ self.force_volume.plot_who['Av'] = \
+ self.widget['check_display_fc_trace'].get_active()
+ self.force_volume.plot_who['Re'] = \
+ self.widget['check_display_fc_retrace'].get_active()
+ self.force_volume.plot_who['PoC'] = \
+ self.widget['check_display_poc'].get_active()
+ self.force_volume.plot_who['Event'] = \
+ self.widget['check_display_event'].get_active()
+ self.force_volume.plot_who['Indent'] = \
+ self.widget['check_display_indent'].get_active()
+ elif what == 'plots':
+ for item in self.win_plot:
+ if self.win_plot[item]:
+ self.win_plot[item].set_size(
+ self.experiment.parameters['plot_size'][0],
+ self.experiment.parameters['plot_size'][1])
+ if self.win_plot[item].get_type() == 'histogram':
+ self.win_plot[item].set_y_rel(
+ self.experiment.parameters['histo_y_rel'])
+
+ self.update_switch()
+
+ def regenerate_plot(self, ptype='normal'):
+ """
+ Regenerate a window plot and returns the window and widgets.
+ """
+ builder = gtk.Builder()
+ if ptype == 'normal':
+ try:
+ _glade_filename = resource_filename('openfovea',
+ 'glade/plot.glade')
+ except NotImplementedError:
+ _glade_filename = 'openfovea/glade/plot.glade'
+ elif ptype == 'tomography':
+ try:
+ _glade_filename = resource_filename('openfovea',
+ 'glade/stiffness_plot.glade')
+ except NotImplementedError:
+ _glade_filename = 'openfovea/glade/stiffness_plot.glade'
+ try:
+ builder.add_from_file(_glade_filename)
+ except: # In windows environment...
+ print ptype
+ if ptype == 'tomography':
+ glade_filename = os.path.join(os.path.dirname(sys.executable),
+ 'openfovea/glade/stiffness_plot.glade')
+ elif ptype == 'normal':
+ glade_filename = os.path.join(os.path.dirname(sys.executable),
+ 'openfovea/glade/plot.glade')
+ else:
+ glade_filename = os.path.join(os.path.dirname(sys.executable),
+ 'openfovea/glade/plot.glade')
+ builder.add_from_file(glade_filename)
+ window = builder.get_object("WindowPlot")
+ builder.connect_signals(self)
+ if ptype == 'normal':
+ widget = {
+ 'plot_area': builder.get_object('boxPlot'),
+ 'scrollbar_min': builder.get_object('scrollbar_min'),
+ 'scrollbar_max': builder.get_object('scrollbar_max'),
+ 'scrollbar_depth': builder.get_object('scrollbar_depth'),
+ 'box_nav_tool': builder.get_object('box_nav_tool'),
+ 'button_modify_plot': builder.get_object('button_modify_plot')}
+ elif ptype == 'tomography':
+ widget = {
+ 'plot_area': builder.get_object('boxPlot'),
+ 'display_x': builder.get_object('display_x'),
+ 'display_y': builder.get_object('display_y'),
+ 'display_z': builder.get_object('display_z'),
+ 'adjust_x': builder.get_object('adjust_x'),
+ 'adjust_y': builder.get_object('adjust_y'),
+ 'adjust_z': builder.get_object('adjust_z'),
+ 'scrollbar_min': builder.get_object('scrollbar_min'),
+ 'scrollbar_max': builder.get_object('scrollbar_max')}
+ return [window, widget]
+
+ def modify_window(self, window, how):
+ """
+ Modify the specified window.
+
+ window = 'Curve Plot' : add the scale bars to modify the events /
+ PoC detection threshold.
+ """
+
+ # Curve Plot window modification to tune the event detection
+ if window == 'Curve Plot':
+ if how == 'Event detection':
+ self.win_plot['Curve Plot'].widget['scrollbar_max'].\
+ set_property('visible', True)
+ self.win_plot['Curve Plot'].widget['scrollbar_max'].\
+ set_adjustment(gtk.Adjustment(
+ self.force_volume.header['event_detect_weight'],
+ 0.0, 5, 0.1, 0.5, 0))
+ self.win_plot['Curve Plot'].widget['scrollbar_max'].\
+ connect_object("change-value",
+ self.change_event_weight, None)
+ if how == 'PoC detection':
+ self.win_plot['Curve Plot'].widget['scrollbar_depth'].\
+ set_property('visible', True)
+ self.win_plot['Curve Plot'].widget['scrollbar_depth'].\
+ set_adjustment(gtk.Adjustment(
+ self.force_volume.stiffness['poc_threshold'],
+ 0.0, 10, 0.1, 0.5, 0))
+ self.win_plot['Curve Plot'].widget['scrollbar_depth'].\
+ connect_object("change-value",
+ self.change_poc_threshold, None)
+
+ def close_all_windows(self):
+ """
+ Close all the plot windows.
+ Usefull when loading new experiment.
+ """
+ for window in self.win_plot:
+ if exist(self.win_plot[window]):
+ self.win_plot[window].on_win_plot_destroy('from_gui')
+
+ def on_gride_click(self, event):
+ """
+ Event when user click on the array.
+ """
+ if event.inaxes is not None:
+ # Create the factor (1 if [0;1000[, 1000 if [1000:1e6[, ...)
+ scan_size = self.force_volume.header['scan_size']
+ pix_size = self.force_volume.header['pixel_size']
+ try:
+ factor = [1000 ** (int(numpy.log10(i) / 3)) for i in scan_size]
+ except OverflowError:
+ # The scan size is set to 0.
+ x_pos = numpy.floor(event.xdata)
+ y_pos = numpy.floor(event.ydata)
+ else:
+ x_pos = event.xdata * factor[0] / pix_size[0]
+ y_pos = event.ydata * factor[1] / pix_size[1]
+ if event.button == 1:
+ # Left mouse button ==> goto
+ self.force_volume.go_to(int(x_pos), int(y_pos))
+ self.plot_curve()
+ self.update('show gride position')
+ elif event.button == 3:
+ # Right mouse button ==> slice
+ self.path_gride(event.inaxes)
+
+ def on_gride_motion(self, event):
+ """
+ Event when mouse is moving on the array.
+ """
+ if event.inaxes is not None:
+ self.path_gride(event.inaxes)
+
+ def path_gride(self, axe_id):
+ """
+ Slice the array.
+ """
+ path = None
+ for item in self.win_plot:
+ # Find the window where user click
+ if self.win_plot[item]:
+ _axis = self.win_plot[item].axis
+ if type(_axis) == dict and 'array' in _axis:
+ if _axis['array'] == axe_id:
+ path = self.win_plot[item].get_path()
+
+ if path is not None:
+ # Points list is defined.
+ this_slice = self.force_volume.get_path('all', path)
+
+ if not exist(self.win_plot['Slice_on_map']):
+ window, widget = self.regenerate_plot()
+ self.win_plot['Slice_on_map'] = plot_gtk.plotInteractiveCurve(
+ window, widget, win_title='Slices')
+ #xlabel = '[nm]',
+ #ylabel = '???',
+ #title = 'Slice of...',
+ #win_title = 'Slice of...')
+ self.win_plot['Slice_on_map'].plot(this_slice[0],
+ this_slice[1],
+ this_slice[2])
+ else:
+ self.win_plot['Slice_on_map'].modify_plot(this_slice[0],
+ this_slice[1])
+
+ def on_mosaic_click(self, event):
+ """
+ Change the current FV scan by clicking on the corresponding scan in
+ the mosaic view.
+ """
+ for item in self.win_plot:
+ if isinstance(self.win_plot[item], plot_gtk.plotMosaic):
+ if self.win_plot[item].axis.count(event.inaxes):
+ _index = self.win_plot[item].axis.index(event.inaxes)
+ self.force_volume = self.experiment.get_file(_index)
+ self.update('fv display prop')
+ self.update('gui')
+ self.widget['combo_list_files'].set_active(_index)
+
+ def on_mosaic_pick(self, event):
+ """
+ Change the corresponding scan state according to the picked object.
+ For example, by clicking on the "M" box, it changes the mask state.
+ """
+ for item in self.win_plot:
+ if isinstance(self.win_plot[item], plot_gtk.plotMosaic):
+ if self.win_plot[item].axis.count(event.mouseevent.inaxes):
+ [axis, otype] = self.win_plot[item].index(event)
+ if otype == 'mask':
+ current = \
+ self.experiment.file['list'][axis].get_switch('mask')
+ self.experiment.file['list'][axis].set_switch('mask',
+ not(current))
+
+ def change_event_weight(self, widget, jump, value):
+ """
+ Change the event detection dependency to noise. Done with the scale
+ bar.
+ """
+
+ if DEBUG:
+ print "Changing event weight..."
+ print widget, jump
+ self.experiment.parameters['event_detect_weight'] = value
+ self.plot_curve()
+
+ def change_poc_threshold(self, widget, jump, value):
+ """
+ Change the point of contact detection threshold. Done with the
+ scale bar.
+ """
+
+ if DEBUG:
+ print "Changing poc threshold..."
+ print widget, jump
+ self.experiment.PoC_threshold = value
+ self.plot_curve()
+
+ #############################################################
+ ## File Menu
+ def on_new_experiment_activate(self, widget):
+ '''
+ Creates a new experiment by asking the user to point where the AFM
+ files are.
+ '''
+ if DEBUG:
+ print "Make new experiment. Call from :"
+ print widget
+ # Call the file chooser dialog.
+ # In order to open directory, change action in
+ # gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER
+ chooser = gtk.FileChooserDialog(
+ title='Choose folder',
+ action=gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
+ buttons=(gtk.STOCK_CANCEL,
+ gtk.RESPONSE_CANCEL,
+ gtk.STOCK_OPEN,
+ gtk.RESPONSE_OK))
+ chooser.set_select_multiple(False)
+ chooser.set_default_response(gtk.RESPONSE_OK)
+ chooser.set_local_only(False)
+ chooser.set_filename(self.config['folder-fv'])
+ response = chooser.run()
+ # Analyse the answer
+ if response == gtk.RESPONSE_OK:
+ folder = u'' + chooser.get_filenames()[0]
+ elif response == gtk.RESPONSE_CANCEL:
+ chooser.destroy()
+ return
+ else:
+ return
+
+ chooser.destroy()
+ self.experiment = classes.FVFolder()
+ _thread = threading.Thread(
+ target=self.experiment.load_folder, args=(folder,))
+ _thread.start()
+ while _thread.isAlive():
+ self.display_progressbar(self.experiment.counter['num'],
+ self.experiment.counter['text'])
+ time.sleep(0.1)
+ self.force_volume = self.experiment.get_file(0)
+ # File was correctly loaded, saving the folder in preferences...
+ self.config['folder-fv'] = folder
+ self.exp_filename = None
+ # update file list comboBox...
+ combo_box_set_list(self.widget['combo_list_files'],
+ self.experiment.file['name'])
+ #self.update('gui')
+ self.display_progressbar(None)
+ self.close_all_windows()
+
+ def load_exp(self):
+ '''
+ Loads an aex file. This is the generic function.
+ '''
+ # Call FileChooser dialog...
+ chooser = gtk.FileChooserDialog(title='Choose file to open',
+ action=gtk.FILE_CHOOSER_ACTION_OPEN,
+ buttons=(gtk.STOCK_CANCEL,
+ gtk.RESPONSE_CANCEL,
+ gtk.STOCK_OPEN,
+ gtk.RESPONSE_OK))
+ chooser.set_select_multiple(False)
+ chooser.set_default_response(gtk.RESPONSE_OK)
+ chooser.set_local_only(False)
+ chooser.set_current_folder(self.config['folder-aex'])
+ ext_filter = gtk.FileFilter()
+ ext_filter.add_pattern('*.aex')
+ ext_filter.set_name('aex - Afm Experiment XML')
+ chooser.add_filter(ext_filter)
+ response = chooser.run()
+ # Analyse output:
+ if response == gtk.RESPONSE_OK:
+ file_item = u'' + chooser.get_filenames()[0]
+ elif response == gtk.RESPONSE_CANCEL:
+ chooser.destroy()
+ return
+ else:
+ chooser.destroy()
+ return
+ chooser.destroy()
+ experiment = self.__load_exp(file_item)
+ return experiment
+
+ def __load_exp(self, file_item):
+ experiment = classes.FVFolder()
+ _thread = threading.Thread(target=experiment.load_aex,
+ args=(file_item, ))
+ _thread.start()
+ self.display_progressbar('pulse')
+ while _thread.isAlive():
+ self.display_progressbar('pulse', experiment.counter['text'])
+ time.sleep(0.1)
+ # File was correctly loaded, saving the folder in preferences...
+ self.config['folder-aex'] = os.path.split(file_item)[0]
+ experiment.counter['num'] = 0 # reset the counter
+ self.display_progressbar(None)
+ return experiment
+
+ def on_open_experiment_activate(self, arg):
+ """
+ Load experiment.
+ """
+
+ if DEBUG:
+ print "Open experiment. Call from :"
+ print arg
+ if type(arg) in [str, unicode]:
+ _experiment = self.__load_exp(arg)
+ else:
+ _experiment = self.load_exp()
+ if _experiment is not None:
+ # If smth is loaded, get it into memory. Else, keep the same.
+ self.experiment = _experiment
+ self.window.set_title(("%s : %s") % (WIN_TITLE,
+ self.experiment.short_name))
+ self.force_volume = self.experiment.get_file(0)
+ self.exp_filename = self.experiment.filename
+ # update file list comboBox...
+ combo_box_set_list(self.widget['combo_list_files'],
+ self.experiment.file['name'])
+ self.update('gui')
+ self.update('hist entry')
+ self.update_switch()
+ self.close_all_windows()
+
+ def on_open_exp_compare_activated(self, widget):
+ """
+ Load experiment for comparison.
+ """
+
+ if DEBUG:
+ print "Make experiment to compare. Call from :"
+ print widget
+ self.experiment_compare = self.load_exp()
+
+ def on_save_experiment_as_activate(self, widget):
+ '''
+ Saves data and results in an aex file.
+ '''
+ if DEBUG:
+ print "Save experiment. Call from :"
+ print widget
+ chooser = gtk.FileChooserDialog(title='Save file to ...',
+ action=gtk.FILE_CHOOSER_ACTION_SAVE,
+ buttons=(gtk.STOCK_CANCEL,
+ gtk.RESPONSE_CANCEL,
+ gtk.STOCK_SAVE,
+ gtk.RESPONSE_OK))
+ chooser.set_default_response(gtk.RESPONSE_OK)
+ chooser.set_local_only(False)
+ chooser.set_current_folder(self.config['folder-aex'])
+ response = chooser.run()
+ # Analyse output:
+ if response == gtk.RESPONSE_OK:
+ self.exp_filename = u'' + chooser.get_filenames()[0]
+ else:
+ chooser.destroy()
+ return
+ chooser.destroy()
+ self.save_exp()
+
+ self.window.set_title(("%s : %s") % (WIN_TITLE,
+ self.experiment.short_name))
+
+ def on_save_experiment_activate(self, widget):
+ """
+ Save the experiment.
+ """
+
+ if self.exp_filename is not None and os.path.isfile(self.exp_filename):
+ self.save_exp()
+ else:
+ self.on_save_experiment_as_activate(widget)
+
+ def save_exp(self):
+ """
+ Save the experiment.
+ """
+ #self.experiment.save(file_item)
+ _thread = threading.Thread(target=self.experiment.save,
+ args=(self.exp_filename, ))
+ _thread.start()
+ self.display_progressbar('pulse')
+ while _thread.isAlive():
+ self.display_progressbar('pulse', self.experiment.counter['text'])
+ time.sleep(0.1)
+ # File was correctly saved. Saving the folder in preferences...
+ self.config['folder-aex'] = os.path.split(self.exp_filename)[0]
+ self.experiment.counter['num'] = 0
+ self.display_progressbar(None)
+
+ def on_menu_export_stiffness_activate(self, widget):
+ """
+ Export the computed stiffness on a csv file
+ """
+ if DEBUG:
+ print "Export stiffness. Called from :"
+ print widget
+
+ chooser = gtk.FileChooserDialog(title='Export stiffness',
+ action=gtk.FILE_CHOOSER_ACTION_SAVE,
+ buttons=(gtk.STOCK_CANCEL,
+ gtk.RESPONSE_CANCEL,
+ gtk.STOCK_SAVE,
+ gtk.RESPONSE_OK))
+ chooser.set_default_response(gtk.RESPONSE_OK)
+ chooser.set_local_only(False)
+ chooser.set_current_folder(self.config['folder-export'])
+ response = chooser.run()
+ if response == gtk.RESPONSE_OK:
+ filename = chooser.get_filenames()[0]
+ self.experiment.export_stiffness(filename)
+ self.config['folder-export'] = os.path.split(filename)[0]
+ elif response == gtk.RESPONSE_CANCEL:
+ chooser.destroy()
+ return
+ else:
+ chooser.destroy()
+ return
+ chooser.destroy()
+
+ def on_menu_export_average_stiffness_tomography_activate(self, widget):
+ """
+ Export the average stiffness on a csv file
+ """
+ if DEBUG:
+ print "Export average stiffness. Called from :"
+ print widget
+ chooser = gtk.FileChooserDialog(title='Export average stiffness',
+ action=gtk.FILE_CHOOSER_ACTION_SAVE,
+ buttons=(gtk.STOCK_CANCEL,
+ gtk.RESPONSE_CANCEL,
+ gtk.STOCK_SAVE,
+ gtk.RESPONSE_OK))
+ chooser.set_default_response(gtk.RESPONSE_OK)
+ chooser.set_local_only(False)
+ chooser.set_current_folder(self.config['folder-export'])
+ response = chooser.run()
+ if response == gtk.RESPONSE_OK:
+ filename = chooser.get_filenames()[0]
+ self.experiment.export_average_stiffness_tomo(filename)
+ self.config['folder-export'] = os.path.split(filename)[0]
+ elif response == gtk.RESPONSE_CANCEL:
+ chooser.destroy()
+ return
+ else:
+ chooser.destroy()
+ return
+ chooser.destroy()
+
+ def on_menu_export_map_activate(self, widget):
+ """
+ Export the stiffness map of the file as a png file.
+ """
+ if DEBUG:
+ print "Export map. Called from :"
+ print widget
+ array_dict = {}
+ for item in self.force_volume.get_array('keys'):
+ array_dict[item] = self.force_volume.get_array(item)
+ export = MapDisplay(array_dict,
+ folder=self.config['folder-export'],
+ depth=self.force_volume.stiffness['size_parts'],
+ fvname=self.force_volume.name,
+ scan_size=self.force_volume.header['scan_size'])
+ export.run()
+
+ def on_menu_export_curve_activate(self, widget):
+ """
+ Export the force indentation curve as csv file.
+ """
+ if DEBUG:
+ print "Export curve. Called from :"
+ print widget
+ chooser = gtk.FileChooserDialog(title='Export FD curve',
+ action=gtk.FILE_CHOOSER_ACTION_SAVE,
+ buttons=(gtk.STOCK_CANCEL,
+ gtk.RESPONSE_CANCEL,
+ gtk.STOCK_SAVE,
+ gtk.RESPONSE_OK))
+ chooser.set_default_response(gtk.RESPONSE_OK)
+ chooser.set_local_only(False)
+ chooser.set_current_folder(self.config['folder-export'])
+ response = chooser.run()
+ if response == gtk.RESPONSE_OK:
+ filename = chooser.get_filenames()[0]
+ self.force_volume.export_curve(filename, 'both')
+ self.config['folder-export'] = os.path.split(filename)[0]
+ elif response == gtk.RESPONSE_CANCEL:
+ chooser.destroy()
+ return
+ else:
+ chooser.destroy()
+ return
+ chooser.destroy()
+
+ def on_menu_export_event_prop_activate(self, widget):
+ """
+ Export the event properties as csv files.
+ """
+
+ if DEBUG:
+ print "Export evnt. Called from :"
+ print widget
+ chooser = gtk.FileChooserDialog(title='Export event properties',
+ action=gtk.FILE_CHOOSER_ACTION_SAVE,
+ buttons=(gtk.STOCK_CANCEL,
+ gtk.RESPONSE_CANCEL,
+ gtk.STOCK_SAVE,
+ gtk.RESPONSE_OK))
+ chooser.set_default_response(gtk.RESPONSE_OK)
+ chooser.set_local_only(False)
+ chooser.set_current_folder(self.config['folder-export'])
+ response = chooser.run()
+ if response == gtk.RESPONSE_OK:
+ filename = chooser.get_filenames()[0]
+ self.experiment.export_events(filename)
+ self.config['folder-export'] = os.path.split(filename)[0]
+ elif response == gtk.RESPONSE_CANCEL:
+ chooser.destroy()
+ return
+ else:
+ chooser.destroy()
+ return
+ chooser.destroy()
+
+ def on_menu_export_evt_nbr_activate(self, widget):
+ """
+ Export the number of evt per curves in csv file.
+ """
+
+ if DEBUG:
+ print "Export evnt. Called from :"
+ print widget
+ chooser = gtk.FileChooserDialog(title='Export event number per curves',
+ action=gtk.FILE_CHOOSER_ACTION_SAVE,
+ buttons=(gtk.STOCK_CANCEL,
+ gtk.RESPONSE_CANCEL,
+ gtk.STOCK_SAVE,
+ gtk.RESPONSE_OK))
+ chooser.set_default_response(gtk.RESPONSE_OK)
+ chooser.set_local_only(False)
+ chooser.set_current_folder(self.config['folder-export'])
+ response = chooser.run()
+ if response == gtk.RESPONSE_OK:
+ filename = chooser.get_filenames()[0]
+ self.experiment.export_nbr_events(filename)
+ self.config['folder-export'] = os.path.split(filename)[0]
+ elif response == gtk.RESPONSE_CANCEL:
+ chooser.destroy()
+ return
+ else:
+ chooser.destroy()
+ return
+ chooser.destroy()
+
+ def on_menu_mask_activate(self, widget):
+ """
+ Generate masks.
+ """
+
+ if DEBUG:
+ print "Activate mask. Called from :"
+ print widget
+ mask_edit = MaskProperties(self.force_volume)
+ answer = mask_edit.run()
+ del answer
+ mask_edit.destroy()
+ self.experiment.update_switch()
+ self.update('gui')
+ ##
+ ## Edition Menu
+
+ ########
+ ## dialog experiment properties
+ def on_exp_prop_activate(self, widget):
+ '''
+ Create the properties dialog.
+ '''
+ if DEBUG:
+ print "Property dialog. Called from :"
+ print widget
+
+ self.properties_dialog = dialog_experiment_properties.main_win(
+ author=self.experiment.author,
+ comment=self.experiment.comment,
+ spring_constant=self.experiment.parameters['spring_constant'],
+ inject_index=[self.experiment.parameters['injection_index'],
+ len(self.experiment.file['list'])],
+ stiff_glass=self.experiment.parameters['glass'],
+ trace_curve=[self.force_volume.trace_x, self.force_volume.trace_y],
+ plot_size=self.experiment.parameters['plot_size'],
+ group=self.experiment.group,
+ linewidth=self.config['linewidth'],
+ histo_y_rel=self.experiment.parameters['histo_y_rel'],
+ plot_hist_gauss=self.experiment.parameters['plot_hist_gauss'],
+ plot_color=self.plot_properties.color_type
+ )
+ self.properties_dialog.validate = self.properties_dialog_validate
+
+ def properties_dialog_validate(self):
+ '''
+ The ending function of the experiment properties dialog.
+ The changed data are stored in the experiment
+ '''
+ # We redefine this function to catch the final signal from the window
+ self.experiment.author = self.properties_dialog.author
+ self.experiment.comment = self.properties_dialog.comment
+ self.experiment.parameters['injection_index'] = \
+ int(self.properties_dialog.inject_index[0])
+ self.experiment.set_parameters('spring_constant',
+ self.properties_dialog.spring_cst)
+ self.experiment.set_parameters('glass',
+ self.properties_dialog.stiff_glass)
+ self.experiment.set_parameters('plot_size',
+ self.properties_dialog.save_size)
+ gprop = self.properties_dialog.group
+ for key in ['label', 'display']:
+ self.experiment.set_group_prop(key, -1, gprop[key])
+ self.config['linewidth'] = self.properties_dialog.linewidth
+ self.experiment.parameters['histo_y_rel'] = \
+ self.properties_dialog.histo_y_rel
+ self.experiment.set_parameters('plot_hist_gauss',
+ self.properties_dialog.plot_hist_gauss)
+ self.plot_properties.color_type = self.properties_dialog.plot_color
+ self.properties_dialog.destroy()
+ self.update('plots')
+ ##
+
+ ####
+ ## Group
+ def on_group_activate(self, widget):
+ """
+ Manage the groups.
+ """
+
+ if DEBUG:
+ print "Activate group. Called from :"
+ print widget
+ win = FileGroup(self.experiment.file['name'],
+ self.experiment.file['group'])
+ result = win.run()
+ if result is not None:
+ self.experiment.set_group(result)
+ win.destroy()
+ ##
+
+ ###############
+ ## Option Menu
+ def on_apply_on_all_toggled(self, widget):
+ """
+ Switch the "apply_on_all" state.
+ """
+
+ self.apply_on_all = widget.get_active()
+ self.experiment.set_switch('all', widget.get_active())
+ self.update('gui')
+
+ def on_show_header_activate(self, widget):
+ """
+ Show the current FV header in a window.
+ """
+
+ if DEBUG:
+ print "Show header. Called from :"
+ print widget
+ if self.force_volume is None:
+ # Stop execution here if no files loaded
+ display_error('no_fv')
+ return
+ treestore = create_header_tree(self.force_volume.header)
+ treeview = gtk.TreeView(treestore)
+ tvcolumn = gtk.TreeViewColumn('File Header')
+ treeview.append_column(tvcolumn)
+ cell = gtk.CellRendererText()
+ tvcolumn.pack_start(cell, True)
+ tvcolumn.add_attribute(cell, 'text', 0)
+ treeview.set_search_column(0)
+ tvcolumn.set_sort_column_id(0)
+ treeview.set_reorderable(True)
+
+ # Create a new window
+ window = gtk.Window(gtk.WINDOW_TOPLEVEL)
+ window.set_title('File Header')
+
+ window.set_size_request(200, 200)
+
+ #window.connect("delete_event", self.delete_event)
+ window.add(treeview)
+ window.show_all()
+
+ def on_flatten_activate(self, widget):
+ """
+ Open the flatten dialog
+ """
+ self.flatten_dialog = FlattenDialog(
+ self.force_volume.get_array('Piezo', raw=True))
+ new_array = self.flatten_dialog.run()
+ self.force_volume.set_array('Piezo', new_array)
+ self.flatten_dialog.destroy()
+
+ ###########################################################################
+ ## Display tab
+ def on_button_up_clicked(self, widget):
+ """
+ Navigate in the array to the upper FC.
+ """
+ if self.force_volume is None:
+ # Stop execution here if no files loaded
+ display_error('no_fv')
+ return
+ self.update_stiffness_param(self.force_volume, widget)
+ self.force_volume.go_up()
+ self.plot_curve()
+ self.update('show gride position')
+
+ def on_button_down_clicked(self, widget):
+ """
+ Navigat in the array to the down FC.
+ """
+
+ if self.force_volume is None:
+ # Stop execution here if no files loaded
+ display_error('no_fv')
+ return
+ self.update_stiffness_param(self.force_volume, widget)
+ self.force_volume.go_down()
+ self.plot_curve()
+ self.update('show gride position')
+
+ def on_button_next_clicked(self, widget):
+ """
+ Navigate in the array to the next FC.
+ """
+ if self.force_volume is None:
+ # Stop execution here if no files loaded
+ display_error('no_fv')
+ return
+ self.update_stiffness_param(self.force_volume, widget)
+ try:
+ self.force_volume.go_next()
+ except StopIteration:
+ pass
+ self.plot_curve()
+ self.update('show gride position')
+
+ def on_button_prev_clicked(self, widget):
+ """
+ Navigate in the array to the previous FC.
+ """
+ if self.force_volume is None:
+ # Stop execution here if no files loaded
+ display_error('no_fv')
+ return
+ self.update_stiffness_param(self.force_volume, widget)
+ self.force_volume.go_prev()
+ self.plot_curve()
+ self.update('show gride position')
+
+ def on_list_file_combo_box_changed(self, signal):
+ """
+ Change the current FV scan according to the list change.
+ """
+
+ self.force_volume = self.experiment.get_file(signal.get_active())
+ self.update('fv display prop')
+ self.update('gui')
+
+ def on_check_display_fc_trace_clicked(self, widget):
+ """
+ Switch the display of approach curve.
+ """
+ if self.force_volume is None:
+ # Stop execution here if no files loaded
+ display_error('no_fv')
+ return
+ if widget.get_active():
+ self.force_volume.set_to_plot('Av', 1)
+ else:
+ self.force_volume.set_to_plot('Av', 0)
+ if not self.force_volume.to_plot('Re'):
+ self.win_plot['Curve Plot'].on_win_plot_destroy()
+ self.plot_curve()
+
+ def on_check_display_fc_retrace_clicked(self, widget):
+ """
+ Switch the display of retraction curve.
+ """
+ if self.force_volume is None:
+ # Stop execution here if no files loaded
+ display_error('no_fv')
+ return
+ if widget.get_active():
+ self.force_volume.set_to_plot('Re', 1)
+ else:
+ self.force_volume.set_to_plot('Re', 0)
+ if not self.force_volume.to_plot('Av'):
+ self.win_plot['Curve Plot'].on_win_plot_destroy()
+ self.plot_curve()
+
+ def on_check_display_poc_clicked(self, widget):
+ """
+ Switch the display of point of contact on approach curve.
+ """
+ if self.force_volume is None:
+ # Stop execution here if no files loaded
+ display_error('no_fv')
+ return
+ if widget.get_active():
+ self.force_volume.set_to_plot('PoC', 1)
+ else:
+ self.force_volume.set_to_plot('PoC', 0)
+ self.plot_curve()
+
+ def on_check_display_event_clicked(self, widget):
+ """
+ Switch the display of event on retraction curve.
+ """
+ if self.force_volume is None:
+ # Stop execution here if no files loaded
+ display_error('no_fv')
+ return
+ if widget.get_active():
+ self.force_volume.set_to_plot('Event', 1)
+ else:
+ self.force_volume.set_to_plot('Event', 0)
+ self.plot_curve()
+
+ def on_check_display_indent_clicked(self, widget):
+ """
+ Switch the display of indentation curve.
+ """
+ if self.force_volume is None:
+ # Stop execution here if no files loaded
+ display_error('no_fv')
+ return
+ if widget.get_active():
+ self.force_volume.set_to_plot('Indent', 1)
+ else:
+ self.force_volume.set_to_plot('Indent', 0)
+ self.win_plot['Indent Plot'].on_win_plot_destroy()
+ self.plot_curve()
+
+ def on_button_piezzo_array_clicked(self, widget):
+ """
+ Display the piezo array.
+ """
+ if self.force_volume is None:
+ display_error('no_fv')
+ return
+ try:
+ array = self.force_volume.get_array('Piezo')
+ except NotReadyError:
+ answer = ask_message(['Piezo array not loaded',
+ 'Piezo array is not loaded.\n' +
+ 'Do you want to load it ?\n\n' +
+ 'This can take minutes...\n\n' +
+ 'Note that if you compute the stiffness\n' +
+ 'Piezo array is automagically loaded.'])
+ if answer == gtk.RESPONSE_YES:
+ self.force_volume.force_piezo_loading()
+ array = self.force_volume.get_array('Piezo')
+ elif answer == gtk.RESPONSE_NO:
+ return
+ if not exist(self.win_plot['Piezo Height']):
+ [window, widget] = self.regenerate_plot()
+ self.win_plot['Piezo Height'] = plot_gtk.plotGride(
+ window, widget,
+ title=PLOT_WINDOW['piezo array'][2],
+ win_title=PLOT_WINDOW['piezo array'][1])
+ self.win_plot['Piezo Height'].figure.canvas.mpl_connect(
+ 'button_press_event',
+ self.on_gride_click)
+ self.win_plot['Piezo Height'].figure.canvas.mpl_connect(
+ 'motion_notify_event',
+ self.on_gride_motion)
+ self.win_plot['Piezo Height'].set_size(
+ self.experiment.parameters['plot_size'])
+ self.win_plot['Piezo Height'].plot(
+ array,
+ self.force_volume.header['scan_size'],
+ cmap=self.plot_properties.get_cmap('Piezo'))
+
+ def on_button_topography_array_clicked(self, widget):
+ """
+ Display the topography (zero-force) array.
+ """
+ if self.force_volume is None:
+ display_error('no_fv')
+ return
+ if self.force_volume.get_array('Topography') is None:
+ display_error('no_compute', 'topography')
+ return
+ if not exist(self.win_plot['Topography Map']):
+ [window, widget] = self.regenerate_plot()
+ self.win_plot['Topography Map'] = plot_gtk.plotGride(
+ window, widget,
+ title=PLOT_WINDOW['topography array'][2],
+ win_title=PLOT_WINDOW['topography array'][1])
+ self.win_plot['Topography Map'].figure.canvas.mpl_connect(
+ 'button_press_event',
+ self.on_gride_click)
+ self.win_plot['Topography Map'].figure.canvas.mpl_connect(
+ 'motion_notify_event',
+ self.on_gride_motion)
+ self.win_plot['Topography Map'].set_size(
+ self.experiment.parameters['plot_size'])
+ self.win_plot['Topography Map'].plot(
+ self.force_volume.get_array('Topography'),
+ self.force_volume.header['scan_size'],
+ cmap=self.plot_properties.get_cmap('Topography'))
+
+ def on_button_stiffness_array_clicked(self, widget):
+ """
+ Display the stiffness array.
+ """
+ if self.force_volume is None:
+ display_error('no_fv')
+ return
+ if not self.force_volume.has_stiffness:
+ display_error('no_compute', 'stiffness')
+ return
+ if not exist(self.win_plot['Stiffness Map']):
+ [window, widget] = self.regenerate_plot()
+ self.win_plot['Stiffness Map'] = plot_gtk.plotGride(
+ window, widget,
+ title=PLOT_WINDOW['stiffness array'][2],
+ win_title=PLOT_WINDOW['stiffness array'][1])
+ self.win_plot['Stiffness Map'].figure.canvas.mpl_connect(
+ 'button_press_event',
+ self.on_gride_click)
+ self.win_plot['Stiffness Map'].figure.canvas.mpl_connect(
+ 'motion_notify_event',
+ self.on_gride_motion)
+ self.win_plot['Stiffness Map'].set_size(
+ self.experiment.parameters['plot_size'])
+ self.win_plot['Stiffness Map'].plot(
+ self.force_volume.get_array('Stiffness'),
+ self.force_volume.header['scan_size'],
+ cmap=self.plot_properties.get_cmap('Stiffness'))
+
+ def on_button_event_array_clicked(self, widget):
+ """
+ Display the event number array.
+ """
+ if self.force_volume is None:
+ display_error('no_fv')
+ return
+ if self.force_volume.get_array('Event') is None:
+ display_error('no_compute', 'event')
+ return
+ if not exist(self.win_plot['Event Map']):
+ [window, widget] = self.regenerate_plot()
+ self.win_plot['Event Map'] = plot_gtk.plotGride(
+ window, widget,
+ title=PLOT_WINDOW['ev_pos array'][2],
+ win_title=PLOT_WINDOW['ev_pos array'][1])
+ self.win_plot['Event Map'].figure.canvas.mpl_connect(
+ 'button_press_event',
+ self.on_gride_click)
+ self.win_plot['Event Map'].set_size(
+ self.experiment.parameters['plot_size'])
+ self.win_plot['Event Map'].plot(
+ self.force_volume.get_array('Event'),
+ self.force_volume.header['scan_size'],
+ cmap=self.plot_properties.get_cmap('Event'))
+
+ def on_button_event_force_array_clicked(self, widget):
+ """
+ Display the event force array.
+ """
+ if self.force_volume is None:
+ display_error('no_fv')
+ return
+ if self.force_volume.get_array('Event force') is None:
+ display_error('no_compute', 'event')
+ return
+ if not exist(self.win_plot['Event Force Map']):
+ [window, widget] = self.regenerate_plot()
+ self.win_plot['Event Force Map'] = plot_gtk.plotGride(
+ window, widget,
+ title=PLOT_WINDOW['ev_force array'][2],
+ win_title=PLOT_WINDOW['ev_force array'][1])
+ self.win_plot['Event Force Map'].figure.canvas.mpl_connect(
+ 'button_press_event',
+ self.on_gride_click)
+ self.win_plot['Event Force Map'].set_size(
+ self.experiment.parameters['plot_size'])
+ self.win_plot['Event Force Map'].plot(
+ self.force_volume.get_array('Event force'),
+ self.force_volume.header['scan_size'],
+ cmap=self.plot_properties.get_cmap('Event force'))
+
+ def on_button_event_dist_array_clicked(self, widget):
+ """
+ Display the event force array.
+ """
+ if self.force_volume is None:
+ display_error('no_fv')
+ return
+ if self.force_volume.get_array('Event distance') is None:
+ display_error('no_compute', 'event distance')
+ return
+ if not exist(self.win_plot['Event Distance Map']):
+ [window, widget] = self.regenerate_plot()
+ self.win_plot['Event Distance Map'] = plot_gtk.plotGride(
+ window, widget,
+ title=PLOT_WINDOW['ev_dist array'][2],
+ win_title=PLOT_WINDOW['ev_dist array'][1])
+ self.win_plot['Event Distance Map'].figure.canvas.mpl_connect(
+ 'button_press_event',
+ self.on_gride_click)
+ self.win_plot['Event Distance Map'].set_size(
+ self.experiment.parameters['plot_size'])
+ self.win_plot['Event Distance Map'].plot(
+ self.force_volume.get_array('Event distance'),
+ self.force_volume.header['scan_size'],
+ cmap=self.plot_properties.get_cmap('Event distance'))
+
+ def on_button_event_length_array_clicked(self, widget):
+ """
+ Display the event number array.
+ """
+ if self.force_volume is None:
+ display_error('no_fv')
+ return
+ if self.force_volume.get_array('Event length') is None:
+ display_error('no_compute', 'event length')
+ return
+ if not exist(self.win_plot['Event Length Map']):
+ [window, widget] = self.regenerate_plot()
+ self.win_plot['Event Length Map'] = plot_gtk.plotGride(
+ window, widget,
+ title=PLOT_WINDOW['ev_len array'][2],
+ win_title=PLOT_WINDOW['ev_len array'][1])
+ self.win_plot['Event Length Map'].figure.canvas.mpl_connect(
+ 'button_press_event',
+ self.on_gride_click)
+ self.win_plot['Event Length Map'].set_size(
+ self.experiment.parameters['plot_size'])
+ self.win_plot['Event Length Map'].plot(
+ self.force_volume.get_array('Event length'),
+ self.force_volume.header['scan_size'],
+ cmap=self.plot_properties.get_cmap('Event length'))
+
+ def on_button_stiffness_tomo_clicked(self, widget):
+ """
+ Dispaly the stiffness tomography slices in x-y-z.
+ """
+ if self.force_volume is None:
+ display_error('no_fv')
+ return
+ if not self.force_volume.has_stiffness:
+ display_error('no_compute', 'stiffness')
+ return
+ self.force_volume.stiffness_tomography_array()
+ if not exist(self.win_plot['Stiffness Tomo']):
+ [window, widget] = self.regenerate_plot('tomography')
+ self.win_plot['Stiffness Tomo'] = plot_gtk.plotTomo(
+ window, widget,
+ self.force_volume.get_array('Stiffness tomo'),
+ scan_size=self.force_volume.header['scan_size'],
+ title='Stiffness Tomo')
+ self.win_plot['Stiffness Tomo'].set_size(
+ self.experiment.parameters['plot_size'])
+ self.win_plot['Stiffness Tomo'].get_pos = \
+ self.on_stiffness_tomo_window_clicked
+
+ def on_stiffness_tomo_window_clicked(self):
+ """
+ When clicking on the stiffness tomography window, it goes to the
+ corresponding force-distance curve.
+ """
+ if self.force_volume is None:
+ display_error('no_fv')
+ return
+ self.force_volume.go_to(self.win_plot['Stiffness Tomo'].slice[0],
+ self.win_plot['Stiffness Tomo'].slice[1])
+ self.update('show gride position')
+ self.plot_curve()
+
+ def on_button_mean_stiffness_tomo_clicked(self, widget):
+ """
+ Distplay the mean stiffness tomography.
+ """
+ if self.force_volume is None:
+ display_error('no_fv')
+ return
+ if not self.force_volume.has_stiffness:
+ display_error('no_compute', 'stiffness')
+ return
+ if not exist(self.win_plot['Average Stiffness Tomo']):
+ [window, widget] = self.regenerate_plot()
+ self.win_plot['Average Stiffness Tomo'] = plot_gtk.errorBar(
+ window, widget)
+
+ if self.apply_on_all:
+ mean_tomo = self.experiment.average_stiffness
+ self.win_plot['Average Stiffness Tomo'].average_stiffness(
+ mean_tomo,
+ group=self.experiment.file['group'],
+ group_info=self.experiment.group)
+ else:
+ mean_tomo = self.force_volume.compute_average_stiffness()
+ self.win_plot['Average Stiffness Tomo'].average_stiffness(
+ mean_tomo)
+ self.win_plot['Average Stiffness Tomo'].set_size(
+ self.experiment.parameters['plot_size'])
+
+ ## Mosaics
+ def mosaic_clicked(self, atype, widget=None):
+ """
+ Generic function to display mosaic view.
+ """
+ if self.force_volume is None:
+ display_error('no_fv')
+ return
+ if DEBUG:
+ print "Mosaic plot. Called from :"
+ print widget
+ array_list = self.experiment.get_arrays(atype)
+ mask_list = self.experiment.get_masked_list()
+ # MOSAIC_PLOT is a dict which contains the values for mosaic name :
+ # mosaic_name[0] = name of the window plot
+ # mosaic_name[1] = title of the window
+ # mosaic_name[2] = title of the plot
+ # mosaic_name[3] = cmap
+ mosaic_name = MOSAIC_PLOT[atype]
+ if not exist(self.win_plot[mosaic_name[0]]):
+ [window, widget] = self.regenerate_plot()
+ self.win_plot[mosaic_name[0]] = plot_gtk.plotMosaic(
+ window, widget,
+ title=mosaic_name[1])
+ self.win_plot[mosaic_name[0]].figure.canvas.mpl_connect(
+ 'button_press_event',
+ self.on_mosaic_click)
+ self.win_plot[mosaic_name[0]].figure.canvas.mpl_connect(
+ 'pick_event',
+ self.on_mosaic_pick)
+ self.win_plot[mosaic_name[0]].set_size(
+ self.experiment.parameters['plot_size'])
+ self.win_plot[mosaic_name[0]].plot(array_list,
+ title=mosaic_name[2],
+ list_name=self.experiment.file['name'],
+ cmap=mosaic_name[3],
+ list_mask=mask_list)
+
+ def on_button_mosaic_piezo_clicked(self, widget):
+ """
+ Display the mosaic view of piezo image.
+ """
+ self.mosaic_clicked('Piezo', widget)
+
+ def on_button_mosaic_topography_clicked(self, widget):
+ """
+ Display the mosaic view of topography.
+ """
+ self.mosaic_clicked('Topography', widget)
+
+ def on_button_mosaic_stiffness_clicked(self, widget):
+ """
+ Display the mosaic view of stiffness.
+ """
+ self.mosaic_clicked('Stiffness', widget)
+
+ def on_button_mosaic_event_clicked(self, widget):
+ """
+ Display the mosaic view of event nbr.
+ """
+ self.mosaic_clicked('Event', widget)
+
+ def on_button_mosaic_event_force_clicked(self, widget):
+ """
+ Display the mosaic view of event force.
+ """
+ self.mosaic_clicked('Event force', widget)
+
+ def on_button_opengl_clicked(self, widget):
+ """
+ Display the stiffness tomography using OpenGL.
+ """
+ if DEBUG:
+ print "OpenGL. Called from :"
+ print widget
+ self.force_volume.stiffness_tomography_array()
+ # get the min and max values from the tomography display :
+ if exist(self.win_plot['Stiffness Tomo']):
+ clut = (self.win_plot['Stiffness Tomo'].colorScaleMin,
+ self.win_plot['Stiffness Tomo'].colorScaleMax)
+ else:
+ clut = None
+ topogl = TopoGL(self.force_volume.get_array('Stiffness tomo'),
+ clut=clut)
+ topogl.run()
+
+ ###########################################################################
+ ## Compute tab
+ def update_stiffness_param(self, force_volume, widget=None):
+ """
+ Update the stiffness parameters.
+ """
+ if DEBUG:
+ print "Updating stiffness parameters. Called by :"
+ print widget
+ # Update the Hertz model and the tip caracteristics.
+ _hertz_model = self.widget['combo_hertz_model'].get_active_text()
+ _stiff_method = self.widget['combo_stiff_method'].get_active_text()
+ _tip_carac = self.widget['spin_tip_carac'].get_value()
+ force_volume.change_model(model=_hertz_model, carac=_tip_carac)
+ force_volume.set_parameters('stiffness_fit_method', _stiff_method)
+ # Update the segmentations parameters.
+ force_volume.change_segment_number(
+ self.widget['entry_number_deep'].get_text())
+ force_volume.change_segment_depth(
+ self.widget['entry_size_deep'].get_text())
+ # Update the point of contact detection algorythm.
+ _poc_model = self.widget['combo_poc_method'].get_active_text()
+ force_volume.stiffness['poc_method'] = _poc_model
+ _limit_poc_slide = self.widget['check_limit_poc_slide'].get_active()
+ force_volume.stiffness['limit_slide'] = _limit_poc_slide
+ _fit_seg_size = self.widget['spin_fit_seg_size'].get_value()
+ force_volume.change_poc_seg_size(_fit_seg_size)
+ # Update whether to recompute or not.
+ _recompute_poc = self.widget['check_compute_poc'].get_active()
+ force_volume.stiffness['recompute_poc'] = _recompute_poc
+
+ def on_button_compute_stiffness_clicked(self, widget):
+ """
+ Launch the stiffness computation.
+ """
+ if DEBUG:
+ print "Compute stiffness. Called from :"
+ print widget
+ if self.apply_on_all:
+ number_of_files = len(self.experiment.file['list'])
+ this_file_nb = 0
+ for force_volume, file_name in zip(self.experiment.file['list'],
+ self.experiment.file['name']):
+ counter_begin = float(this_file_nb) / number_of_files
+ counter_end = float(this_file_nb + 1) / number_of_files
+ counter_text = " of file " + file_name
+ self._compute_stiffness(force_volume,
+ counter_begin,
+ counter_end,
+ counter_text)
+ this_file_nb += 1
+ else:
+ self._compute_stiffness(self.force_volume)
+ # updates the histogram tabs according to the latest computations.
+ self.update('hist entry')
+ self.display_progressbar(None)
+
+ def on_check_compute_poc_toggled(self, widget):
+ """
+ Change the state of forcing the detection of point of contact.
+ """
+ if self.force_volume is not None:
+ self.update_stiffness_param(self.force_volume, widget)
+
+ def _compute_stiffness(self, force_volume, counter_begin=0,
+ counter_end=1, counter_text=''):
+ """
+ Compute the stiffness. This is for one file.
+ """
+ # Update the computation properties
+ self.update_stiffness_param(force_volume)
+ # Start the computation
+ _thread = threading.Thread(target=force_volume.compute_stiffness)
+ _thread.start()
+ while _thread.isAlive():
+ self.display_progressbar(counter_begin +
+ float(force_volume.counter['num']) *
+ (counter_end - counter_begin),
+ force_volume.counter['text'] + counter_text)
+ time.sleep(0.1)
+ force_volume.counter['num'] = 0
+
+ def update_evt_parameters(self, force_volume):
+ evt_fit_model = self.widget['combo_ev_fit_model'].get_active_text()
+ if evt_fit_model == 'None':
+ evt_fit_model = None
+ force_volume.set_parameters('evt_fit', evt_fit_model)
+ force_volume.header['event_detect_weight'] = \
+ self.experiment.parameters['event_detect_weight']
+
+ def on_button_detect_events_clicked(self, widget):
+ """
+ Launch the event detection.
+ """
+ if DEBUG:
+ print "Detect events. Called from :"
+ print widget
+ if self.apply_on_all:
+ number_of_files = len(self.experiment.file['list'])
+ this_file_nb = 0
+ for force_volume, file_name in zip(self.experiment.file['list'],
+ self.experiment.file['name']):
+ counter_begin = float(this_file_nb) / number_of_files
+ counter_end = float(this_file_nb + 1) / number_of_files
+ counter_text = " of file " + file_name
+ self._compute_evt(force_volume,
+ counter_begin,
+ counter_end,
+ counter_text)
+ this_file_nb += 1
+ else:
+ self._compute_evt(self.force_volume)
+ self.update('hist entry')
+ self.display_progressbar(None)
+
+ def _compute_evt(self, force_volume, counter_begin=0,
+ counter_end=1, counter_text=''):
+ """
+ Compute the stiffness. This is for one file.
+ """
+ # Update the computation properties
+ self.update_evt_parameters(force_volume)
+ # Start the computation
+ _thread = threading.Thread(target=force_volume.compute_events)
+ _thread.start()
+ while _thread.isAlive():
+ self.display_progressbar(counter_begin +
+ float(force_volume.counter['num']) *
+ (counter_end - counter_begin),
+ force_volume.counter['text'] + counter_text)
+ time.sleep(0.1)
+ force_volume.counter['num'] = 0
+
+ def on_combo_ev_fit_model_changed(self, widget):
+ if self.force_volume is not None:
+ self.experiment.parameters['event_fit_model'] = \
+ self.widget['combo_ev_fit_model'].get_active_text()
+ self.plot_curve()
+
+ def on_button_regenerate_random_clicked(self, widget):
+ """
+ Regenerate the random event array.
+ """
+ if DEBUG:
+ print "Regenerate random. Called from :"
+ print widget
+ nbr_rand_event = self.widget['spin_nbr_rand_event'].get_value()
+ if self.apply_on_all:
+ for force_volume in self.experiment.file['list']:
+ force_volume.generate_random_event(nbr_rand_event)
+ else:
+ self.force_volume.generate_random_event(nbr_rand_event)
+
+ def on_button_compute_rel_stiffness_clicked(self, widget):
+ """
+ Compute the relative stiffness
+ """
+ if DEBUG:
+ print "Compute rel stiff. Called from :"
+ print widget
+ self.experiment.parameters['rel_stiff_dist'] = \
+ int(self.widget['spin_max_rel_stiffness'].get_value())
+ if self.apply_on_all:
+ self.experiment.compute_relative_stiffness()
+ else:
+ self.force_volume.change_relative_distance(
+ int(self.experiment.parameters['rel_stiff_dist']))
+ self.force_volume.compute_relative_stiffness()
+
+ ###########################################################################
+ ## Parameter tab
+ def on_spin_poisson_ratio_value_changed(self, widget):
+ """
+ Change the poisson ratio of the material.
+ """
+ if self.experiment is not None:
+ self.experiment.set_parameters('poisson_ratio', widget.get_value())
+
+ def on_button_event_calib_clicked(self, widget):
+ """
+ Modify the calibration of the event detection.
+ """
+
+ if DEBUG:
+ print "Calib event. Called from :"
+ print widget
+ # Turn on the "Retraction force curve" and "Event on curve display"
+ self.widget['check_display_fc_retrace'].set_active(True)
+ self.force_volume.set_to_plot('Re', 1)
+ self.widget['check_display_event'].set_active(True)
+ self.force_volume.set_to_plot('Event', 1)
+ self.modify_window('Curve Plot', 'Event detection')
+ self.plot_curve()
+
+ def on_spin_ev_threshold_dist_value_changed(self, widget):
+ """
+ Change the event filter based on its distance on the curve.
+ """
+ self.experiment.set_switch('ev_dist', widget.get_value())
+ adjustment = widget.get_adjustment()
+ adjustment.set_upper(widget.get_value() + 1000)
+ self.update_switch()
+
+ def on_spin_ev_length_thresh_min_value_changed(self, widget):
+ """
+ Change the event filter based on its minimum length computed from
+ the fit.
+ """
+ self.update_ev_length_thresh()
+ adjustment = widget.get_adjustment()
+ adjustment.set_upper(widget.get_value() + 1000)
+
+ def on_spin_ev_length_thresh_max_value_changed(self, widget):
+ """
+ Change the event filter based on its maximum length computed from
+ the fit.
+ """
+ self.update_ev_length_thresh()
+ adjustment = widget.get_adjustment()
+ adjustment.set_upper(widget.get_value() + 1000)
+
+ def update_ev_length_thresh(self):
+ """
+ Update the threshold fit based on the length computed from the fit.
+ """
+ min_length = self.widget['spin_ev_length_thresh_min'].get_value()
+ max_length = self.widget['spin_ev_length_thresh_max'].get_value()
+ self.experiment.set_switch('ev_fit_length', [min_length, max_length])
+
+ def on_button_poc_calib_clicked(self, widget):
+ """
+ Change the calibration of the point of contact detection
+ """
+ if DEBUG:
+ print "Calib poc. Called from :"
+ print widget
+ # Turn on the "Approach force curve" and "PoC on curve display"
+ self.widget['check_display_fc_trace'].set_active(True)
+ self.force_volume.set_to_plot('Av', 1)
+ self.widget['check_display_poc'].set_active(True)
+ self.force_volume.set_to_plot('PoC', 1)
+ self.modify_window('Curve Plot', 'PoC detection')
+ self.plot_curve()
+
+ def on_combo_poc_method_changed(self, widget):
+ """
+ Change the method of the point of contact detection
+ """
+
+ if DEBUG:
+ print "Change poc method. Called from :"
+ print widget
+ if self.force_volume is not None:
+ self.update_stiffness_param(self.force_volume, widget)
+
+ def on_check_limit_poc_slide_toggled(self, widget):
+ """
+ Limits the sliding of point of contact detection.
+ """
+
+ if DEBUG:
+ print "Check limit poc. Called from :"
+ print widget
+ if self.force_volume is not None:
+ self.update_stiffness_param(self.force_volume, widget)
+
+ def on_spin_fit_seg_size_value_changed(self, widget):
+ """
+ Change the segment size of indentation curve
+ """
+ if DEBUG:
+ print "Spin fit seg. Called from :"
+ print widget
+ if self.force_volume is not None:
+ self.update_stiffness_param(self.force_volume)
+ self.plot_curve()
+ ##
+
+ #############################################################
+ ## Histograms tab
+ def disp_hist(self, data_type, widget=None):
+ """
+ This is the generic function called to plot the histogram.
+
+ data_type is a string that describes the data you want to plot.
+
+ Can be :
+
+ * 'force' to plot the force.
+ * 'lr' to plot the loading rate
+ * 'dist' to plot the distance of the unbinding
+ * 'stiff' to plot the stiffness on pixel where event occured
+ * 'relstiff' to plot the relative stiffness of pixels where event
+ occured
+ """
+ if DEBUG:
+ print "Disp histogram. Called by :"
+ print widget
+ if self.force_volume is None:
+ return
+ if data_type not in PLOT_LABEL.keys():
+ raise TypeError
+ if widget in [self.widget['spin_ev_force_hist_max'],
+ self.widget['spin_ev_lr_hist_max'],
+ self.widget['spin_ev_length_hist_max'],
+ self.widget['spin_ev_plength_hist_max'],
+ self.widget['spin_ev_stiff_hist_max'],
+ self.widget['spin_ev_relstiff_hist_max']]:
+ curr_val = widget.get_value()
+ widget.set_range(0, curr_val * 10)
+ # Set the label of the plot
+ [title, xlabel, window_name] = PLOT_LABEL[data_type]
+ # Set the range and type of the plot
+ # data_hist is a PlotData object that stores the plot properties
+ self.experiment.data_hist.set_range(data_type,
+ self.get_histo_range(data_type))
+ self.experiment.data_hist.set_type(data_type)
+ # We have a special case for the stiffness where we can compare event
+ # versus non event. We check thank's to a checkbox :
+ stiff_case = self.widget['check_stiff_ev_vs_notev'].get_active() and \
+ (data_type == 'stiff')
+ if self.apply_on_all or self.plot_properties.plot_compare:
+ # When we compare different experiments, we take the whole files
+ if stiff_case:
+ [event_att, event_group, fid, coord] = \
+ self.experiment.get_event_att('stiff_ev')
+ [noevent_att, noevent_group, noevent_fid, noevent_coord] = \
+ self.experiment.get_event_att('stiff_notev')
+ else:
+ [event_att, event_group, fid, coord] = \
+ self.experiment.get_event_att(data_type)
+ self.experiment.data_hist.set_data(data_type, event_att)
+ self.experiment.data_hist.set_group(data_type, event_group)
+ self.experiment.data_hist.nbr_curves = \
+ self.experiment.parameters['number_curves']
+ if self.plot_properties.plot_compare:
+ self.experiment_compare.data_hist.set_range(data_type,
+ self.get_histo_range(data_type))
+ self.experiment_compare.data_hist.nbr_curves = \
+ self.experiment_compare.parameters['number_curves']
+ self.experiment_compare.data_hist.set_type(data_type)
+ self.experiment_compare.data_hist.set_data(data_type,
+ self.experiment_compare.get_event_att(data_type))
+ self.experiment_compare.event_gauss_fit(data_type)
+ else:
+ if stiff_case:
+ event_att = self.force_volume.get_event_prop('stiff_ev')
+ noevent_att = self.force_volume.get_event_prop('stiff_notev')
+ else:
+ # Reinitialize the group, because the number of event can
+ # change.
+ self.experiment.data_hist.set_group(data_type, None)
+ try:
+ data = self.force_volume.get_event_prop(data_type)
+ except TypeError:
+ display_error('no_compute', data_type)
+ return
+ self.experiment.data_hist.set_data(data_type, data)
+ self.experiment.data_hist.nbr_curves = \
+ self.force_volume.header['number_curves']
+ #self.experiment.event_gauss_fit(data_type)
+ #ev_prop = self.experiment.event_stat[data_type]
+ ### Set the groups.
+ if self.apply_on_all and not self.plot_properties.plot_compare:
+ # We put the group properties to the histogram.
+ self.experiment.data_hist.set_group_prop('all',
+ self.experiment.group)
+ if not exist(self.win_plot[window_name]):
+ [window, widget] = self.regenerate_plot()
+ self.win_plot[window_name] = plot_gtk.plotHist(
+ window, widget,
+ title=title,
+ xlabel=xlabel,
+ y_rel=self.experiment.parameters['histo_y_rel'])
+ if not self.plot_properties.plot_compare:
+ if stiff_case:
+ self.win_plot[window_name].hist(event_att, noevent_att,
+ label='event', label2='not event',
+ hist_range=self.get_histo_range(data_type),
+ y2=True)
+ else:
+ try:
+ self.win_plot[window_name].hist(self.experiment.data_hist)
+ except IndexError:
+ pass
+ else:
+ if self.experiment.parameters['plot_hist_gauss']:
+ self.experiment.event_gauss_fit(data_type)
+ self.win_plot[window_name].gauss(
+ self.experiment.event_stat[data_type])
+ else:
+ # we compare two experiments :
+ self.win_plot[window_name].hist(
+ self.experiment.data_hist,
+ self.experiment_compare.data_hist,
+ label=self.plot_properties.main_exp_label,
+ label2=self.plot_properties.comp_exp_label,
+ color_type=self.plot_properties.color_type)
+ # Set the size of the window...
+ self.win_plot[window_name].set_size(
+ self.experiment.parameters['plot_size'])
+
+ def on_button_ev_force_hist_clicked(self, widget):
+ """
+ Display the histogram of force.
+ """
+ self.disp_hist('force', widget)
+
+ def on_button_ev_lr_hist_clicked(self, widget):
+ """
+ Display the histogram of loading rate.
+ """
+ self.disp_hist('lr', widget)
+
+ def on_button_ev_dist_hist_clicked(self, widget=None):
+ """
+ Display the histogram of distance.
+ """
+ self.disp_hist('dist', widget)
+
+ def on_button_ev_stiff_hist_clicked(self, widget):
+ """
+ Display the histogram of stiffness.
+ """
+ self.disp_hist('stiff', widget)
+
+ def on_button_ev_relstiff_hist_clicked(self, widget):
+ """
+ Display the histogram of relative stiffness.
+ """
+ self.disp_hist('relstiff', widget)
+
+ def on_button_ev_length_hist_clicked(self, widget):
+ """
+ Display the histogram of relative stiffness.
+ """
+ self.disp_hist('length', widget)
+
+ def on_button_ev_plength_hist_clicked(self, widget):
+ """
+ Display the histogram of relative stiffness.
+ """
+ self.disp_hist('plength', widget)
+
+ def disp_scatter(self, data_type, widget=None):
+ """
+ The generic function to display the scatter plot.
+ """
+ if DEBUG:
+ print "Disp scatter. Called by :"
+ print widget
+ self.experiment.data_hist.set_range(data_type[0],
+ self.get_histo_range(data_type[0]))
+ self.experiment.data_hist.set_range(data_type[1],
+ self.get_histo_range(data_type[1]))
+ if self.apply_on_all:
+ [data, group, fid, coord] = \
+ self.experiment.get_event_att(data_type[0])
+ del fid, coord
+ self.experiment.data_hist.set_data(data_type[0], data)
+ self.experiment.data_hist.set_group(data_type[0], group)
+ [data, group, fid, coord] = \
+ self.experiment.get_event_att(data_type[1])
+ self.experiment.data_hist.set_data(data_type[1], data)
+ self.experiment.data_hist.set_group(data_type[1], group)
+ self.experiment.data_hist.nbr_curves = \
+ self.experiment.parameters['number_curves']
+ # We put the group properties to the histogram.
+ self.experiment.data_hist.set_group_prop('all',
+ self.experiment.group)
+ else:
+ self.experiment.data_hist.set_group(data_type[0], None)
+ self.experiment.data_hist.set_group(data_type[1], None)
+ self.experiment.data_hist.set_data(data_type[0],
+ self.force_volume.get_event_prop(data_type[0]))
+ self.experiment.data_hist.set_data(data_type[1],
+ self.force_volume.get_event_prop(data_type[1]))
+ self.experiment.data_hist.nbr_curves = \
+ self.force_volume.header['number_curves']
+ # The labels
+ label_x = PLOT_LABEL[data_type[0]]
+ label_y = PLOT_LABEL[data_type[1]]
+ [plot_name, plot_title] = PLOT_WINDOW[data_type[0] + data_type[1]]
+ if not exist(self.win_plot[plot_name]):
+ [window, widget] = self.regenerate_plot()
+ self.win_plot[plot_name] = plot_gtk.plotScatter(
+ window, widget,
+ title=plot_title,
+ xlabel=label_x[1],
+ ylabel=label_y[1])
+ #if data_type[0] == 'lr':
+ # self.win_plot[plot_name].set_xscale('log')
+ self.win_plot[plot_name].display(self.experiment.data_hist, data_type)
+ self.win_plot[plot_name].set_size(
+ self.experiment.parameters['plot_size'])
+
+ def on_button_scatter_Force_LR_clicked(self, widget):
+ """
+ Display the scatter plot of force vs loading rate.
+ """
+ self.disp_scatter(['force', 'lr'], widget)
+
+ def on_button_scatter_force_dist_clicked(self, widget):
+ """
+ Display the scatter plot of force vs distance.
+ """
+ self.disp_scatter(['dist', 'force'], widget)
+
+ def on_button_scatter_force_stiffness_clicked(self, widget):
+ """
+ Display the scatter plot of force vs stiffness.
+ """
+ self.disp_scatter(['force', 'stiff'], widget)
+
+ def on_button_scatter_LR_Dist_clicked(self, widget):
+ """
+ Display the scatter plot of loading rate vs distance.
+ """
+ self.disp_scatter(['dist', 'lr'], widget)
+
+ def on_button_scatter_LR_Stiffness_clicked(self, widget):
+ """
+ Display the scatter plot of loading rate vs stiffness.
+ """
+ self.disp_scatter(['lr', 'stiff'], widget)
+
+ def on_button_scatter_Dist_Stiffness_clicked(self, widget):
+ """
+ Display the scatter plot of distance vs stiffness.
+ """
+ self.disp_scatter(['dist', 'stiff'], widget)
+
+ def on_button_scatter_Force_RelStiff_clicked(self, widget):
+ """
+ Display the scatter plot of force vs relative stiffness.
+ """
+ self.disp_scatter(['relstiff', 'force'], widget)
+
+ def on_button_scatter_LR_RelStiff_clicked(self, widget):
+ """
+ Display the scatter plot of loading rate vs relative stiffness.
+ """
+ self.disp_scatter(['relstiff', 'lr'], widget)
+
+ def on_button_scatter_Dist_RelStiff_clicked(self, widget):
+ """
+ Display the scatter plot of distance vs rel.stiffness.
+ """
+ self.disp_scatter(['relstiff', 'dist'], widget)
+
+ def get_histo_range(self, d_type):
+ """
+ Get the histogram range of the specified type.
+ """
+ if d_type in ['force', 'lr', 'dist', 'stiff', 'relstiff',
+ 'length', 'plength']:
+ base_name = d_type
+ elif d_type in ['force_base']:
+ base_name = d_type[:-5]
+ min_val = self.widget[
+ 'spin_ev_' + base_name + '_hist_min'].get_value()
+ max_val = self.widget[
+ 'spin_ev_' + base_name + '_hist_max'].get_value()
+ col_size = self.widget[
+ 'spin_ev_' + base_name + '_hist_size'].get_value()
+ if d_type is 'relstiff':
+ dist = int(self.widget['spin_ev_relstiff_hist_dist'].get_value())
+ return [min_val, max_val, col_size, dist]
+ return [min_val, max_val, col_size]
+
+ def on_button_regenerate_event_properties_clicked(self, widget):
+ """
+ Regenerate the event properties.
+ """
+ if DEBUG:
+ print "Regenerate event. Call by :"
+ print widget
+ self.experiment.clear_event_prop_list()
+
+ ###########################################################################
+ ## Plot tab
+ def on_adj_plot_rel_dist_value_changed(self, widget):
+ """
+ Changing the relative distance value for the plot.
+ """
+ self.plot_rel_dist(widget.get_value())
+
+ def plot_rel_dist(self, dist):
+ """
+ Plot the relative stiffness at a specified distance.
+ """
+ # We put the group properties to the histogram.
+ self.experiment.data_hist.set_group_prop('all',
+ self.experiment.group)
+ dist = int(dist)
+ [data, group, fid, coord] = self.experiment.get_event_att('relstiff')
+ del coord
+ self.experiment.data_hist.set_type('relstiff')
+ self.experiment.data_hist.set_data('relstiff', data)
+ self.experiment.data_hist.set_range('relstiff',
+ [None, None, None, dist])
+ self.experiment.data_hist.set_group('relstiff', group)
+ self.experiment.data_hist.set_fid('relstiff', fid)
+ [data, group, fid, coord] = self.experiment.get_event_att(
+ 'relstiffctl')
+ self.experiment.data_hist.set_type('relstiffctl')
+ self.experiment.data_hist.set_data('relstiffctl', data)
+ self.experiment.data_hist.set_range('relstiffctl',
+ [None, None, None, dist])
+ self.experiment.data_hist.set_group('relstiffctl', group)
+ self.experiment.data_hist.set_fid('relstiffctl', fid)
+ if not exist(self.win_plot['TL Rel Stiffness']):
+ [window, widget] = self.regenerate_plot()
+ self.win_plot['TL Rel Stiffness'] = plot_gtk.TimeLapse(
+ window, widget,
+ title='Event relative stiffness',
+ win_title='Relative Stiffness')
+ self.win_plot['TL Rel Stiffness'].displayPlot(
+ self.experiment.data_hist)
+
+ def on_adj_rel_stiff_limite_value_changed(self, widget):
+ """
+ Modification of limit values of relative stiffness.
+ """
+ range_val = widget.get_value()
+ self.experiment.compute_relative_stiffness(exclude=range_val)
+ self.plot_rel_dist(self.widget['spin_rel_stiff_plot_dist'].get_value())
+
+ def on_button_plot_nbr_event_tl_clicked(self, widget):
+ """
+ Plots the number of event per scans.
+ """
+ event_nbr_list = self.experiment.event_nbr_list()
+ _px = range(len(event_nbr_list))
+ if not exist(self.win_plot['TL Nbr Event']):
+ [window, widget] = self.regenerate_plot()
+ self.win_plot['TL Nbr Event'] = plot_gtk.plotCurves(
+ window, widget,
+ xlabel='Time',
+ ylabel='Number of events',
+ title='Number of events',
+ win_title='Number of events')
+ self.win_plot['TL Nbr Event'].ylim = 0
+
+ self.win_plot['TL Nbr Event'].displayPlot(_px, event_nbr_list,
+ linestyle='bo-')
+
+ ###########################################################################
+ ## Tomography tab
+ def on_button_compute_tomo_clicked(self, widget=None):
+ """
+ Computes the stiffness tomography.
+ """
+ if DEBUG:
+ print "Compute tomo. Call from :"
+ print widget
+ self.force_volume.stiffness_tomography_array()
+
+ def on_MainWindow_destroy(self, widget, data=None):
+ '''
+ Destroys the window
+ '''
+ if DEBUG:
+ print "Destroy main window :"
+ print widget, data
+ # saves the preferences...
+ if not(os.path.isdir(os.path.split(self.config['config'])[0])):
+ os.makedirs(os.path.split(self.config['config'])[0])
+ conf_id = open(self.config['config'], 'wb')
+ for keys in self.config.keys():
+ if keys == 'msgid':
+ conf_id.write(keys + ' = ' +
+ self.config[keys].strftime('%Y, %m, %d') + '\n')
+ elif keys in ['linewidth', 'connect-server']:
+ conf_id.write(keys + ' = ' +
+ str(self.config[keys]) + '\n')
+ else:
+ conf_id.write(keys + ' = ' + self.config[keys] + '\n')
+ conf_id.write('')
+ conf_id.close()
+ gtk.main_quit()
+
+ ###########################################################################
+ ## Mechanical prop. tab
+ def hist_mech_prop(self, dtype, depth=0):
+ """
+ Display histogram of mechanical properties.
+ """
+ [title, xlabel, window_name] = PLOT_LABEL[dtype]
+ self.experiment.set_data_hist(dtype, self.get_mech_histo_range(dtype),
+ depth=depth)
+ if not exist(self.win_plot[window_name]):
+ [window, widget] = self.regenerate_plot()
+ self.win_plot[window_name] = plot_gtk.plotHist(
+ window, widget,
+ title=title,
+ xlabel=xlabel,
+ y_rel=self.experiment.parameters['histo_y_rel'])
+ self.win_plot[window_name].hist(self.experiment.data_hist)
+ if self.experiment.parameters['plot_hist_gauss']:
+ self.win_plot[window_name].gauss(
+ self.experiment.event_stat['stiff'])
+ self.win_plot[window_name].set_size(
+ self.experiment.parameters['plot_size'])
+
+ def on_button_young_modulus_hist_clicked(self, widget):
+ """
+ Display histogram of young modulus.
+ """
+ if DEBUG:
+ print "Young modulus :"
+ print widget
+ # Get the depth value :
+ depth = self.widget['spin_ym_hist_depth_nb'].get_value()
+ self.hist_mech_prop('young_modulus', depth=depth)
+
+ def get_mech_histo_range(self, dtype):
+ """
+ Get the range of histogram of mechanical properties.
+ """
+ if dtype in ['young_modulus']:
+ base_name = dtype
+ min_val = self.widget['spin_' + base_name + '_hist_min'].get_value()
+ max_val = self.widget['spin_' + base_name + '_hist_max'].get_value()
+ col_size = self.widget['spin_' + base_name + '_hist_size'].get_value()
+ return [min_val, max_val, col_size]
+
+ ###########################################################################
+ # About dialog
+ def on_about_activate(self, widget):
+ '''
+ Display the about dialog.
+ '''
+ if DEBUG:
+ print "About dialog called from :"
+ print widget
+ self.about_dialog.run()
+ self.about_dialog.hide()
+
+ ###########################################################################
+ # Compare Experiment Dialog
+ def on_compare_exp_activate(self, widget):
+ """
+ Displays the compare experiment dialog
+ """
+ if DEBUG:
+ print "Compare experiment. Call from :"
+ print widget
+ if self.experiment is not None:
+ self.widget_exp_compare['label_main_filename'].set_label(
+ self.experiment.short_name)
+ self.compare_exp_dialog.run()
+ self.compare_exp_dialog.hide()
+
+ def on_open_exp_compare_clicked(self, widget):
+ """
+ Opens the compare experiment
+ """
+ if DEBUG:
+ print "Compare experiment. Call from :"
+ print widget
+ self.temp_exp_compare = self.load_exp()
+ self.widget_exp_compare['comp_exp_filename'].set_label(
+ self.temp_exp_compare.short_name)
+ self.widget_exp_compare['label_comp_filename'].set_label(
+ self.temp_exp_compare.short_name)
+
+ def on_button_compare_exp_validate_clicked(self, widget):
+ """
+ Compare experiments.
+ """
+ if DEBUG:
+ print "validation of compare expexperiment :"
+ print widget
+ self.experiment_compare = self.temp_exp_compare
+ del self.temp_exp_compare
+ self.plot_properties.main_exp_label = \
+ self.widget_exp_compare['entry_main_label'].get_text()
+ self.plot_properties.comp_exp_label = \
+ self.widget_exp_compare['entry_comp_label'].get_text()
+ col_type = self.widget_exp_compare[
+ 'combo_plot_color_type'].get_active()
+ if col_type == 0:
+ self.plot_properties.color_type = 'color'
+ elif col_type == 1:
+ self.plot_properties.color_type = 'bw'
+ self.plot_properties.plot_compare = True
+
+ def on_button_compare_exp_cancel_clicked(self, widget):
+ """
+ Cancel the modification of the comparison
+ """
+ if DEBUG:
+ print "compare experiment called by : "
+ print widget
+ if hasattr(self, 'temp_exp_compare'):
+ del self.temp_exp_compare
+ if hasattr(self, 'experiment_compare'):
+ del self.experiment_compare
+ self.plot_properties.comp_exp_label = None
+ self.plot_properties.plot_compare = False
+
+ def plot_curve(self):
+ """
+ Plot the curve according to the user choice.
+ """
+ [_px, _py] = [self.force_volume.trace_x, self.force_volume.trace_y]
+ if self.force_volume.to_plot('Av'):
+ if not exist(self.win_plot['Curve Plot']):
+ [window, widget] = self.regenerate_plot()
+ self.win_plot['Curve Plot'] = plot_gtk.plotCurves(
+ window, widget,
+ title='Deflection-Distance Curve',
+ linewidth=self.config['linewidth'])
+ self.win_plot['Curve Plot'].displayPlot(_px, _py)
+ if self.force_volume.to_plot('PoC'):
+ [poc, _cx, _cy, error, seg_slice, deriv] = \
+ self.force_volume.get_poc_display()
+ self.win_plot['Curve Plot'].hold('On')
+ if deriv is not None:
+ self.win_plot['Curve Plot'].displayPlot(deriv[0],
+ deriv[1])
+ self.win_plot['Curve Plot'].displayPlot(_cx, _cy,
+ 'g-', alpha=0.3)
+ self.win_plot['Curve Plot'].displayPlot(_px[poc: poc + 1],
+ _py[poc: poc + 1],
+ 'go')
+ self.win_plot['Curve Plot'].displayPlot(_cx, _cy + error,
+ 'r-', alpha=0.5)
+ self.win_plot['Curve Plot'].displayPlot(_cx, _cy - error,
+ 'r-', alpha=0.5)
+ self.win_plot['Curve Plot'].displayPlot(_px[seg_slice],
+ _py[seg_slice],
+ 'r-', alpha=1)
+ self.win_plot['Curve Plot'].hold('Off')
+ if self.force_volume.to_plot('Re'):
+ [_rx, _ry] = [self.force_volume.retrace_x,
+ self.force_volume.retrace_y]
+ if not exist(self.win_plot['Curve Plot']):
+ [window, widget] = self.regenerate_plot()
+ self.win_plot['Curve Plot'] = plot_gtk.plotCurves(
+ window, widget,
+ title='Deflection-Distance Curve',
+ linewidth=self.config['linewidth'])
+ if self.force_volume.to_plot('Av'):
+ self.win_plot['Curve Plot'].hold('On')
+ self.win_plot['Curve Plot'].displayPlot(_rx, _ry, 'r-')
+ self.win_plot['Curve Plot'].hold('Off')
+ if self.force_volume.to_plot('Event'):
+ det_event = self.force_volume.current_curve(
+ 'Event',
+ [self.experiment.parameters['event_detect_weight'],
+ self.experiment.parameters['event_fit_model']])
+ self.win_plot['Curve Plot'].hold('On')
+ if det_event:
+ for event in det_event:
+ self.win_plot['Curve Plot'].displayPlot(
+ _rx[event['Slice']],
+ _ry[event['Slice']], 'g')
+ self.win_plot['Curve Plot'].displayPlot(
+ [_rx[event['min']]],
+ [_ry[event['min']]], 'go')
+ self.win_plot['Curve Plot'].displayPlot(
+ [_rx[event['max']]],
+ [_ry[event['max']]], 'wo')
+ self.win_plot['Curve Plot'].displayPlot(
+ event['fit_x'],
+ event['fit_y'],
+ 'c')
+ self.win_plot['Curve Plot'].hold('Off')
+ if exist(self.win_plot['Curve Plot']):
+ self.win_plot['Curve Plot'].set_size(
+ self.experiment.parameters['plot_size'])
+ if self.force_volume.to_plot('Indent'):
+ all_plot = False
+ [poc, _cx, _cy, error, seg_slice, deriv] = \
+ self.force_volume.get_poc_display()
+ [_px, _py] = self.force_volume.get_current_indent_curve('trace')
+ [part_x, part_y] = self.force_volume.get_segments_indent()
+ # Prepare dots coordinates to plot
+ dot_x = [item[-1] for item in part_x]
+ dot_y = [item[-1] for item in part_y]
+ if not exist(self.win_plot['Indent Plot']):
+ [window, widget] = self.regenerate_plot()
+ self.win_plot['Indent Plot'] = plot_gtk.plotCurves(
+ window, widget,
+ title='Indention Curve',
+ xlabel='Indentation [nm]',
+ ylabel='Force [nN]',
+ linewidth=self.config['linewidth'])
+ self.win_plot['Indent Plot'].set_size(
+ self.experiment.parameters['plot_size'])
+ if all_plot:
+ self.win_plot['Indent Plot'].displayPlot(_px, _py)
+ else:
+ self.win_plot['Indent Plot'].displayPlot(
+ _px[:poc] - _px[poc - 1],
+ _py[:poc] - _py[poc - 1])
+ self.win_plot['Indent Plot'].hold('On')
+ if all_plot:
+
+ self.win_plot['Indent Plot'].displayPlot(dot_x, dot_y,
+ 'ko', alpha=0.4)
+ else:
+ self.win_plot['Indent Plot'].displayPlot(dot_x - _px[poc - 1],
+ dot_y - _py[poc - 1],
+ 'ko', alpha=0.4)
+ if DEBUG and self.force_volume.to_plot('Re'):
+ [_rx, _ry] = self.force_volume.get_current_indent_curve(
+ 'retrace')
+ self.win_plot['Indent Plot'].displayPlot(_rx, _ry, 'r-')
+ if self.force_volume.to_plot('Event'):
+ det_event = self.force_volume.current_curve(
+ 'Event',
+ self.experiment.parameters['event_detect_weight'])
+ self.win_plot['Indent Plot'].hold('On')
+ if det_event:
+ for event in det_event:
+ self.win_plot['Indent Plot'].displayPlot(
+ _rx[event['Slice']],
+ _ry[event['Slice']], 'g')
+ self.win_plot['Indent Plot'].displayPlot(
+ [_rx[event['min']]],
+ [_ry[event['min']]], 'go')
+ self.win_plot['Indent Plot'].displayPlot(
+ [_rx[event['max']]],
+ [_ry[event['max']]], 'wo')
+ self.win_plot['Indent Plot'].hold('Off')
+
+
+def exist(win_plot):
+ """
+ Test the existance of a window plot.
+ """
+ if win_plot == 0:
+ return 0
+ return not win_plot.destroyed
+
+
+def create_header_tree(header):
+ """
+ >>> test = create_header_tree(None)
+ """
+ treestore = gtk.TreeStore(str)
+ for parent in HEADER.keys():
+ piter = treestore.append(None, ['%s' % parent])
+ for child in HEADER[parent]:
+ treestore.append(piter, ['%s : %s' % (child, str(header[child]))])
+ return treestore
+
+
+def display_error(message, mtype=''):
+ """
+ Display an error message.
+ """
+ if message == 'no_fv':
+ msg = ('%s\n%s\n%s') % (
+ "No force volume file loaded.\n",
+ "Please load fv file using file>new to create new experiment",
+ "or file>load to load a saved experiment.")
+ if message == 'no_compute':
+ msg = "The %s is not compute. Please compute it first." % mtype
+ dialog = gtk.MessageDialog(type=gtk.MESSAGE_ERROR,
+ buttons=gtk.BUTTONS_OK,
+ message_format=msg)
+ dialog.set_title('Error')
+
+ try:
+ _icon = resource_filename('openfovea', 'Icon/openfovea.png')
+ except NotImplementedError:
+ _icon = 'openfovea/Icon/openfovea.png'
+ try:
+ dialog.set_icon_from_file(_icon)
+ except GError:
+ pass
+ if dialog.run():
+ dialog.destroy()
+
+
+def display_message(message):
+ """
+ Display a message box with the given message.
+ """
+ _msg = ''
+
+ for _line in message[1:]:
+ _msg += _line
+ _msg += '\n'
+ _msg = _msg[:-2]
+ dialog = gtk.MessageDialog(type=gtk.MESSAGE_INFO, buttons=gtk.BUTTONS_OK)
+ dialog.set_properties('secondary-use-markup', True)
+ dialog.set_title(message[0])
+ dialog.set_markup(_msg)
+ try:
+ _icon = resource_filename('openfovea', 'Icon/openfovea.png')
+ except NotImplementedError:
+ _icon = 'openfovea/Icon/openfovea.png'
+ dialog.set_icon_from_file(_icon)
+ if dialog.run():
+ dialog.destroy()
+
+
+def ask_message(message):
+ """
+ Display a
+ """
+ dialog = gtk.MessageDialog(type=gtk.MESSAGE_QUESTION,
+ buttons=gtk.BUTTONS_YES_NO)
+ dialog.set_default_response(gtk.RESPONSE_YES)
+ dialog.set_properties('secondary-use-markup', True)
+ dialog.set_title(message[0])
+ dialog.set_markup(message[1])
+ try:
+ _icon = resource_filename('openfovea', 'Icon/openfovea.png')
+ except NotImplementedError:
+ _icon = 'openfovea/Icon/openfovea.png'
+ dialog.set_icon_from_file(_icon)
+ response = dialog.run()
+ dialog.destroy()
+ return response
+WIN_TITLE = "Open Fovea"
+HEADER = {
+'Equipment': ['Microscope',
+ 'spring_constant'],
+'Parameters': [ # 'force_bytes_per_pixel',
+ # 'hard_z_scale',
+ # 'image_scale',
+ 'sensit_deflection',
+ # 'sens_z_scan'
+ ],
+'Scan': [ # 'image_mode',
+ 'scan_size',
+ # 'force_samples_per_curve',
+ # 'image_number_lines',
+ 'number_curves',
+ # 'ramp_size',
+ # 'size_x',
+ # 'size_y'
+ 'size'],
+'Misc': ['date', 'note'],
+#'Data': [ # 'force_data_length',
+# # 'force_data_offset',
+# # 'image_length',
+# # 'image_offset',
+# 'image_samps_per_line',
+# 'matrix_length',
+# 'x_factor']
+}
+# Label = label : [title, x_label, window_name]
+PLOT_LABEL = {
+'force': ['Event force', 'Force [nN]', 'Hist Event Force'],
+'force_base': ['Event force relative to baseline', 'Force [nN]',
+ 'Hist Event Force'],
+'lr': ['Loading rate', 'Loading rate [nN/s]', 'Hist Event LR'],
+'dist': ['Event distance', 'distance [nm]', 'Hist Event Dist'],
+'stiff': ['Event stiffness', 'Stiffness [Pa]', 'Hist Event Stiff'],
+'relstiff': ['Event relative stiffness', 'Stiffness [Pa]',
+ 'Hist Event Rel Stiff'],
+'young_modulus': ['Young modulus', 'Stiffness [Pa]', 'Hist Young Modulus'],
+'length': ['Event length', 'Length [nm]', 'Hist Event Length'],
+'plength': ['Event persistent length', 'Persistent length [nm]',
+ 'Hist Event Persistent Length']}
+# Window = window : [window name, window title, graph title]
+PLOT_WINDOW = {
+'forcelr': ['Scatter_FvsLR', 'Scatter Force VS Loading rate'],
+'lrforce': ['Scatter_FvsLR', 'Scatter Force VS Loading rate'],
+'forcedist': ['Scatter_FvsDist', 'Scatter Force VS Distance'],
+'distforce': ['Scatter_FvsDist', 'Scatter Force VS Distance'],
+'forcestiff': ['Scatter_FvsStiff', 'Scatter Force VS Stiffness'],
+'stiffforce': ['Scatter_FvsStiff', 'Scatter Force VS Stiffness'],
+
+'force_baselr': ['Scatter_FvsLR', 'Scatter Force VS Loading rate'],
+'lrforce_base': ['Scatter_FvsLR', 'Scatter Force VS Loading rate'],
+'force_basedist': ['Scatter_FvsDist', 'Scatter Loading rate VS Stiffness'],
+'distforce_base': ['Scatter_FvsDist', 'Scatter Loading rate VS Stiffness'],
+'force_basestiff': ['Scatter_FvsStiff', 'Scatter Force VS Stiffness'],
+'stiffforce_base': ['Scatter_FvsStiff', 'Scatter Force VS Stiffness'],
+
+'lrdist': ['Scatter_LRvsDist', 'Scatter Loading rate VS Distance'],
+'distlr': ['Scatter_LRvsDist', 'Scatter Loading rate VS Distance'],
+'lrstiff': ['Scatter_LRvsStiff', 'Scatter Loading rate VS Stiffness'],
+'stifflr': ['Scatter_LRvsStiff', 'Scatter Loading rate VS Stiffness'],
+'diststiff': ['Scatter_DistvsStiff', 'Scatter Distance VS Stiffness'],
+'stiffdist': ['Scatter_DistvsStiff', 'Scatter Distance VS Stiffness'],
+'forcerelstiff': ['Scatter_FvsRelStiff',
+ 'Scatter Force VS Relative Stiffness'],
+'relstiffforce': ['Scatter_FvsRelStiff',
+ 'Scatter Force VS Relative Stiffness'],
+'lrrelstiff': ['Scatter_LRvsRelStiff',
+ 'Scatter Loading Rate VS Realtive Stiffness'],
+'relstifflr': ['Scatter_LRvsRelStiff',
+ 'Scatter Loading Rate VS Realtive Stiffness'],
+'distrelstiff': ['Scatter_DistvsRelStiff',
+ 'Scatter Distance VS Relative Stiffness'],
+'relstiffdist': ['Scatter_DistvsRelStiff',
+ 'Scatter Distance VS Relative Stiffness'],
+'poc array': [None, 'poc_map', 'Point of contact'],
+'piezo array': ['Piezo Height', 'Piezo_Map', 'Piezo Height'],
+'topography array': ['Topography Map', 'Topo_map',
+ 'Topography (zero force image)'],
+'stiffness array': ['Stiffness Map', 'Stiff_map', 'Stiffness'],
+'ev_pos array': ['Event Map', 'Ev_pos_map', 'Event position'],
+'ev_force array': ['Event Force Map', 'Ev_force_map', 'Event force'],
+'ev_dist array': ['Event Distance Map', 'Ev_dist_map', 'Event distance'],
+'ev_len array': ['Event Length Map', 'Ev_len_map', 'Event length']}
+
+WINDOW2ARRAY = {
+ 'Topography Map': 'Topography',
+ 'Piezo Height': 'Piezo',
+ 'Stiffness Map': 'Stiffness',
+ 'Event Map': 'Event',
+ 'Event Force Map': 'Event force',
+ 'Event Distance Map': 'Event dist'}
+
+# MOSAIC_PLOT is a dict which contains the values for mosaic name :
+# mosaic_name[0] = name of the window plot
+# mosaic_name[1] = title of the window
+# mosaic_name[2] = title of the plot
+# mosaic_name[3] = cmap
+MOSAIC_PLOT = {
+ 'Piezo': ['Mosaic Piezo', 'Mosaic of piezo', 'Piezo images', 'afm image'],
+ 'Topography': ['Mosaic Topo', 'Mosaic of topos', 'Topographies',
+ 'afm image'],
+ 'Stiffness': ['Mosaic Stiffness', 'Mosaic of stiffness', 'Stiffness',
+ 'jet'],
+ 'Event': ['Mosaic Event', 'Mosaic of events', 'Events', 'gray'],
+ 'Event force': ['Mosaic Event Force', 'Mosaic of event force',
+ 'Event forces', 'gray']}
+
+
+def main(arg=None):
+ '''
+ OpenFovea main function
+ '''
+ if os.name == 'posix':
+ try:
+ if os.uname()[0] == 'Linux':
+ try:
+ locale.setlocale(locale.LC_ALL, 'en_GB.UTF8')
+ except locale.Error:
+ locale.setlocale(locale.LC_ALL, 'en_US.UTF8')
+ elif os.uname()[0] == 'Darwin':
+ locale.setlocale(locale.LC_ALL, 'en_GB')
+ except locale.Error:
+ gtk.MessageDialog(type=gtk.MESSAGE_ERROR)
+ message = \
+ "Locale en_GB does not seem to be installed on your system." +\
+ "\nPlease install it."
+ message_sec = "On Linux system : change the file" +\
+ "\n/etc/locale.gen" +\
+ "\nto uncomment the corresponding line and run, as root :" +\
+ "\nlocale-gen"
+ error = gtk.MessageDialog(buttons=gtk.BUTTONS_CLOSE,
+ type=gtk.MESSAGE_ERROR,
+ message_format=message)
+ error.format_secondary_text(message_sec)
+ error.set_title('Installation Error')
+ error.run()
+ elif os.name == 'nt':
+ locale.setlocale(locale.LC_ALL, 'english')
+ app = OpenFoveaMainGui(arg)
+ app.window.show()
+ gtk.main()
+
+if __name__ == '__main__':
+ locale.setlocale(locale.LC_ALL, 'en_GB.UTF8')
+ APP = OpenFoveaMainGui()
+ APP.window.show()
+ gtk.main()
+
+ #toto = open('../welcome')
+ #message = toto.readlines()
+ #display_message(message)
diff --git a/openfovea/plot_generic.py b/openfovea/plot_generic.py
new file mode 100644
index 0000000..ee6de71
--- /dev/null
+++ b/openfovea/plot_generic.py
@@ -0,0 +1,2239 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+'''
+This module contains the necessary tools to plot graphics independently from the
+gui.
+
+The examples saves the result in a folder named FIGURE_FOLDER. This is a string
+that contain the folder where the figure are plotted.
+'''
+
+# for script installation
+from pkg_resources import resource_filename # pylint: disable-msg=E0611
+import os, sys
+
+import pdb # pdb.set_trace()
+import numpy
+from scipy import stats
+
+try:
+ matplotlib.__version__
+except NameError:
+ import matplotlib
+ matplotlib.use('Agg', warn=False)
+ import matplotlib.pyplot as plt
+
+from matplotlib.patches import Circle, Rectangle, FancyArrow
+from matplotlib.widgets import Cursor
+from matplotlib.ticker import NullFormatter
+from mpl_toolkits.axes_grid.anchored_artists import AnchoredText
+NULLFMT = NullFormatter()
+
+from fovea_toolbox import post_proc
+
+try:
+ NO_DATA_FILENAME = resource_filename('openfovea','Icon/NoData.png')
+except NotImplementedError:
+ NO_DATA_FILENAME = 'openfovea/Icon/NoData.png'
+try:
+ NO_DATA_IMAGE = matplotlib.image.imread(NO_DATA_FILENAME)
+except IOError:
+ NO_DATA_FILENAME = os.path.join(os.path.dirname(sys.executable), 'openfovea/Icon/NoData.png')
+ NO_DATA_IMAGE = matplotlib.image.imread(NO_DATA_FILENAME)
+class Plot(object):
+ """
+ This is the generic plot class that will be inherited from the other.
+ It has no plot capabilities, but have the standard attribute.
+ """
+ def __init__(self):
+ self.figure = plt.figure()
+ self.axis = self.figure.add_subplot(111)
+ self.colors = ['blue', 'green', 'red', 'cyan', 'magenta', 'yellow',
+ 'black', 'white']
+ self.win_size = [8, 6]
+ self.scale = ['linear', 'linear']
+ self.type = None
+ def save(self, filename):
+ """
+ Save your plot in a file.
+ """
+
+ self.figure.savefig(filename)
+
+ def hold(self, value):
+ """
+ Change the hold state of the axis.
+ Accepted values are :
+
+ * 'On' or True to make two plots.
+ * 'Off' or False to override the last plot.
+ """
+
+ if value in ('On', True):
+ self.axis.hold(True)
+ elif value in ('Off', False):
+ self.axis.hold(False)
+
+ def set_size(self, width, height, deep=False):
+ """
+ Change the size of the figure.
+ """
+ # Figure size is inch * dpi
+ #dpi = self.figure.get_dpi()
+ #inch = self.figure.get_size_inches()
+ #
+ ##
+ # Normally, the figure size in pixel is the inch * dpi.
+ # But I see that it's inch * 100... I force it to 100 and wait for explanations later.
+ new_inch = [float(width) / 100, float(height) / 100]
+ self.win_size = new_inch
+ if deep:
+ self.figure.set_size_inches(new_inch)
+ def set_xscale(self, scale=None):
+ if scale is not None:
+ self.scale[0] = scale
+ self.axis.set_xscale(self.scale[0])
+ def get_type(self):
+ return self.type
+class Curve(Plot):
+ """
+ Plot a curve on a window. Stores the standard values required for the
+ plot in OpenFovea so that I don't have to think about it later.
+
+ >>> import numpy
+
+ >>> x_data = numpy.arange(0, 10, 0.1)
+ >>> y_data = numpy.sin(x_data)
+ >>> y_data_2 = numpy.sin(x_data + 1)
+
+ >>> test = Curve(xlabel = 'x axis', ylabel = 'y axis', title = 'plot title')
+ >>> test.plot(x_data, y_data)
+ >>> test.hold(True)
+ >>> test.plot(x_data, y_data_2, linestyle='go')
+ >>> test.save(FIGURE_FOLDER + 'plot.svg')
+ """
+
+ def __init__(self, xlabel='Scanner Extention [nm]',
+ ylabel='Cantilever Deflection [nm]',
+ title='Force-Distance curve',
+ linewidth=1.5):
+ Plot.__init__(self)
+
+ self.xlabel = xlabel
+ self.ylabel = ylabel
+ self.title = title
+ self.axis.hold(False)
+ self.axis.set_xlabel(self.xlabel)
+ self.axis.set_ylabel(self.ylabel)
+ self.axis.set_title(self.title)
+ self.axis.grid(True)
+ self.lim = None
+ self.linewidth = linewidth
+ self.type = 'curve'
+ def plot(self, x_data, y_data, linestyle='b-', alpha=1):
+ """
+ Plot your data with the specified style.
+ """
+ if self.lim is not None:
+ # The plot has already been initialized. Then, we get the current
+ # limit in order to restore it after the plot.
+ self.lim['x'] = self.axis.get_xlim()
+ self.lim['y'] = self.axis.get_ylim()
+ self.axis.plot(x_data, y_data, linestyle, linewidth=self.linewidth, alpha=alpha)
+ self.__reinitialize()
+
+ def __reinitialize(self):
+ """
+ This is to reinitialize your plot. You don't need to manually call
+ it, as it is automatically called when you do a plot.
+ """
+
+ self.axis.set_xlabel(self.xlabel)
+ self.axis.set_ylabel(self.ylabel)
+ self.axis.set_title(self.title)
+# if self.ylim is not None:
+# self.axis.set_ylim(self.ylim)
+ if self.lim is not None:
+ self.axis.set_xlim(self.lim['x'])
+ self.axis.set_ylim(self.lim['y'])
+ else:
+ self.lim = {'x' : self.axis.get_xlim(),
+ 'y' : self.axis.get_ylim()}
+
+ self.axis.grid(True)
+class ErrorBar(Curve):
+ def __init__(self, xlabel='Depth [nm]',
+ ylabel='Young Modulus [Pa]',
+ title='Mean Stiffness',):
+ Curve.__init__(self, xlabel, ylabel, title)
+ self.axis2 = self.axis.twinx()
+ self.pretty_axis()
+ def average_stiffness(self, data, group=None, group_info=None, linestyle='b-', style='isol', show_count=True):
+ """
+ Plot the errorbar with the specified style
+
+ * Parameters :
+ data : list
+ group : list
+ group_info : list
+ linestyle : str
+ style : str
+ Defines the style of the average. This is pertinent only
+ with grouped data.
+ 'isol' : each item of a group is isolated. The average and
+ standard deviation reflects the population
+ homogeneity. If one file is 245 ± 10 and the other
+ is 255 ± 15, the result will be 250 ± 5.
+ 'merge' : Each item of a group is merged. The average and
+ standard deviation reflects the pixels
+ homogeneity. If one file is 245 ± 10 with 10
+ values, the other is 255 ± 15 with 20 values, the
+ result will be 251 ± 13
+ """
+ self.axis.cla()
+ self.axis2.cla()
+ if group is not None:
+ # data is a set of several measurements each of them belonging to a
+ # group
+ if group_info is None:
+ # no information about the groups...
+ group_info = {'id' : numpy.unique(group),
+ 'label' : ['group %i'%gid for gid in numpy.unique(group)],
+ 'display' : [True for gid in numpy.unique(group)],
+ }
+ group = numpy.array(group)
+ list_group = [group_info['id'][i] for i in numpy.nonzero(group_info['display'])[0]]
+ for item in list_group:
+ #pdb.set_trace()
+ gindex = group_info['id'].index(item)
+ index_list = numpy.nonzero(group == group_info['id'][gindex])[0]
+ # We recover the data that has to be plotted
+ _data = [data[i]['mean'] for i in index_list]
+ _std = [data[i]['std'] for i in index_list]
+ _count = [data[i]['count'] for i in index_list]
+ # Here, we have the maximum depth in the files of the current group.
+ _deep = [data[i]['deep'] for i in index_list]
+ _tot_size = [len(item) for item in _deep]
+ #_max_size = max([len(item) for item in _deep])
+ deep = _deep[_tot_size.index(max(_tot_size))]
+ if style == 'merge':
+ # We have to fill with 0 the arrays that are smaller.
+ _data = numpy.array([list(item) + [0] * (max(_tot_size) - len(item))
+ for item in _data])
+ _std = numpy.array([list(item) + [0] * (max(_tot_size) - len(item))
+ for item in _std])
+ _count = numpy.array([list(item) + [0] * (max(_tot_size) - len(item))
+ for item in _count])
+ new_mean = (_data * _count).sum(0) / _count.sum(0)
+ new_std = (_std ** 2 * (_count - 1)).sum(0) / _count.sum(0) - 1
+ new_std = new_std ** 0.5
+ elif style == 'isol':
+ # We have to fill with nan the arrays that are smaller.
+ new_data = numpy.ma.array(numpy.zeros((len(_data), max(_tot_size))),
+ mask=numpy.ones((len(_data),
+ max(_tot_size))))
+ new_count = numpy.zeros((len(_data), max(_tot_size)))
+ for i in range(len(_data)):
+ new_count[i,:len(_count[i])] = _count[i]
+ new_count = new_count.sum(0)
+ for i in range(len(_data)):
+ new_data[i][:len(_data[i])] = _data[i]
+ new_data[i].mask[0:len(_data[i])] = 0
+ new_mean = new_data.mean(0)
+ new_std = new_data.std(0)
+# _data = numpy.array([list(item) + [nan] * (max(_tot_size) - len(item))
+# for item in _data])
+# _std = numpy.array([list(item) + [nan] * (max(_tot_size) - len(item))
+# for item in _std])
+# _count = numpy.array([list(item) + [nan] * (max(_tot_size) - len(item))
+# for item in _count])
+# new_mean = _data.mean(0)
+# new_std = _data.std(0)
+ self.plot(deep, new_mean, new_std, label=group_info['label'][gindex])
+ if show_count:
+ self.plot_y2(deep, new_count)
+ else:
+ self.plot(data['deep'], data['mean'], data['std'])
+ if show_count:
+ self.plot_y2(data['deep'], data['count'])
+ self.pretty_axis(show_y2=show_count)
+ def pretty_axis(self, show_y2=False):
+ yaxis = self.axis.get_yaxis()
+ yaxis.set_label_position('left')
+ yaxis.set_ticks_position('left')
+ yaxis = self.axis2.get_yaxis()
+ yaxis.set_label_position('right')
+ yaxis.set_ticks_position('right')
+ yaxis.set_visible(show_y2)
+
+ def plot(self, data_x, data_y, y_err, label=None, linestyle='b-'):
+ self.axis.hold(True)
+ self.axis.errorbar(data_x, data_y, y_err, label=label)
+ self.axis.hold(False)
+ if label is not None:
+ self.axis.legend(loc='best')
+ def plot_y2(self, data_x, data_y, linestyle='--'):
+ self.axis2.plot(data_x, data_y, linestyle)
+ self.axis2.set_ylabel('Number of data')
+
+class Array(Plot):
+ """
+ Plot an array. It can behaves differently corresponding to the array
+ type you give.
+
+ >>> import numpy
+
+ >>> data = numpy.random.randn(10,10)
+ >>> test = Array('Random array')
+ >>> test.plot(data)
+ >>> test.save(FIGURE_FOLDER + 'array_pixel.svg')
+
+ If the scan size is specified, the array is recognized as nanometric
+ scale image.
+
+ >>> test.title = 'Random array in nanometer'
+ >>> test.plot(data, scan_size=[1000, 1000])
+ >>> test.save(FIGURE_FOLDER + 'array_nm.svg')
+
+ A scale bar can be added to show the correlation between color and
+ values.
+
+ >>> test.colorbar = True
+ >>> test.plot(data, scan_size=[1000, 1000])
+ >>> test.save(FIGURE_FOLDER + 'array_cb.svg')
+
+ If the data is changed, the colormap is updated :
+
+ >>> data = data*100
+ >>> test.plot(data, scan_size=[1000, 1000])
+ >>> test.vmax = 140
+ >>> test.save(FIGURE_FOLDER + 'array_cb100.svg')
+
+ And you can plot a marker at a position given in pixel :
+
+ >>> test.colorbar = False
+ >>> test.plot(data, scan_size=[1000, 1000])
+ >>> test.marker = (5, 4)
+ >>> test.save(FIGURE_FOLDER + 'array_marker.png')
+ """
+
+ def __init__(self, title='Data array',):
+ Plot.__init__(self)
+ self.type = 'pcolor'
+ # definitions for the axes
+ left = 0.15
+ bottom = 0.15
+ ar_width = 0.7
+ cb_width = 0.03
+ height = 0.75
+ plot_dist = 0.03
+
+ left_bar = left + ar_width + plot_dist
+ rect_arr = [left, bottom, ar_width, height]
+ rect_cb = [left_bar, bottom, cb_width, height]
+
+ self.figure.clf()
+ self.axis = {'array' : self.figure.add_axes(rect_arr),
+ 'colorbar' : self.figure.add_axes(rect_cb)
+ }
+ ## Default values:
+ self.array = None # stores the array
+ self.scale = {'size' : None, # Is the size of the scan
+ 'unit' : '[pixel]', # is the unit of the size
+ 'array' : None, # Is the array to display the label
+ 'voxel_size' : [1, 1], # The size of a pixel
+ 'initialized' : False,
+ 'show' : True
+ }
+ self.color_scale = {'min' : None,
+ 'max' : None,
+ 'colorbar' : False, # Do we plot or not the
+ # scalebar.
+ 'cmap' : 'gray'}
+
+ self.__init_pmarker()
+
+ self.path = { 'start' : None, # The start point
+ 'stop' : None, # The end point
+ 'list' : None, # The list of points that define the path
+ 'plot' : None, # Contain the curve traced on the plot array.
+ 'grab' : None, # Which point to grab = 'start' or 'stop'
+ 'refresh' : None, # Contains the list of points that define the path. Is erased when used.
+ }
+ #TODO Remove self.trace and self.show_trace
+ self.trace = None # Contain the curve traced on the plot array.
+ self.title = title
+
+ def __init_pmarker(self):
+ self.p_marker = {'display' : True,
+ 'indice_x' : None,
+ 'indice_y' : None,
+ 'patch' : None} # matplotlib patch object to mark the
+ # position
+
+ def __setattr__(self, att, value):
+ if att is 'colorbar':
+ self.__dict__['color_scale']['colorbar'] = value
+ elif att is 'cmap':
+ self.__dict__['color_scale']['cmap'] = value
+ elif att is 'vmin':
+ self.__dict__['color_scale']['min'] = value
+ if self.array is not None:
+ self.plot()
+ elif att is 'vmax' :
+ self.__dict__['color_scale']['max'] = value
+ if self.array is not None:
+ self.plot()
+ elif att is 'marker':
+ self.__dict__['p_marker']['display'] = True
+ self.__dict__['p_marker']['indice_x'] = value[0]
+ self.__dict__['p_marker']['indice_y'] = value[1]
+ #self.mark_pos(value)
+ self.mark_pos()#plot()
+ else:
+ self.__dict__[att] = value
+ def __getattr__(self, att):
+ if att is 'colorbar':
+ return self.__dict__['color_scale']['colorbar']
+ else:
+ return self.__dict__[att]
+ def plot(self, array=None, scan_size=None):
+ """
+ Displays the array.
+ The scan size has to be in nanometer.
+ """
+ ## Initialize the variables
+ self.axis['array'].cla()
+ self.__init_pmarker()
+ if array is not None:
+ self.array = array
+ if type(self.array) == numpy.ma.core.MaskedArray and \
+ numpy.all(self.array.mask.all(True)):
+ self.axis['array'].imshow(NO_DATA_IMAGE)
+ self.axis['array'].set_axis_off()
+ self.axis['colorbar'].set_visible(False)
+ return
+ #raise ValueError, 'Array with only masked values.'
+ if not self.scale['initialized']:
+ if scan_size in [0, None]:
+ scan_size = None
+ elif 0 in scan_size:
+ scan_size = None
+ shape = self.array.shape
+ self.scale['array'] = [numpy.arange(0, shape[0]+1) for i in range(2)]
+ self.scale['size'] = [1 for i in range(2)]
+ self.scale['unit'] = ['[pixel]' for i in range(2)]
+ self.scale['voxel_size'] = [1 for i in range(2)]
+ if scan_size is not None:
+ shape = self.array.shape
+ if isinstance(scan_size, (int, float)):
+ scan_size = [float(scan_size), float(scan_size)]
+ else:
+ scan_size = [float(dim) for dim in scan_size]
+ for dim in range(2):
+ if scan_size[dim] < 1000:
+ self.scale['unit'][dim] = '[nm]'
+ self.scale['size'][dim] = scan_size[dim]
+ elif scan_size[dim] >= 1000:
+ self.scale['unit'][dim] = r'[$\mathregular{\mu}$m]'
+ self.scale['size'][dim] = scan_size[dim] / 1000.
+ self.scale['array'][dim] = numpy.arange(
+ 0,
+ self.scale['size'][dim] + self.scale['size'][dim] / shape[0],
+ self.scale['size'][dim] / shape[0])
+ self.scale['voxel_size'][dim] = self.scale['size'][dim]/shape[dim]
+ self.scale['initialized'] = True
+# if not self.scale['initialized']:
+# self.scale['array'] = [numpy.arange(0, shape[0]+1) for i in range(2)]
+# self.scale['unit'] = ['[pixel]' for i in range(2)]
+ # Plot
+ self.axis['array'].set_title(self.title)
+ image = self.axis['array'].pcolormesh(self.scale['array'][0],
+ self.scale['array'][1],
+ self.array.transpose(),
+ vmin=self.color_scale['min'],
+ vmax=self.color_scale['max'],
+ cmap=self.color_scale['cmap'])
+ self.axis['array'].axis([0, self.scale['array'][0][-1],
+ 0, self.scale['array'][1][-1]])
+ if self.scale['show']:
+ self.axis['array'].set_xlabel('Size ' + self.scale['unit'][0])
+ self.axis['array'].set_ylabel('Size ' + self.scale['unit'][1])
+ else:
+ self.axis['array'].set_xticks([])
+ self.axis['array'].set_yticks([])
+ if self.color_scale['colorbar']:
+ self.axis['colorbar'].set_visible(True)
+ self.axis['colorbar'].cla()
+ self.figure.colorbar(image, self.axis['colorbar'])
+ else:
+ self.axis['colorbar'].set_visible(False)
+ if self.p_marker['display']:
+ self.mark_pos()
+
+ def mark_pos(self):
+ """
+ Mark the position of the cursor.
+ """
+ if self.p_marker['indice_x'] is None:
+ return
+ pos_x = self.p_marker['indice_x'] * self.scale['voxel_size'][0]
+ pos_y = self.p_marker['indice_y'] * self.scale['voxel_size'][1]
+ if self.p_marker['patch'] is not None:
+ self.p_marker['patch'].set_xy((pos_x, pos_y))
+ else:
+ self.p_marker['patch'] = Rectangle((pos_x, pos_y),
+ self.scale['voxel_size'][0],
+ self.scale['voxel_size'][1],
+ zorder=10)
+ self.p_marker['patch'].set_antialiased(True)
+ self.p_marker['patch'].set_linewidth(1)
+ self.p_marker['patch'].set_facecolor((1.0, 1.0, 1.0, 1.0))
+ self.p_marker['patch'].set_alpha(0.7)
+ self.axis['array'].hold(True)
+ self.axis['array'].add_patch(self.p_marker['patch'])
+
+ def on_gride_click(self, event):
+ if event.button == 1:
+ # Left mouse button ==> goto
+ return [event.xdata, event.ydata]
+ elif event.button == 3:
+ # Right mouse button ==> slice
+ posx = int(event.xdata / self.scale['voxel_size'][0])
+ posy = int(event.ydata / self.scale['voxel_size'][1])
+ self.set_path(posx, posy)
+ def show_trace(self, trace):
+ """
+ Show the trace.
+ """
+ if self.trace is None:
+ self.trace = self.axis['array'].plot(trace[0], trace[1], picker=5,
+ linewidth = 4, alpha=0.5)
+ else:
+ self.trace[0].set_data(trace)
+ def set_path(self, posx, posy):
+ """
+ Create a path on the gride.
+ """
+ if self.path['start'] is None:
+ self.path['start'] = [posx, posy]
+ elif self.path['stop'] is None:
+ self.path['stop'] = [posx, posy]
+ # Create the path and show it
+ self.path['list'] = post_proc.generate_path(self.path['start'],
+ self.path['stop'])
+ self.path['refresh'] = self.path['list']
+ plot_list = self.path['list']
+ if self.path['plot'] is None:
+ self.path['plot'] = self.axis['array'].plot(
+ plot_list[0] * self.scale['voxel_size'][0],
+ plot_list[1] * self.scale['voxel_size'][1],
+ color = 'white', linewidth = 5, alpha = 0.6)
+ else:
+ self.path['plot'][0].set_data(plot_list *
+ self.scale['voxel_size'][0])
+ else:
+ picker = 3
+ if ((posx - picker < self.path['start'][0] < posx + picker) and
+ (posy - picker < self.path['start'][1] < posy + picker)):
+ #if [posx, posy] == self.path['start']:
+ self.path['grab'] = 'start'
+ if ((posx - picker < self.path['stop'][0] < posx + picker) and
+ (posy - picker < self.path['stop'][1] < posy + picker)):
+ self.path['grab'] = 'stop'
+ def get_path(self):
+ """
+ Get the path and erase it.
+ """
+ path, self.path['refresh'] = self.path['refresh'], None
+ return path
+ def on_button_release(self, event):
+ self.path['grab'] = None
+ def move_mouse(self, event):
+ """
+ What to do when the mouse is moved...
+ """
+ if not event.inaxes:
+ return
+ if self.path['grab'] == 'start':
+ self.path['start'] = [int(event.xdata / self.scale['voxel_size'][0]),
+ int(event.ydata / self.scale['voxel_size'][1])]
+ plot_list = self.path['list']
+ self.path['list'] = post_proc.generate_path(self.path['start'], self.path['stop'])
+ self.path['plot'][0].set_data(plot_list * self.scale['voxel_size'][0])
+ self.path['refresh'] = self.path['list']
+ elif self.path['grab'] == 'stop':
+ self.path['stop'] = [int(event.xdata / self.scale['voxel_size'][0]),
+ int(event.ydata / self.scale['voxel_size'][1])]
+ plot_list = self.path['list']
+ self.path['list'] = post_proc.generate_path(self.path['start'], self.path['stop'])
+ self.path['plot'][0].set_data(plot_list * self.scale['voxel_size'][0])
+ self.path['refresh'] = self.path['list']
+
+class TimeLapse(Plot):
+ """
+ Plots the time laps with a barplot displaying the average before and
+ after a treatment.
+
+ For example :
+
+ >>> import numpy
+
+ Let's make some difference between data before 100 and after 100
+
+ Data between 0-9 belong to group "0" and between 10-14 to group "1"
+
+ >>> test = TimeLapse()
+
+
+ Let's do with DataPlot object.
+
+ Our data is made of 15 data points with 10 measurments each. Relative
+ stiffness is computed on 3 distances. So let's generate the final list
+ like this :
+
+ >>> tl_array = numpy.random.randn(15 * 10)
+ >>> data = [tl_array for i in range(3)]
+
+ >>> ctl_array = numpy.random.randn(15 * 10)
+ >>> data_ctl = [ctl_array/(i+1) for i in range(3)]
+
+ We generate the file index :
+
+ >>> index = numpy.mgrid[0:15, 0:10][0]
+ >>> index.shape = 15 * 10
+
+ >>> group = [0 for i in range(10*10)] + [1 for i in range(5*10)]
+ >>> group = [group for i in range(3)]
+ >>> plot_data = PlotData()
+
+ >>> plot_data.set_type('relstiff')
+ >>> plot_data.set_data('relstiff', data)
+ >>> plot_data.set_range('relstiff', [-5, 5, 0.1, 0])
+ >>> plot_data.set_group('relstiff', group)
+ >>> plot_data.set_fid('relstiff', index)
+
+ And the same with the control (and let's add a group to look):
+
+ >>> plot_data.set_data('relstiffctl', data_ctl)
+ >>> plot_data.set_range('relstiffctl', [-5, 5, 0.1, 0])
+ >>> plot_data.set_group('relstiffctl', group)
+ >>> plot_data.set_fid('relstiffctl', index)
+
+ >>> test.plot(plot_data)
+ >>> test.save(FIGURE_FOLDER + 'PlotData_tl_group_ctl')
+ """
+ #TODO Clean old compatibility for vectors, all should go through DataPlot
+ #objects
+ def __init__(self, xlabel='Time',
+ ylabel='Stiffness',
+ title='Relative Stiffness'):
+
+ Plot.__init__(self)
+ self.type = 'plot-bar'
+ # definitions for the axes
+ #
+ # <----tl_width----> <bar_width>
+ # +----------------+ +---------+ ^
+ # | | | | |
+ # | Time-Lapse | | Bar | height
+ # | | | | |
+ # left +----------------+ +---------+ v
+ # bottom <->
+ # plot_dist
+
+ # definitions for the axes
+ left = 0.05
+ bottom = 0.1
+ tl_width = 0.7
+ bar_width = 0.22
+ height = 0.8
+ plot_dist = 0
+
+ left_bar = left + tl_width + plot_dist
+ rect_tl = [left, bottom, tl_width, height]
+ rect_hist = [left_bar, bottom, bar_width, height]
+
+ self.figure = plt.figure(figsize=(10, 2))
+ self.axis = {'tl' : self.figure.add_axes(rect_tl),
+ 'bar' : self.figure.add_axes(rect_hist)
+ }
+ self.sem = 1
+ def plot(self, data, data_ctl=None, baseline=0):
+ """
+ Plots the data
+ """
+ # pylint: disable-msg=R0913
+ # disable the "too many argument" warning from pylint
+
+ # Defines the properties of the box plot
+ box = {'size' : 0.45, # the size of the box
+ 'slide' : 0.4}# how far the box slides
+ # (between data and data_ctl)
+
+ ## Get the values to plot :
+ if not isinstance(data, PlotData):
+ raise AttributeError, "Data has to be a PlotData object."
+ indice = data.get_group('relstiff')
+ fileid = data.get_fid('relstiff')
+ if data_ctl is None:
+ fileid_ctl = data.get_fid('relstiffctl')
+ data_ctl = data.get_data('relstiffctl')
+ group_ctl = data.get_group('relstiffctl')
+ data = data.get_data('relstiff')
+ time = numpy.unique(fileid)
+
+ if indice is None:
+ indice = numpy.zeros(data.shape[0])
+ res_tl = compute_indice_stat(data, fileid)
+ res_bar = compute_indice_stat(data, indice)
+
+ # Prepare the axes :
+ self.axis['tl'].cla()
+ self.axis['bar'].cla()
+ # And plot.
+ self.axis['tl'].errorbar(time, res_tl['mean'], res_tl['sem'],
+ fmt='-', color='black')
+
+ self.axis['bar'].bar(res_bar['indice'], res_bar['mean'],
+ width=box['size'], color='black', alpha=0.5)
+ self.axis['bar'].errorbar(res_bar['indice'] + box['size']/2.,
+ res_bar['mean'], res_bar['sem'],
+ fmt=None, ecolor='k')
+
+ if data_ctl is not None:
+ # We have control to plot in dashed line :
+ if isinstance(data_ctl, PlotData):
+ # Another data set is given for the control.
+ indice_ctl = data_ctl.get_group('relstiff')
+ fileid_ctl = data_ctl.get_fid('relstiff')
+ group_ctl = data_ctl.get_group('relstiff')
+ data_ctl = data_ctl.get_data('relstiff')
+
+ res_tl_ctl = compute_indice_stat(data_ctl, fileid_ctl)
+ res_bar_ctl = compute_indice_stat(data_ctl, group_ctl)
+ self.axis['tl'].hold(True)
+ time = numpy.unique(fileid_ctl)
+ self.axis['tl'].errorbar(time,
+ res_tl_ctl['mean'], res_tl_ctl['sem'],
+ fmt='--', color='gray')
+
+ self.axis['bar'].bar(
+ res_bar_ctl['indice'] + box['slide'], res_bar_ctl['mean'],
+ width=box['size'], color='white', alpha=0.5)
+
+ self.axis['bar'].errorbar(
+ res_bar_ctl['indice'] + box['slide'] + box['size'] / 2,
+ res_bar_ctl['mean'], res_bar_ctl['sem'],
+ fmt=None, ecolor='k')
+ self.axis['tl'].hold(False)
+
+ # Display the baseline in the time-lapse
+ self.axis['tl'].hold(True)
+
+ baseline = [[time[0]-0.5, time[-1]+0.5],
+ [baseline, baseline]]
+
+ self.axis['tl'].plot(baseline[0], baseline[1], 'k-')
+ self.axis['tl'].set_xlim(baseline[0][0], baseline[0][1])
+
+ # Control the plot axis
+ # 1) make y axis the same for both and do not display bar one
+ # 2) change the x axis of bar to display 'Before' and 'After'
+ tl_y_lim = self.axis['tl'].get_ylim()
+ bar_y_lim = self.axis['bar'].get_ylim()
+
+ min_y = min(tl_y_lim[0], bar_y_lim[0])
+ max_y = max(tl_y_lim[1], bar_y_lim[1])
+ # 1)
+ self.axis['tl'].set_ylim(min_y, max_y)
+ self.axis['bar'].set_ylim(min_y, max_y)
+ self.axis['bar'].set_yticks([])
+ # 2)
+ self.axis['bar'].set_xticks(res_bar['indice'] + box['slide'] +
+ (box['size'] - box['slide']) / 2)
+ self.axis['bar'].set_xticklabels(res_bar['indice'])
+
+class Mosaic(Plot):
+ """
+ Plots the mosaic of several arrays.
+
+ >>> from numpy.random import randn
+ >>> import pylab
+
+ >>> array_list = [randn(10,10), randn(10,10), randn(15,15)]
+ >>> list_name = ['first and a very long text to test it...', 'second', 'third']
+ >>> test = Mosaic()
+ >>> test.plot(array_list, list_name = list_name)
+ >>> test.save(FIGURE_FOLDER + 'mosaic_test.png')
+
+ Note that mask annotation is pickable.
+ """
+
+ def __init__(self):
+ Plot.__init__(self)
+ self.type = 'mosaic'
+ self.figure.clf()
+ self.axis = list()
+ self.text_annotation = list()
+ self.mask_annotation = list()
+
+
+ def plot(self, list_array, title = 'Mosaic plot',
+ list_name = None, cmap = 'gray', list_mask = None):
+ """
+ Plot the Mosaic of arrays.
+ """
+
+ #self.__init__()
+ size_list = len(list_array)
+ nbr_line = numpy.round(numpy.sqrt(size_list))
+ nbr_col = numpy.ceil(numpy.sqrt(size_list))
+ # Create the list of name
+ if list_name is None:
+ list_name = ['Image ' + '{0:0>2}'.format(v)
+ for v in xrange(size_list)]
+ # Create the list of mask
+ if list_mask is None:
+ list_mask = [False for i in range(size_list)]
+ # Create the list of subplots
+ for item in xrange(len(list_array)):
+ new_array = None
+ self.axis.append(self.figure.add_subplot(nbr_line, nbr_col, item+1))
+ # determine what to plot
+ if list_array[item] is not None:
+ if list_array[item].ndim > 2:
+ # This is a 3d array, then take the first one.
+ if isinstance(list_array[item], numpy.ma.core.MaskedArray):
+ if 0 in list_array[item][:, :, 0].mask:
+ new_array = list_array[item][:, :, 0]
+ else:
+ new_array = None
+ else :
+ # This is a 2d array, then take it like this.
+ new_array = list_array[item]
+ # Plot it in the subplot.
+ if new_array is not None:
+ self.axis[item].pcolorfast(new_array.transpose(),
+ cmap=cmap)
+ else:
+ self.axis[item].imshow(NO_DATA_IMAGE)
+ self.axis[item].set_axis_off()
+ for item in xrange(len(list_array)):
+ # Annotate with the name given in the list.
+ ax_box = self.axis[item].get_position().get_points()
+ middle = (ax_box[0][0] + (ax_box[1][0] - ax_box[0][0]) / 2,
+ ax_box[0][1] + (ax_box[1][1] - ax_box[0][1]) / 2)
+ self.text_annotation.append(
+ self.axis[-1].annotate(list_name[item],
+ xy=middle, xycoords = 'figure pixels',
+ horizontalalignment = 'center',
+ verticalalignment = 'center',
+ fontsize = 12,
+ fontweight = 'bold',
+ bbox=dict(boxstyle="round", fc="0.8", alpha=0.8),
+ visible = False
+ )
+ )
+ # Annotate with 'M' if masked, or nothing if not.
+ if list_mask[item]:
+ mask_color = 'red'
+ alpha = 0.7
+ else:
+ mask_color = 'green'
+ alpha = 0.3
+ bottom_right = (0,0)#ax_box[0][0], ax_box[0][1])
+ self.mask_annotation.append(
+ self.axis[item].annotate('M',
+ xy=bottom_right, xycoords = 'axes fraction',
+ horizontalalignment = 'left',
+ verticalalignment = 'bottom',
+ fontsize = 12,
+ fontweight = 'bold',
+ bbox=dict(boxstyle='round', fc=mask_color, alpha=alpha),
+ visible = True,
+ picker=20,
+ alpha=alpha)
+ )
+ self.figure.suptitle(title)
+ self.figure.canvas.mpl_connect('axes_enter_event', self.enter_array)
+ self.figure.canvas.mpl_connect('axes_leave_event', self.leave_array)
+ self.figure.canvas.mpl_connect('motion_notify_event', self.enter_array)
+ #self.figure.canvas.mpl_connect('pick_event', self.on_pick)
+ def index(self, event):
+ """
+ Get the index number of the axes and the object type where a mouse event occured.
+
+ You can directly access the axis number of a mouse event :
+
+ [index, otype] = mosaic.index(event)
+ """
+ axe = self.axis.index(event.mouseevent.inaxes)
+ if self.mask_annotation.count(event.artist):
+ # This is the mask artist that was clicked.
+ obj = 'mask'
+ return [axe, obj]
+ def enter_array(self, event):
+ """
+ Responsible for the mouse motion event, when the cursor enter an
+ axis.
+ """
+
+ try:
+ self.text_annotation[self.axis.index(event.inaxes)].set_visible(
+ True)
+ self.text_annotation[self.axis.index(event.inaxes)].xytext = \
+ (event.x, event.y)
+ except ValueError:
+ pass
+ def leave_array(self, event):
+ """
+ Responsible for the mouse motion event, when the cursor leave an
+ axis.
+ """
+ try:
+ self.text_annotation[self.axis.index(event.inaxes)].set_visible(False)
+ except ValueError:
+ pass
+
+class Histogram(Plot):
+ """
+ Plot the histogram of a data set.
+ This is an enhanced version of the hist function of matplotlib.
+
+ >>> from numpy.random import randn
+ >>> data = randn(1000)
+ >>> test = Histogram(xlabel='Random [AU]')
+
+ There is a simple way to plot your data just by giving your data array :
+
+ >>> test.plot(data)
+ >>> test.save(FIGURE_FOLDER + 'simple_hist.png')
+
+ But there is a more advanced way, which is to make an instance of the
+ PlotData class, set it's properties and send it to plot :
+
+ >>> plot_data = PlotData()
+ >>> plot_data.set_data('force', data)
+ >>> plot_data.set_range('force', [-5, 5, 0.1])
+ >>> test.plot(plot_data)
+ >>> test.save(FIGURE_FOLDER + 'PlotData_class_hist.png')
+
+ Comparing several datasets is possible. Note that the second set of data
+ does not need to set the range. The range of the first one will be used.
+
+ >>> plot_data2 = PlotData()
+ >>> plot_data2.set_data('force', data + 1.2)
+ >>> test.plot(plot_data, plot_data2, color_type='color')
+ >>> test.save(FIGURE_FOLDER + 'PlotData_cmpare_hist.png')
+
+ We can also plot the kernel density estimation over the plot :
+
+ >>> from fovea_toolbox.post_proc import find_gauss_fit
+ >>> result = find_gauss_fit(data, 1024)
+ >>> test.plot(plot_data)
+ >>> test.show_fit(result)
+ >>> test.save(FIGURE_FOLDER + 'PlotData_fit.png')
+
+ You can also define some groups and plot the data according to the group
+ they belong to.
+
+ >>> from numpy.random import rand
+ >>> data = randn(100)
+ >>> data2 = rand(100)
+ >>> data3 = randn(100) * rand(100)
+ >>> merged = list(data)+list(data2)+list(data3)
+ >>> group = [0 for i in range(100)] + \
+ [1 for i in range(100)] + \
+ [2 for i in range(100)]
+
+ >>> plot_data = PlotData()
+ >>> plot_data.set_data('force', merged)
+ >>> plot_data.set_group('force', group)
+ >>> plot_data.set_range('force', [-5, 5, 0.1])
+ >>> test.plot(plot_data)
+ >>> test.save(FIGURE_FOLDER + 'PlotData_with_group.png')
+ """
+
+ def __init__(self, xlabel = '...', title='Histogram', data_type='', y_rel=False):
+ Plot.__init__(self)
+ self.type = 'histogram'
+ self.xlabel = xlabel
+ self.axis2 = None
+ self.title = title
+ self.data = None
+ self.data2 = None
+ self.label, self.label2 = None, None
+ self.index = None
+ self.data_type = data_type
+ self.max_hist = None # Stores the maximum of histogram to scale the fit.
+ self.range = None # Stores the x_range of the histogram.
+ self.relative = y_rel # The y axis in relative or absolute mode.
+ self.text_annotation = {'list' : list(),
+ 'grabbed' : None,
+ 'clicked' : [0,0]}
+ def plot(self, data, data2 = None, label = None, label2 = None,
+ hist_range = None, color_type = 'color', y2=False):
+ """
+ Plots the histogram.
+ """
+ frequency_factor = 1
+ if isinstance(data, PlotData):
+ self.range = data.range
+ if self.relative and (data.nbr_curves is not None):
+ frequency_factor = 1 / float(data.nbr_curves)
+ else:
+ frequency_factor = 1
+ group = data.group
+ group_label = data.get_group_prop('label')
+ group_visible = data.get_group_prop('display')
+ group_id = data.get_group_prop('id')
+ data = data.data
+ else:
+ group = [0 for i in range(len(data))]
+ group_label = None
+ group_visible = [True]
+ group_id = [0]
+ self.range = hist_range
+ if isinstance(data2, PlotData):
+ if self.relative and (data2.nbr_curves is not None):
+ frequency_factor2 = 1 / float(data2.nbr_curves)
+ else:
+ frequency_factor2 = 1
+ group2 = data2.group
+ data2 = data2.data
+ elif data2 is not None:
+ group2 = [0 for i in range(len(data2))]
+ frequency_factor2 = 1
+ group = numpy.asanyarray(group)
+ data = numpy.asanyarray(data)
+ index = numpy.isfinite(data)
+ self.label = label
+ self.label2 = label2
+ if self.label is None:
+ self.label = '...'
+ if self.label2 is None:
+ self.label = '...'
+ if color_type == 'color':
+ color = ['blue', 'red']
+ edgecolor = ['black', 'black']
+ elif color_type == 'bw':
+ color = ['black', 'white']
+ edgecolor = ['black', 'black']
+ group = group[index]
+ list_group = [group_id[i] for i in numpy.nonzero(group_visible)[0]]
+ #list_group = numpy.unique(group)
+ self.data = data[index]
+ if data2 is not None:
+ group2 = numpy.asanyarray(group2)
+ data2 = numpy.asanyarray(data2)
+ index = numpy.isfinite(data2)
+ self.data2 = data2[index]
+ self.axis.cla()
+ self.axis.hold(True)
+ if self.axis2 is not None:
+ self.axis2.cla()
+ self.axis2.hold(True)
+ self.axis.cla()
+ if data2 is None:
+ self.axis2.yaxis.set_visible(False)
+ else:
+ self.axis2.yaxis.set_visible(True)
+ if self.range == None:
+ if data2 is None:
+ for indice in range(group.min(), group.max()+1):
+ pl_x = self.axis.hist(self.data[group==indice], alpha=0.5)
+ else:
+ pl_x = self.axis.hist(self.data)
+ if data2 is not None:
+ pl_x2 = self.axis.hist(self.data2, alpha=0.5)
+ else:
+ index = (self.range[0] < self.data) * (self.data < self.range[1])
+ if data2 is None:
+ _i = 0
+ for item in list_group:
+ indice = index * (group == item)
+ pl_x = numpy.histogram(self.data[indice],
+ numpy.arange(self.range[0],
+ self.range[1],
+ self.range[2]))
+ pl_x = (pl_x[0] * frequency_factor, pl_x[1])
+ self.axis.bar(pl_x[1][:-1],
+ pl_x[0],
+ color=self.colors[_i],
+ width=self.range[2],
+ label=group_label[item],#'Group ' + str(item),
+ alpha=0.5)
+ _i += 1
+ else:
+ pl_x = numpy.histogram(self.data[index],
+ numpy.arange(self.range[0],
+ self.range[1],
+ self.range[2]))
+ pl_x = (pl_x[0] * frequency_factor, pl_x[1])
+
+ if y2:
+ if self.axis2 is None:
+ self.axis2 = self.axis.twinx()
+ axis = self.axis2
+ self.axis.yaxis.tick_left()
+ axis.yaxis.tick_right()
+ else:
+ axis = self.axis
+ bar1 = self.axis.bar(pl_x[1][:-1],
+ pl_x[0],
+ width=self.range[2],
+ label=self.label,
+ alpha=0.5,
+ color=color[0],
+ edgecolor=edgecolor[0])
+
+ index = ((self.range[0] < self.data2) *
+ (self.data2 < self.range[1]))
+ self.data2 = self.data2[index]
+
+
+ pl_x2 = numpy.histogram(self.data2,
+ numpy.arange(self.range[0],
+ self.range[1],
+ self.range[2]))
+ pl_x2 = (pl_x2[0] * frequency_factor2, pl_x2[1])
+ bar2 = axis.bar(pl_x2[1][:-1],
+ pl_x2[0],
+ width=self.range[2],
+ label=self.label2,
+ alpha=0.5,
+ color=color[1],
+ edgecolor=edgecolor[1])
+ __max = max(max(pl_x[0]), max(pl_x2[0]))
+ __max = __max + __max*5/100.
+ if not y2:
+ self.axis.set_ylim(0, __max)
+ #if label2 is not None:
+ if len(list_group) > 1 or data2 is not None:
+ if y2:
+ leg = plt.legend((bar1[0], bar2[0]),
+ (self.label, self.label2),
+ 'best')
+ self.axis2.set_ylabel(self.label2)
+ self.axis.set_ylabel(self.label)
+ else:
+ leg = self.axis.legend(loc='best')
+ if leg is not None:
+ # If there is a legend... otherwise, make nothing.
+ frame = leg.get_frame()
+ frame.set_alpha(0.4)
+ if len(pl_x[0]):
+ self.max_hist = max(pl_x[0])
+ else :
+ self.max_hist = self.range[1]
+ self.axis.set_xlabel(self.xlabel)
+ if self.range is not None:
+ self.axis.set_xlim(self.range[0], self.range[1])
+
+ def show_fit(self, prop):
+ self.text_annotation['list'] = list()
+ prop['kde'][1] = (prop['kde'][1] * self.max_hist /
+ max(prop['kde'][1]) * 0.9)
+ prop['maxima'][1] = (prop['maxima'][1] * self.max_hist /
+ max(prop['maxima'][1]) * 0.9)
+ self.axis.plot(prop['kde'][0], prop['kde'][1], linewidth = 1.5)
+ for [p_x, p_y, mean, std, count] in zip(prop['maxima'][0],
+ prop['maxima'][1],
+ prop['mean'],
+ prop['std'],
+ prop['count']):
+
+ text = "Mean : " + str(round(mean, 4)) + "\n" +\
+ "Std : " + str(round(std, 4)) + "\n" +\
+ "Count : " + str(count)
+ self.text_annotation['list'].append(
+ self.axis.annotate("Peak : " + str(round(p_x, 4)) + "\n" + text,
+ xy=(p_x, p_y), xytext=(p_x, p_y + p_y/20),
+ textcoords='data',
+ arrowprops=dict(arrowstyle="->",
+ connectionstyle="arc3"),
+ bbox=dict(boxstyle="round",
+ fc="0.8",
+ alpha=0.5)
+ )
+ )
+ if self.range is not None:
+ self.axis.set_xlim(self.range[0], self.range[1])
+ self.figure.canvas.mpl_connect('button_press_event',
+ self.grab_annotation)
+ self.figure.canvas.mpl_connect('button_release_event',
+ self.release_annotation)
+ self.figure.canvas.mpl_connect('motion_notify_event',
+ self.move_annotation)
+ def grab_annotation(self, event):
+ for item in self.text_annotation['list']:
+ text_pos = item.get_position()
+ bbox = item.get_bbox_patch()
+ if text_pos[0] < event.x < text_pos[0] + bbox.get_width():
+ if text_pos[1] < event.y < text_pos[1] + bbox.get_height():
+ self.text_annotation['grabbed'] = item
+ if event.inaxes:
+ self.text_annotation['clicked'] = [
+ event.xdata - item.xytext[0],
+ event.ydata - item.xytext[1]]
+ else:
+ self.text_annotation['clicked'] = [0,0]
+ def release_annotation(self, event):
+ self.text_annotation['grabbed'] = None
+ def move_annotation(self, event):
+ #print "...", self.text_annotation['grabbed']
+ if self.text_annotation['grabbed'] is not None:
+ try:
+ if event.inaxes:
+ pos_x = event.xdata - self.text_annotation['clicked'][0]
+ pos_y = event.ydata - self.text_annotation['clicked'][1]
+ self.text_annotation['grabbed'].xytext = (pos_x , pos_y)
+ except TypeError:
+ pass
+class Scatter(Plot):
+ """
+ Displays a scatter plot of the data provided.
+ The data are PlotData objects.
+
+ >>> from numpy.random import randn
+ >>> data = randn(1000)
+ >>> data2 = randn(1000)*2 + 1.5
+
+ >>> plot_data = PlotData()
+ >>> plot_data.set_data('force', data)
+ >>> plot_data.set_data('lr', data2)
+ >>> plot_data.set_range('force', [-5, 5, 0.1])
+ >>> plot_data.set_range('lr', [-5, 10, 0.2])
+ >>> plot_data.set_group_prop('all', {'id': [0], 'label' : ['0'], 'display' : [True]})
+
+ >>> test = Scatter(xlabel='data1', ylabel='data2', title='test of scatter')
+ >>> #print plot_data.get_data('force')
+ >>> test.plot(plot_data, ['force', 'lr'])
+ >>> test.save(FIGURE_FOLDER + 'PlotData_Scatter.png')
+ >>> test.type = "Density"
+ >>> test.refresh()
+ >>> test.save(FIGURE_FOLDER + 'PlotData_Density.png')
+
+ You can also define some groups and plot the data according to the group
+ they belong to.
+
+ >>> from numpy.random import rand
+ >>> data = randn(100)
+ >>> data2 = rand(100)
+ >>> data3 = randn(100) * rand(100)
+ >>> merged = list(data)+list(data2)+list(data3)
+ >>> group = [0 for i in range(100)] + \
+ [1 for i in range(100)] + \
+ [2 for i in range(100)]
+
+ >>> data4 = randn(100)
+ >>> data5 = rand(100)
+ >>> data6 = randn(100) * rand(100)
+ >>> merged2 = list(data4)+list(data5)+list(data6)
+ >>> group2 = [0 for i in range(100)] + \
+ [1 for i in range(100)] + \
+ [2 for i in range(100)]
+
+ >>> plot_data = PlotData()
+
+ >>> plot_data.set_data('force', merged)
+ >>> plot_data.set_group('force', group)
+ >>> plot_data.set_range('force', [-5, 5, 0.1])
+
+ >>> plot_data.set_data('lr', merged2)
+ >>> plot_data.set_group('lr', group2)
+ >>> plot_data.set_range('lr', [-5, 5, 0.1])
+ >>> plot_data.set_group_prop('all', {'id': [0], 'label' : ['0'], 'display' : [True]})
+
+ >>> test = Scatter(xlabel='data1', ylabel='data2', title='test of scatter')
+ >>> test.plot(plot_data, ['force', 'lr'])
+ >>> test.save(FIGURE_FOLDER + 'PlotData_Scatter_with_group.png')
+ """
+
+ def __init__(self, xlabel='...', ylabel='...', title=''):
+ Plot.__init__(self)
+ self.xlabel = xlabel
+ self.ylabel = ylabel
+ self.type = 'scatter'
+ self.markers = ['o', # circle
+ 's', # square
+ '^', # triangle up
+ '>', # triangle right
+ 'v', # triangle down
+ '<', # triangle left
+ 'd', # diamond
+ 'p', # pentagram
+ 'h', # hexagon
+ '8', # octagon
+ '+', # plus
+ 'x', # cross
+ ]
+ self.data_x, self.data_y = None, None # Store the variables
+ self.range_x, self.range_y = None, None # Store the ranges
+ self.group_x, self.group_y = None, None # Store the groups
+
+ # definitions for the axes
+
+ # h-size
+ # <-----width------> <---->
+ # +----------------+ ^
+ # | hist_x | |
+ # | | | h-size
+ # +----------------+ v
+ # +----------------+ +----+ ^
+ # | | | h | |
+ # | | | i | |
+ # | Scatter | | s | |
+ # | | | t | |
+ # | | | - | |
+ # | | | y | |
+ # left +----------------+ +----+ v
+ # bottom <->
+ # plot_dist
+ left, width = 0.1, 0.65
+ bottom, height = 0.1, 0.65
+ h_size = 0.2
+ bottom_h = left_h = left + width + 0.02
+
+ rect_scatter = [left, bottom, width, height]
+ rect_histx = [left, bottom_h, width, h_size]
+ rect_histy = [left_h, bottom, h_size, height]
+
+ self.figure.clf()
+
+ self.axis_scatter = self.figure.add_axes(rect_scatter)
+ self.axis_histx = self.figure.add_axes(rect_histx)
+ self.axis_histy = self.figure.add_axes(rect_histy)
+ self.clear()
+ def clear(self):
+ """
+ Reinitialize the plots.
+ """
+
+ self.axis_histx.cla()
+ self.axis_histy.cla()
+ # no labels
+ self.axis_histx.xaxis.set_major_formatter(NULLFMT)
+ self.axis_histy.yaxis.set_major_formatter(NULLFMT)
+ def plot(self, data, data_type):
+ """
+ Plot the data.
+ The data has to be a PlotData object.
+ data_type is a list of two string representing the data to plot.
+ """
+ self.clear()
+ self.data_x = numpy.asarray(data.get_data(data_type[0]))
+ self.data_y = numpy.asarray(data.get_data(data_type[1]))
+ self.range_x = data.get_range(data_type[0])
+ self.range_y = data.get_range(data_type[1])
+ self.group_x = data.get_group(data_type[0])
+ self.group_y = data.get_group(data_type[1])
+ self.group_prop = { 'id' : data.get_group_prop('id'),
+ 'label' : data.get_group_prop('label'),
+ 'display' : data.get_group_prop('display')}
+ if self.type == 'scatter':
+ self.__scatter()
+ elif self.type == 'density':
+ self.__density()
+ else:
+ return
+
+ x_index = ((self.range_x.min < self.data_x) *
+ (self.data_x < self.range_x.max))
+ y_index = ((self.range_y.min < self.data_y) *
+ (self.data_y < self.range_y.max))
+ self.axis_histx.hist(self.data_x[x_index],
+ numpy.arange(self.range_x.min,
+ self.range_x.max,
+ self.range_x.bin_size)
+ )
+ self.set_xscale(plot='hist')
+ self.axis_histy.hist(self.data_y[y_index],
+ numpy.arange(self.range_y.min,
+ self.range_y.max,
+ self.range_y.bin_size),
+ orientation='horizontal'
+ )
+ def refresh(self):
+ """
+ Refresh the graph without changing the data. Used to toogle between
+ scatter and density plot.
+ """
+
+ if self.type == 'scatter':
+ self.__scatter()
+ elif self.type == 'density':
+ self.__density()
+ else:
+ return
+
+ def __scatter(self):
+ """
+ Specific code to display the scatter plot.
+ """
+
+ #list_group = numpy.unique(self.group_x)
+ list_group = [i for i in numpy.nonzero(self.group_prop['display'])[0]]
+ list_group = numpy.asarray(list_group)
+ if len(list_group) > 1:
+ use_group = 1
+ x_g_index = [self.group_x == i for i in list_group]
+ y_g_index = [self.group_y == i for i in list_group]
+ else:
+ use_group = 0
+ self.axis_scatter.cla()
+ if use_group:
+ for i in list_group:
+ try:
+ index = list(list_group).index(i)
+ self.axis_scatter.scatter(self.data_x[x_g_index[index]],
+ self.data_y[y_g_index[index]],
+ label=self.group_prop['label'][i],
+ #label='Group ' + str(i),
+ c=self.colors[index],
+ marker=self.markers[index],
+ alpha=0.5)
+ except ValueError, msg:
+ if msg.args[0] == \
+ 'zero-size array to ufunc.reduce without identity':
+ pass
+ else:
+ raise ValueError, msg
+ else:
+ leg = self.axis_scatter.legend(loc='best')
+ frame = leg.get_frame()
+ frame.set_alpha(0.4)
+ else:
+ self.axis_scatter.scatter(self.data_x, self.data_y, alpha=0.5)
+ self.axis_scatter.set_xlabel(self.xlabel)
+ self.axis_scatter.set_ylabel(self.ylabel)
+ self.set_xscale(plot='scatter')
+ self.axis_scatter.axis([self.range_x.min, self.range_x.max,
+ self.range_y.min, self.range_y.max])
+
+ def __density(self):
+ """
+ Specific code to display the density plot.
+ """
+
+ self.axis_scatter.cla()
+ ## make the density plot
+ # Beginn...
+ finite = numpy.isfinite(self.data_x) * numpy.isfinite(self.data_y)
+ in_range = ((self.data_x < self.range_x.max) *
+ (self.data_x > self.range_x.min) *
+ (self.data_y < self.range_y.max) *
+ (self.data_y > self.range_y.min))
+ index = finite * in_range
+
+ vect_x = ((self.data_x - self.range_x.min) /
+ (self.range_x.max - self.range_x.min)) * 128
+
+ vect_y = ((self.data_y - self.range_y.min) /
+ (self.range_y.max - self.range_y.min)) * 128
+
+ lin_vect = numpy.asanyarray([vect_x[index], vect_y[index]])
+ kde = stats.kde.gaussian_kde(lin_vect)
+ # Regular grid to evaluate kde upon
+ mesh = numpy.mgrid[0:128, 0: 128]
+ grid_coords = numpy.append(mesh[0].reshape(-1, 1),
+ mesh[1].reshape(-1, 1),
+ axis=1)
+ arr_z = kde(grid_coords.T)
+ arr_z = arr_z.reshape(128, 128)
+ self.axis_scatter.imshow(arr_z.transpose(1, 0),
+ origin='lower',
+ extent=(self.range_x.min,
+ self.range_x.max,
+ self.range_y.min,
+ self.range_y.max),
+ aspect='auto')
+ self.axis_scatter.set_xlabel(self.xlabel)
+ self.axis_scatter.set_ylabel(self.ylabel)
+ #self.set_xscale()
+ def set_xscale(self, scale=None, plot=None):
+ if scale is not None:
+ self.scale[0] = scale
+ if plot == 'scatter':
+ self.axis_scatter.set_xscale(self.scale[0])
+ self.axis_scatter.axis([self.range_x.min, self.range_x.max,
+ self.range_y.min, self.range_y.max])
+ elif plot == 'hist':
+ self.axis_histx.set_xscale(self.scale[0])
+ self.axis_histx.set_xlim(self.range_x.min, self.range_x.max)
+class Tomo(Plot):
+ """
+ Plot the interior of a 3D array.
+
+ >>> from numpy.random import randn
+ >>> data = randn(64, 64, 100)
+
+ Consider that the data are recorded on a square of 2µm per 2µm
+
+ >>> test = Tomo(data, [2000, 2000])
+ >>> test.plot([20, 10, 50]) # Let's plot the position x=20, y=10, z=50
+ >>> test.save(FIGURE_FOLDER + 'Tomography.png')
+ """
+
+ def __init__(self, tomo_array, scan_size=None):
+ Plot.__init__(self)
+ self.type='tomography'
+ # Defines the axes :
+ # plot_dist
+ # <->
+ # <-----width------> <-----width------>
+ # +----------------+ ^
+ # | | |
+ # | | |
+ # | display Y | | Height
+ # | | |
+ # | | |
+ # | | | ^
+ # bottom_2 +----------------+ v plot_dist
+ # +----------------+ +----------------+ ^ v
+ # | | | | |
+ # | | | | |
+ # | display X | | display Z | | Height
+ # | | | | |
+ # | | | | |
+ # | | | | |
+ # bottom +----------------+ +----------------+ v
+ # ^
+ # left L left_2
+
+ width, height = 0.35, 0.35
+ left, bottom = 0.1, 0.1
+ plot_dist = 0.1
+ bottom_2 = bottom + width + plot_dist
+ left_2 = left + height + plot_dist
+
+ rect = {'display_x' : [left, bottom, width, height],
+ 'display_y' : [left, bottom_2, width, height],
+ 'display_z' : [left_2, bottom, width, height]}
+ self.figure.clf()
+ self.axis = dict()
+ self.cursor = dict()
+ for key, rectangle in zip(rect.keys(), rect.values()):
+ self.axis[key] = self.figure.add_axes(rectangle)
+ self.cursor[key] = Cursor(self.axis[key], useblit=True,
+ color='green', alpha=0.3, linewidth=2)
+
+ ## Define the labels
+ self.unit = ['', '', '']
+ self.scale = {'x' : None,
+ 'y' : None,
+ 'z' : None,
+ 'scan_size' : None}
+ self.pixel_size = [None, None]
+ self.__set_label(tomo_array.shape, scan_size)
+
+ ##
+ self.tomo_array = tomo_array
+ self.slice = [0, 0, 0] # the current slice [x, y, z]
+ self.colorScaleMax = self.tomo_array.max()
+ self.colorScaleMin = 0
+
+ def plot(self, position, color_scale_min=None, color_scale_max=None):
+ if color_scale_min is not None:
+ self.colorScaleMin = color_scale_min
+ if color_scale_max is not None:
+ self.colorScaleMax = color_scale_max
+ self.slice = position
+ self.plot_x()
+ self.plot_y()
+ self.plot_z()
+ def plot_x(self):
+ '''
+ Refresh the x slice plot
+ '''
+ self.axis['display_x'].cla()
+ self.axis['display_x'].pcolor(
+ self.scale['y'], self.scale['z'],
+ self.tomo_array[self.slice[0],
+ :,
+ :].transpose(),
+ vmin = self.colorScaleMin,
+ vmax = self.colorScaleMax)
+ self.axis['display_x'].set_xlabel('Size' + self.unit[0])
+ def plot_y(self):
+ '''
+ Refresh the y slice plot
+ '''
+ self.axis['display_y'].cla()
+ self.axis['display_y'].pcolor(
+ self.scale['x'], self.scale['z'],
+ self.tomo_array[:,
+ self.slice[1],
+ :].transpose(),
+ vmin = self.colorScaleMin,
+ vmax = self.colorScaleMax)
+ self.axis['display_y'].set_xlabel('Size' + self.unit[1])
+ def plot_z(self):
+ '''
+ Refresh the z slice plot
+ '''
+ self.axis['display_z'].cla()
+ self.axis['display_z'].pcolor(
+ self.scale['x'], self.scale['y'],
+ self.tomo_array[:,
+ :,
+ self.slice[2]].transpose(),
+ vmin = self.colorScaleMin,
+ vmax = self.colorScaleMax)
+ self.axis['display_z'].set_xlabel('Size ' + self.unit[2])
+ self.axis['display_z'].set_ylabel('Size ' + self.unit[2])
+ def __set_label(self, shape, scan_size=None):
+ _scan_size = list(scan_size)
+ if scan_size is not None:
+ for dim in range(2):
+ if scan_size[dim] < 1000:
+ self.unit[dim] = '[nm]'
+ elif scan_size[dim] >= 1000:
+ self.unit[dim] = r'[$\mathregular{\mu}$m]'
+ _scan_size[dim] = _scan_size[dim] / 1000.
+ self.scale['x'] = numpy.arange(0,
+ _scan_size[0] + _scan_size[0] / shape[0],
+ _scan_size[0] / shape[0])
+ self.scale['y'] = numpy.arange(0,
+ _scan_size[1] + _scan_size[1] / shape[1],
+ _scan_size[1] / shape[1])
+ self.scale['z'] = numpy.arange(0, shape[2])
+ self.pixel_size = [_scan_size[0] / shape[0], _scan_size[1] / shape[1]]
+ else:
+ self.scale['x'] = numpy.arange(0, shape[0])
+ self.scale['y'] = numpy.arange(0, shape[1])
+ self.scale['z'] = numpy.arange(0, shape[2])
+ self.pixel_size = [1,1]
+
+class InteractiveLinePlot(Plot):
+ """
+ Plots several lines in as many plots as the number of lines.
+
+ * Parameters :
+ array_x : array_like
+ The x array that is common for all plots.
+ arrays : list of array_like
+ The y arrays that defines the plots.
+ label : list of str
+ The label that will be display to identify the plots.
+
+ * Output :
+
+ >>> import numpy
+ >>> array_x = numpy.arange(0, 6, 0.1)
+ >>> array_1 = array_x * 2
+ >>> array_2 = numpy.sin(array_x)
+ >>> array_3 = numpy.cos(array_x)
+ >>> test = InteractiveLinePlot()
+ >>> test.plot(array_x, [array_1, array_2, array_3],\
+ ['linear', 'cos', 'sin'])
+ """
+
+ def __init__(self):
+ Plot.__init__(self)
+ self.figure.clf()
+
+ self.figure.canvas.mpl_connect('button_press_event',
+ self.button_press_event)
+ self.figure.canvas.mpl_connect('motion_notify_event',
+ self.mouse_move)
+ self.figure.canvas.mpl_connect('button_release_event',
+ self.button_release_event)
+ self.figure.canvas.mpl_connect('pick_event', self.on_pick)
+ self.array_x = None
+ self.arrays_y = None
+ self.picked = None
+ self.info = None
+ self.plot_list = None
+ def button_press_event(self, event):
+ """
+ Function called when user click on the figure.
+ It hides visible graph we click on or display hidden graph we click
+ on.
+ """
+
+ if event.inaxes in self.axis:
+ if event.button == 3:
+
+ # right click
+ modify = self.axis.index(event.inaxes)
+ self.toggle_display(modify)
+ elif event.button == 1:
+ self.info_display(event)
+ def button_release_event(self, event):
+ self.picked = None
+ def toggle_display(self, axis_nbr):
+ """
+ Display or hide the axis.
+ """
+
+ if self.axis[axis_nbr].get_visible():
+ # We hide this subplot and push all ones below to replace it.
+ # The hidden plot goes to the end of the display.
+ self.axis[axis_nbr].set_visible(False)
+ cur = self.axis[axis_nbr].get_geometry()
+ for item in self.axis:
+ tgeo = item.get_geometry()
+ if tgeo[2] > cur[2]:
+ item.change_geometry(tgeo[0], tgeo[1], tgeo[2]-1)
+
+ self.axis[axis_nbr].change_geometry(cur[0], cur[1],
+ len(self.axis))
+ else:
+ self.axis[axis_nbr].set_visible(True)
+ def plot(self, array_x, arrays, label):
+ """
+ Fuction called to display the graph.
+ """
+ self.figure.clf()
+ self.axis = []
+ self.array_x = array_x
+ self.arrays_y = arrays
+ self.arrows = {'begin' : [None for i in range(len(arrays))],
+ 'end' : [None for i in range(len(arrays))]}
+ tot_nbr = len(arrays)
+ self.info = []
+ for nbr in range(1, len(arrays)+1):
+ self.axis.append(self.figure.add_subplot(
+ int(str(tot_nbr) + str(1) + str(nbr))
+ ))
+ self.info.append(
+ self.axis[nbr-1].annotate('Right click on axes to get info',
+ xy=[1.05, 0.7], xycoords = 'axes fraction',
+ horizontalalignment = 'left',
+ verticalalignment = 'center',
+ fontsize = 10,
+ #fontweight = 'bold',
+ bbox=dict(boxstyle="round", fc="0.8", alpha=0.8),
+ visible = True
+ )
+ )
+ self.plot_list = list()
+ for array, axis, label in zip(arrays, self.axis, label):
+ self.plot_list.append(axis.plot(array_x, array))
+ at = AnchoredText(label, loc=9, prop=None, pad=0., borderpad=0.5,
+ frameon=False)
+ axis.add_artist(at)
+ self.figure.subplots_adjust(left=0.05, right=0.6)
+ def modify_plot(self, array_x, arrays):
+ self.array_x = array_x
+ self.arrays_y = arrays
+ for plots, array_y, axis in zip(self.plot_list, arrays, self.axis):
+ plots[0].set_data(array_x, array_y)
+ axis.set_xlim(min(array_x), max(array_x))
+ axis.set_ylim(min(array_y), max(array_y))
+ def info_display(self, event):
+ axis_nbr = self.axis.index(event.inaxes)
+ index = numpy.nonzero(self.array_x >= event.xdata)[0][0]
+ pos = [self.array_x[index], self.arrays_y[axis_nbr][index]]
+ if self.arrows['begin'][axis_nbr] == None:
+ self.create_arrow(index, 'begin')
+ if self.arrows['end'][axis_nbr] == None:
+ self.create_arrow(index, 'end')
+ else:
+ self.move_arrow(index)
+ self.get_info()
+ def create_arrow(self, index, side):
+ for axis_nbr in range(len(self.axis)):
+ lim = self.axis[axis_nbr].get_ylim()
+ length = (lim[1]-lim[0]) / 10.
+ lim = self.axis[axis_nbr].get_xlim()
+ width = (lim[1]-lim[0]) / 50.
+ if type(self.arrays_y[axis_nbr][0]) == numpy.ma.core.MaskedArray:
+ pos = [self.array_x[index], self.arrays_y[axis_nbr][0][index]]
+ else:
+ pos = [self.array_x[index], self.arrays_y[axis_nbr][index]]
+ arrows = [FancyArrow(pos[0], pos[1] + length,
+ 0, -length,
+ length_includes_head=True,
+ head_length=length, head_width=None,
+ width=width, picker=5),
+ FancyArrow(pos[0], pos[1] - length,
+ 0, length,
+ length_includes_head=True,
+ head_length=length, head_width=None,
+ width=width, picker=5)]
+ self.arrows[side][axis_nbr] = arrows
+ self.axis[axis_nbr].add_patch(self.arrows[side][axis_nbr][0])
+ self.axis[axis_nbr].add_patch(self.arrows[side][axis_nbr][1])
+ def move_arrow(self, index):
+ if self.picked == None:
+ return
+ for axis_nbr in range(len(self.axis)):
+ if type(self.arrays_y[axis_nbr][0]) == numpy.ma.core.MaskedArray:
+ pos = [self.array_x[index], self.arrays_y[axis_nbr][0][index]]
+ else:
+ pos = [self.array_x[index], self.arrays_y[axis_nbr][index]]
+ init_pos = self.arrows[self.picked][axis_nbr][0].get_xy()
+ diff = [pos[0] - init_pos[0][0], pos[1] - init_pos[0][1]]
+ init_pos += diff
+ init_pos = self.arrows[self.picked][axis_nbr][1].get_xy()
+ init_pos += diff
+ def mouse_move(self, event):
+ if not event.inaxes:
+ return
+ if event.button == 1:
+ self.info_display(event)
+ def on_pick(self, event):
+ nbr_graph = len(self.axis)
+ for gnb in range(nbr_graph):
+ res = (self.arrows['begin'][gnb][0] == event.artist) + (self.arrows['begin'][gnb][1] == event.artist)
+ if res:
+ self.picked = 'begin'
+ res = False
+ try:
+ res = (self.arrows['end'][gnb][0] == event.artist) + (self.arrows['end'][gnb][1] == event.artist)
+ except:
+ pass
+ if res:
+ self.picked = 'end'
+ def get_info(self):
+ xdata = [self.arrows['begin'][0][0].get_xy()[0][0],
+ self.arrows['end'][0][0].get_xy()[0][0]]
+ indexes = [numpy.nonzero(self.array_x >= xdata[0])[0][0],
+ numpy.nonzero(self.array_x >= xdata[1])[0][0]]
+ for axis in range(len(self.axis)):
+ text = self.create_info_text(axis, indexes)
+ self.info[axis].set_text(text)
+ def create_info_text(self, axis_nbr, indexes):
+ if type(self.arrays_y[axis_nbr][0]) == numpy.ma.core.MaskedArray:
+ pos = [[self.array_x[indexes[0]], self.arrays_y[axis_nbr][0][indexes[0]]],
+ [self.array_x[indexes[1]], self.arrays_y[axis_nbr][0][indexes[1]]]]
+ else:
+ pos = [[self.array_x[indexes[0]], self.arrays_y[axis_nbr][indexes[0]]],
+ [self.array_x[indexes[1]], self.arrays_y[axis_nbr][indexes[1]]]]
+
+ text = "Start Point : %.2f, %.2f\n"%(pos[0][0], pos[0][1]) +\
+ "End Point : %.2f, %.2f\n"%(pos[1][0], pos[1][1]) +\
+ "Horizontal distance : %.2f\n"%(pos[1][0]-pos[0][0]) +\
+ "Vertical distance : %.2f"%(pos[1][1]-pos[0][1])
+ return text
+
+class PlotData(object):
+ """
+ Stores the data and the propeties specific to this data set.
+ """
+ def __init__(self):
+ self.group_prop = {'id' : [],
+ 'label' : [],
+ 'display' : [],
+ 'set' : False}
+ self.group_dict = dict()
+ self.data_dict = dict()
+ self.range_dict = dict()
+ self.fileid_dict = dict()
+ self.coord_dict = dict()
+ for item in PLOT_DATA:
+ prop = PLOT_DATA[item]
+ self.group_dict[item] = prop[0]
+ self.data_dict[item] = prop[1]
+ self.range_dict[item] = prop[2]
+ self.fileid_dict[item] = prop[3]
+ self.coord_dict[item] = prop[4]
+ self.label = None
+ self.nbr_curves = None
+ self.type = 'force'
+
+ def __getattr__(self, att):# pylint disable-msg=R0911
+ if att is 'data':
+ if self.type in PLOT_DATA_TYPE['simple']:
+ return self.data_dict[self.type]
+ elif self.type in PLOT_DATA_TYPE['dist']:
+ return self.data_dict[self.type][
+ self.range_dict[self.type].dist]
+
+ elif att is 'range':
+ return self.range_dict[self.type].range
+ elif att is 'group':
+ if self.type in PLOT_DATA_TYPE['simple']:
+ return self.group_dict[self.type]
+ elif self.type in PLOT_DATA_TYPE['dist']:
+ return self.group_dict[self.type][
+ self.range_dict[self.type].dist]
+ def get_group_prop(self, gtype):
+ """
+ Get the group property.
+ Parameters:
+ gtype : str
+ 'id'
+ 'label'
+ 'display'
+ """
+ if self.group_prop['set']:
+ return self.group_prop[gtype]
+ else:
+ if gtype == 'id':
+ return numpy.unique(self.group)
+ elif gtype == 'label':
+ return ["Groupe %i"%numpy.unique(self.group)[0]]
+ elif gtype == 'display':
+ return [True]
+ else:
+ raise TypeError, "%s is not recognized attribute."
+ def set_group_prop(self, gtype, data):
+ """
+ Set the group property.
+ Parameter:
+ gtype : str
+ data : list
+ """
+ if gtype == 'all':
+ if data is not None:
+ self.group_prop = data
+ self.group_prop['set'] = True
+ else:
+ self.group_prop = {'id' : [],
+ 'label' : [],
+ 'display' : [],
+ 'set' : False}
+ else:
+ self.group_prop[gtype] = data
+ def set_range(self, data_type, d_range):
+ """
+ Set the range of the data to plot.
+ """
+ if data_type in PLOT_DATA.keys():
+ self.range_dict[data_type].set_range(d_range)
+ else:
+ raise TypeError, "Data type %s not known." % data_type
+ def set_dist(self, dist):
+ """
+ Set the distance at which we want to get the relative values.
+ """
+ for data_type in PLOT_DATA_TYPE['dist']:
+ self.range_dict[data_type].dist = dist
+ def set_data(self, data_type, data):
+ """
+ Store the data to plot.
+ """
+ if type(data) is list:
+ if data_type in PLOT_DATA_TYPE['dist']:
+ data = [numpy.array(i) for i in data]
+ else:
+ data = numpy.asanyarray(data)
+ if data_type in PLOT_DATA.keys():
+ self.data_dict[data_type] = data
+ if self.group_dict[data_type] is None:
+ # Group is not related to current data, then reinitialize it.
+ if data_type in PLOT_DATA_TYPE['dist']:
+ this_shape = [len(i) for i in data]
+ group = []
+ for item in this_shape:
+ group.append([0 for this in range(item)])
+ self.set_group(data_type, group)
+ else:
+ self.set_group(data_type, [0 for i in range(len(data))])
+ else:
+ raise TypeError, "Data type %s not known." % data_type
+ def set_group(self, data_type, group):
+ """
+ Store the group of the data to plot.
+ """
+ if data_type in PLOT_DATA.keys():
+ self.group_dict[data_type] = group
+ else:
+ raise TypeError, "Data type %s not known." % data_type
+ def set_fid(self, data_type, fid):
+ """
+ Store the file index of the data to plot.
+ """
+ if data_type in PLOT_DATA.keys():
+ self.fileid_dict[data_type] = fid
+ else:
+ raise TypeError, "Data type %s not known." % data_type
+ def set_coord(self, data_type, coord):
+ """
+ Store the coordinate of the data to plot.
+ """
+ if data_type in PLOT_DATA.keys():
+ self.coord_dict[data_type] = coord
+ else:
+ raise TypeError, "Data type %s not known." % data_type
+
+ def set_type(self, data_type):
+ """
+ Set the data type you want to plot
+ """
+
+ if data_type in PLOT_DATA.keys():
+ self.type = data_type
+ else:
+ raise TypeError
+ def __totake(self, data_type):
+ group = [self.group_prop['id'][i] for i in numpy.nonzero(self.group_prop['display'])[0]]
+ if data_type in PLOT_DATA_TYPE['simple']:
+ totake = numpy.asarray([sum([i==j for j in group]) for i in self.group_dict[data_type]], dtype=bool)
+ elif data_type in PLOT_DATA_TYPE['dist']:
+ totake = numpy.asarray([sum([i==j for j in group])
+ for i in self.group_dict[data_type][self.range_dict[data_type].dist]],
+ dtype=bool)
+ return totake
+ def get_data(self, data_type, group=None):
+ """
+ Get the data that is stored.
+ """
+ totake = self.__totake(data_type)
+ if data_type in PLOT_DATA_TYPE['simple']:
+ return self.data_dict[data_type][totake]
+ elif data_type in PLOT_DATA_TYPE['dist']:
+ try:
+ return self.data_dict[data_type][
+ self.range_dict[data_type].dist]#[totake]
+ except TypeError, message:
+ if str(message) == "'NoneType' object is unsubscriptable":
+ return None
+ def get_range(self, data_type):
+ """
+ Get the range of the data.
+ """
+ return self.range_dict[data_type]
+ def get_group(self, data_type):
+ """
+ Get the group of the data.
+ """
+ # change the type and store the old one
+ old_data_type, self.type = self.type, data_type
+
+ group = self.group
+
+ self.type = old_data_type
+
+ return group
+ def get_fid(self, data_type):
+ """
+ Get the file index of the data.
+ """
+ return self.fileid_dict[data_type]
+ def get_coord(self, data_type):
+ """
+ Get the file index of the data.
+ """
+ return self.coord_dict[data_type]
+
+class PlotRange(object): # pylint: disable-msg=R0903
+ """
+ Stores the range of the display
+ """
+ def __init__(self):
+ self.min = None
+ self.max = None
+ self.bin_size = None
+ self.dist = None
+ def __setattr__(self, att, value):
+ if att is 'min':
+ if self.max is not None:
+ self.__dict__[att] = min(value, self.max)
+ else:
+ self.__dict__[att] = value
+ elif att is 'max':
+ if self.min is not None:
+ self.__dict__[att] = max(value, self.min)
+ else:
+ self.__dict__[att] = value
+ else:
+ self.__dict__[att] = value
+ def __getattr__(self, att):
+ if att is 'range':
+ return [self.min, self.max, self.bin_size]
+ def set_range(self, d_range):
+ """
+ set the range of the data.
+ """
+ if d_range[0] is not None:
+ self.min = d_range[0]
+ if d_range[1] is not None:
+ self.max = d_range[1]
+ if d_range[2] is not None:
+ self.bin_size = d_range[2]
+ try:
+ if d_range[3] is not None:
+ self.dist = d_range[3]
+ except IndexError:
+ pass
+
+class PlotProperties(object): # pylint: disable-msg=R0903
+ """
+ Stores the plot properties
+ """
+ def __init__(self):
+ self.main_exp_label = None
+ self.comp_exp_label = None
+ self.color_type = 'bw'
+ self.plot_compare = False
+ self.min = None
+ self.max = None
+ self.bin_size = None
+ self.absolute = True
+ self.__type = {'Stiffness': 'jet',
+ 'Piezo': 'copper',
+ 'Topography': 'copper',
+ 'Event': 'gray',
+ 'Event force': 'gray',
+ 'Event distance': 'gray',
+ 'Event length': 'gray'}
+
+ def __setattr__(self, att, value):
+ if att is 'color_type':
+ if value in ['bw', 'black and white', 'BW']:
+ self.__dict__[att] = 'bw'
+ elif value in ['color', 'Color']:
+ self.__dict__[att] = 'color'
+ else:
+ self.__dict__[att] = value
+
+ def get_cmap(self, ptype):
+ if self.color_type == 'bw':
+ return 'gray'
+ else:
+ return self.__type[ptype]
+
+
+### Some useful functions
+def compute_array_stat(data):
+ """
+ Compute the statistic of the data for each time point.
+ For example :
+
+ >>> import numpy
+
+ # Let's generate an array of data filled with 1 and 2
+
+ >>> data = numpy.ones((15,10))
+ >>> data[10:] = 2
+
+ >>> indice = numpy.zeros(15)
+ >>> indice[10:] = 1
+ >>> result = compute_array_stat(data)
+
+ # here is the result
+
+ >>> result['mean']
+ array([ 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 2., 2., 2.,
+ 2., 2.])
+ >>> result['std']
+ array([ 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
+ 0., 0.])
+ >>> result['count']
+ array([ 10., 10., 10., 10., 10., 10., 10., 10., 10., 10., 10.,
+ 10., 10., 10., 10.])
+ """
+ mean = data.mean(1)
+ std = data.std(1)
+ if type(data) == numpy.ma.core.MaskedArray:
+ count = data.count(1)
+ else:
+ count = numpy.ones(data.shape[0]) * data.shape[1]
+ sem = std / numpy.sqrt(count)
+
+ result = {'mean' : mean,
+ 'std' : std,
+ 'count' : count,
+ 'sem' : sem}
+ return result
+
+def compute_indice_stat(data, indice):
+ """
+ Compute the statistic of the data, according to the indice.
+ For example :
+
+ >>> import numpy
+
+ # Let's generate an array of data filled with 1 and 2
+
+ >>> data = numpy.ones((150))
+ >>> data[100:] = 2
+
+ >>> indice = numpy.zeros(150)
+ >>> indice[100:] = 1
+ >>> result = compute_indice_stat(data, indice)
+
+ # here is the result
+
+ >>> result['indice']
+ array([ 0., 1.])
+ >>> result['mean']
+ array([ 1., 2.])
+ >>> result['std']
+ array([ 0., 0.])
+ >>> result['count']
+ array([ 100., 50.])
+
+ Let's make the same, but with on dimension vector.
+
+ >>> data.shape = 15 * 10
+ >>> indice = [0 for i in range(10 * 10)] + [1 for i in range(5 * 10)]
+
+ >>> result = compute_indice_stat(data, indice)
+
+ # here is the result
+
+ >>> result['indice']
+ array([0, 1])
+ >>> result['mean']
+ array([ 1., 2.])
+ >>> result['std']
+ array([ 0., 0.])
+ >>> result['count']
+ array([ 100., 50.])
+
+ """
+ indice_list = numpy.unique(indice)
+ mean = list()
+ std = list()
+ count = list()
+ sem = list()
+ indice_finite = numpy.isfinite(data)
+ for index in indice_list:
+ # doi_indice points to data with the good indice and which are finite
+ doi_indice = indice_finite * (indice == index)
+ doi = data[doi_indice] # doi means data of interrest
+ if len(doi):
+ mean.append(doi.mean())
+ std.append(doi.std())
+ else:
+ mean.append(numpy.nan)
+ std.append(numpy.nan)
+ if type(doi) == numpy.ma.core.MaskedArray:
+ count.append(doi.count())
+ elif len(doi.shape) == 2:
+ count.append(doi.shape[0] * doi.shape[1])
+ elif len(doi.shape) == 1:
+ count.append(doi.shape[0])
+ # change list to array_
+ mean, std, count = numpy.array(mean), numpy.array(std), numpy.array(count, dtype=float)
+ # Exclude values that have no count.
+ indice = count == 0.
+ mean[indice], std[indice], count[indice] = numpy.nan, numpy.nan, numpy.nan
+ sem = std / numpy.sqrt(count)
+ result = {'mean' : mean,
+ 'std' : std,
+ 'count' : count,
+ 'sem' : sem,
+ 'indice' : indice_list}
+ return result
+
+
+# PLOT_DATA stores the properties of standard properties.
+# [group_dict, data_dict, range_dict, fileid_dict, coord_dict]
+PLOT_DATA = {'force' : [None, None, PlotRange(), None, None],
+ 'force_base' : [None, None, PlotRange(), None, None],
+ 'lr' : [None, None, PlotRange(), None, None],
+ 'dist' : [None, None, PlotRange(),None, None],
+ 'stiff': [None, None, PlotRange(), None, None],
+ 'stiff_ev': [None, None, PlotRange(), None, None],
+ 'stiff_notev': [None, None, PlotRange(), None, None],
+ 'relstiff' : [None, None, PlotRange(), None, None],
+ 'relstiffctl' : [None, None, PlotRange(), None, None],
+ 'young_modulus': [None, None, PlotRange(), None, None],
+ 'length' : [None, None, PlotRange(), None, None],
+ 'plength' : [None, None, PlotRange(), None, None]}
+PLOT_DATA_TYPE = {'simple' : ['force', 'force_base', 'lr', 'dist', 'stiff',
+ 'stiff_ev', 'stiff_notev', 'length', 'plength'],
+ 'dist' : ['relstiff', 'relstiffctl']}
+
+if __name__ == '__main__':
+ import doctest
+ FIGURE_FOLDER = 'Tests/Graphs/'
+
+ doctest.testmod()
diff --git a/openfovea/plot_gtk.py b/openfovea/plot_gtk.py
new file mode 100644
index 0000000..a2292eb
--- /dev/null
+++ b/openfovea/plot_gtk.py
@@ -0,0 +1,702 @@
+#!/usr/bin/env python
+# -*- coding: UTF-8 -*-
+'''
+This module contains the necessary tools to plot graphics in gtk
+'''
+# TODO: Separate the generic plot from the GTK-only
+
+import pygtk
+#import gnome.ui
+import gtk
+# for script installation
+from pkg_resources import resource_filename
+#import pdb
+
+import numpy
+from scipy import stats
+##############
+## Graphic library ##
+##############
+try:
+ matplotlib.__version__
+except NameError:
+ import matplotlib
+ matplotlib.use('Agg')
+
+from matplotlib.figure import Figure
+from matplotlib.backends.backend_gtkagg import \
+ FigureCanvasGTKAgg as FigureCanvas
+from matplotlib.backends.backend_gtkagg import \
+ NavigationToolbar2GTKAgg as NavigationToolbar
+from matplotlib import cm # colormap
+from matplotlib.patches import Circle, Rectangle
+from matplotlib.image import imread
+from matplotlib.widgets import Cursor
+from matplotlib.ticker import NullFormatter
+nullfmt = NullFormatter()
+
+# Internal classes
+import classes
+import plot_generic
+#from fovea_toolbox.post_proc import gauss_fit, compute_gauss
+
+try:
+ no_data_filename = resource_filename('openfovea','Icon/NoData.png')
+except NotImplementedError:
+ no_data_filename = 'openfovea/Icon/NoData.png'
+
+class plot(object):
+ """
+ This is the generic plot class that will be inherited from the other.
+ It has no plot capabilities, but have the standard attribute.
+ """
+ def __init__(self, window, widget, plot_generic):
+ self.window = window
+ self.widget = widget
+ self.window.set_property('visible', True)
+ self.window.connect('destroy', self.on_win_plot_destroy)
+ self.plot_generic = plot_generic
+ self.axis = plot_generic.axis
+ self.figure = plot_generic.figure
+ self.canvas = FigureCanvas(self.figure) # a gtk.DrawingArea
+ self.canvas.show()
+ self.graphview = self.widget['plot_area']
+ self.graphview.pack_start(self.canvas, True, True)
+ # below is optional if you want the navigation toolbar
+ if 'box_nav_tool' in self.widget.keys():
+ self.navToolbar = FoveaNavToolbar(self.canvas, self.window)
+ self.navToolbar.plot_gen = self.plot_generic
+ self.navToolbar.lastDir = '/var/tmp/'
+ self.widget['box_nav_tool'].pack_start(self.navToolbar)
+ self.navToolbar.show()
+ #self.navToolbar.connect('button_press_event', self.press)
+ self.destroyed = 0
+ def show(self):
+ if not self.window.get_property('visible'):
+ raise StandardError
+ self.figure.subplots_adjust(left=0.2, bottom=0.12)
+ self.canvas.draw_idle()
+ def on_win_plot_destroy(self, *signal):
+ self.destroyed = 1
+ self.window.destroy()
+ def hold(self, value):
+ """
+ Change the hold of the figure.
+ """
+ self.plot_generic.hold(value)
+ def set_size(self, size, size2=None):
+ """
+ Change the size of the figure.
+ """
+ if size2 == None:
+ self.plot_generic.set_size(size[0], size[1])
+ else:
+ self.plot_generic.set_size(size, size2)
+ def set_xscale(self, scale):
+ self.plot_generic.set_xscale(scale)
+ self.show()
+ def get_type(self):
+ return self.plot_generic.get_type()
+class plotCurves(plot):
+ def __init__(self, window, widget, xlabel='Scanner Extention [nm]',
+ ylabel='Cantilever Deflection [nm]',
+ title='Force-Distance curve',
+ win_title='Force Curve',
+ linewidth=1.5):
+ _plot_generic = plot_generic.Curve(xlabel, ylabel, title, linewidth)
+ plot.__init__(self, window, widget, _plot_generic)
+ self.window.set_title(win_title)
+
+ def displayPlot(self, X, Y, linestyle='b-', alpha=1):
+ """
+ Display the plot.
+ """
+ if self.navToolbar.auto:
+ self.plot_generic.lim = None
+ self.plot_generic.plot(X, Y, linestyle=linestyle, alpha=alpha)
+ self.show()
+
+class plotGride(plot):
+ '''
+ Plots the given matrix corresponding to the type.
+
+ Type can be :
+ basic = 2D matrix
+
+ In the following cases, plot is a forceVolume object
+ stiffness = plot the stiffness of the object
+ piezzoHeight = plot the piezzo height of the object
+ zeroForceImage = plot the zero force Image of the object
+ '''
+ def __init__(self, window, widget, title = 'Stiffness',
+ win_title = 'Gride Plot'):
+
+ _plot_generic = plot_generic.Array(title)
+
+ self.scaleMax = None
+ self.array = {'scan_size' : None,
+ 'data' : None}
+
+ plot.__init__(self, window, widget, _plot_generic)
+ # Set the properties of the GUI
+ window.set_title(win_title)
+ self.widget['scrollbar_min'].set_property('visible', True)
+ self.widget['scrollbar_max'].set_property('visible', True)
+ self.widget['scrollbar_min'].connect_object("change-value",
+ self.change_scale_min,
+ None)
+ self.widget['scrollbar_max'].connect_object("change-value",
+ self.change_scale_max,
+ None)
+
+ self.widget['scrollbar_depth'].connect_object("change-value",
+ self.change_depth,
+ None)
+
+ self.widget['button_modify_plot'].set_label('Color scale')
+ self.widget['button_modify_plot'].connect_object(
+ 'clicked',
+ self.color_scale,
+ self.widget['button_modify_plot'])
+ self.widget['button_modify_plot'].set_property('visible', True)
+ self.figure.canvas.mpl_connect('button_press_event',
+ self.on_gride_click)
+ self.figure.canvas.mpl_connect('button_release_event', self.on_button_release)
+ self.figure.canvas.mpl_connect('motion_notify_event', self.move_mouse)
+ def __getattr__(self, att):
+ if att == 'path_refresh':
+ return self.plot_generic.path_refresh
+ elif att == 'path':
+ return self.plot_generic.path
+ def plot(self, dispArray, scanSize = None, nanVal= 80e9, cmap = 'gray'):
+ self.cmap = cmap
+ if self.scaleMax == None:
+ # The scale hasn't yet been initialized. Then do it.
+ try:
+ self.scaleMax = numpy.nanmax(dispArray)
+ except TypeError:
+ # In windows, numpy.nanmax seems to not work in this case...
+ self.scaleMax = numpy.max(
+ dispArray[numpy.array((numpy.isnan(dispArray)-1),
+ dtype=bool)])
+ if type(self.scaleMax) == numpy.ma.core.MaskedArray:
+ self.scaleMax = 0
+ self.widget['scrollbar_min'].set_adjustment(
+ gtk.Adjustment(0.0, 0.00,
+ self.scaleMax,
+ self.scaleMax / 100,
+ self.scaleMax / 25,
+ 0))
+ self.widget['scrollbar_max'].set_adjustment(
+ gtk.Adjustment(self.scaleMax, 0.00,
+ self.scaleMax,
+ self.scaleMax / 100,
+ self.scaleMax / 25,
+ 0))
+ self.array['data'] = dispArray
+ if scanSize is not None:
+ self.array['scan_size'] = scanSize
+ if self.array['data'].ndim == 3:
+ self.widget['scrollbar_depth'].set_property('visible', True)
+ self.widget['scrollbar_depth'].set_adjustment(
+ gtk.Adjustment(0.0, 0.00,
+ self.array['data'].shape[2],
+ 1, 5, 1.0))
+ self.depth = 0
+ self.__plot()
+ def __plot(self):
+ if self.array['data'].ndim == 3:
+ if self.depth >= self.array['data'].shape[2]:
+ self.depth = self.array['data'].shape[2] - 1
+ elif self.depth < 0:
+ self.depth = 0
+ disp_array = self.array['data'][:,:,self.depth]
+ else:
+ disp_array = self.array['data']
+ self.plot_generic.cmap = self.cmap
+ self.plot_generic.plot(disp_array, scan_size=self.array['scan_size'])
+ self.show()
+ def get_path(self):
+ return self.plot_generic.get_path()
+ def change_depth(self, widget, jump, value):
+ self.depth = value
+ self.__plot()
+ def change_scale_min(self, widget, jump, value):
+ self.plot_generic.vmin = value
+ self.show()
+ def change_scale_max(self, widget, jump, value):
+ self.plot_generic.vmax = value
+ self.show()
+ def color_scale(self, widget):
+ self.plot_generic.colorbar = not(self.plot_generic.colorbar)
+ self.__plot()
+ self.show()
+ def mark_pos(self, pos_x, pos_y):
+ self.plot_generic.marker = (pos_x, pos_y)
+ self.show()
+ def show_trace(self, trace):
+ self.plot_generic.show_trace(trace)
+ self.show()
+ def on_gride_click(self, event):
+ self.plot_generic.on_gride_click(event)
+ self.show()
+ def on_button_release(self, event):
+ self.plot_generic.on_button_release(event)
+ self.show()
+ def move_mouse(self, event):
+ self.plot_generic.move_mouse(event)
+ self.show()
+class errorBar(plot):
+ def __init__(self, window, widget, xlabel='Depth',
+ ylabel='Stiffness',
+ title='mean stiffness',
+ win_title='Mean Stiffness'):
+ _plot_generic = plot_generic.ErrorBar(xlabel, ylabel, title)
+ plot.__init__(self, window, widget, _plot_generic)
+ # Add button to toggle the display of the number of data.
+ self.widget['button_modify_plot'].set_label('Hide nbr data')
+ self.widget['button_modify_plot'].connect_object(
+ 'clicked',
+ self.toogle_plot,
+ self.widget['button_modify_plot'])
+ self.widget['button_modify_plot'].set_property('visible', True)
+ self.init_data = {'data' : None,
+ 'group' : None,
+ 'group_info' : None,
+ 'linestyle' : 'b-'}
+ # self.plot_gen = plot_generic.ErrorBar(xlabel, ylabel, title)
+ def average_stiffness(self, data, group=None, group_info=None, linestyle='b-'):
+ self.init_data['data'] = data
+ self.init_data['group'] = group
+ self.init_data['group_info'] = group_info
+ self.init_data['linestyle'] = linestyle
+ self.__refresh_average_stiffness()
+ def __refresh_average_stiffness(self):
+ # If the button label is set to hide, we have to show it, and inversly.
+ if self.widget['button_modify_plot'].get_label() == 'Hide nbr data':
+ show_count = True
+ elif self.widget['button_modify_plot'].get_label() == 'Show nbr data':
+ show_count = False
+ self.plot_generic.average_stiffness(self.init_data['data'],
+ self.init_data['group'],
+ self.init_data['group_info'],
+ self.init_data['linestyle'],
+ show_count=show_count)
+ self.show()
+ def plot(self, data_x, data_y, y_err, linestyle='b-'):
+ """
+ Data is organized like this :
+ """
+
+ self.plot_generic.plot(data_x, data_y, y_err, linestyle)
+ self.show()
+ def toogle_plot(self, widget):
+ """
+ Toogle between density and scatter plot
+ """
+ if widget.get_label() == 'Hide nbr data':
+ widget.set_label('Show nbr data')
+ elif widget.get_label() == 'Show nbr data':
+ widget.set_label('Hide nbr data')
+ self.__refresh_average_stiffness()
+class TimeLapse(plotCurves):
+ def __init__(self, window, widget, xlabel='Time',
+ ylabel='Stiffness',
+ title='Relative Stiffness',
+ win_title='Relative Stiffness'):
+ _plot_generic = plot_generic.TimeLapse(xlabel, ylabel, title)
+ plot.__init__(self, window, widget, _plot_generic)
+ self.window.set_title(win_title)
+ def displayPlot(self, data, data_ctl=None, baseline=0):
+ """
+ Displays the plot.
+
+ See plot_generic.TimeLapse documentation for details
+ """
+
+ self.plot_generic.plot(data, data_ctl, baseline)
+ self.show()
+
+class plotMosaic(plotGride):
+ '''
+ Plots a mosaic of arrays. The input has to be a list of arrays.
+ When you create an instance of plotMosaic, you can plot a list of arrays
+ in a single window with subplots.
+
+ For example, if you have several arrays like array_1, array_2, array_3 put
+ them in a single list :
+
+ >>> list_of_array = (array_1, array_2, array_3)
+ >>> new_mosaic = plotMosaic()
+ >>> new_mosaic.plot(list_of_array)
+
+ This will open a new window with your array plotted.
+ '''
+ def __init__(self, window, widget, title = 'Mosaic plot'):
+ '''
+ Create a new instance of the class plotMosaic.
+ window = builder object of the window where you want to plot your data
+ widget = builder object of widget that are used for connection.
+ title = string that will be used as the name of the window
+ '''
+ _plot_generic = plot_generic.Mosaic()
+ plot.__init__(self, window, widget, _plot_generic)
+ self.window.set_title(title)
+ def plot(self, list_array, title = 'Mosaic plot', list_name=None,
+ cmap = 'gray',
+ list_mask=None):
+ '''
+ Plot your arrays.
+
+ list_array = list of the array to be plotted. They do not need to have
+ the same shape or size.
+
+ title = string that will be displayed as the general title of the grid.
+
+ list_title = list of string. Each string will be used as a title for
+ each grides. This can take some place and can be places
+ over the above gride. So be carefull for the esthetic of
+ your plot.
+ '''
+ if cmap in ('gray', 'grey', 'height'):
+ cmap = cm.gray
+ elif cmap in ('copper', 'afm image'):
+ cmap = cm.copper
+ elif cmap in ('jet', 'stiffness'):
+ cmap = cm.jet
+ self.plot_generic.plot(list_array, title, list_name, cmap, list_mask)
+ self.plot_generic.figure.canvas.mpl_connect('axes_enter_event',
+ self.enter_array)
+ self.plot_generic.figure.canvas.mpl_connect('axes_leave_event',
+ self.leave_array)
+ self.plot_generic.figure.canvas.mpl_connect('motion_notify_event',
+ self.enter_array)
+# self.plot_generic.figure.canvas.mpl_connect('pick_event', self.on_pick)
+ def enter_array(self, event):
+ self.plot_generic.enter_array(event)
+ self.show()
+ def leave_array(self, event):
+ self.plot_generic.leave_array(event)
+ self.show()
+ def index(self, event):
+ return self.plot_generic.index(event)
+# def on_pick(self, event):
+# self.plot_generic.on_pick(event)
+# self.show()
+# print "On plot_gtk"
+
+class plotHist(plot):
+ '''
+ Plot the histogram of the list of values.
+ '''
+ def __init__(self, window, widget, xlabel = '', title='Histogram', y_rel=False):
+ _plot_generic = plot_generic.Histogram(xlabel, title, y_rel=y_rel)
+ plot.__init__(self, window, widget, _plot_generic)
+ self.window.set_title(title)
+ def hist(self, data, data2 = None, label = None, label2 = None,
+ hist_range = None, color_type = 'color', y2=False, ):
+ self.plot_generic.plot(data, data2, label, label2,
+ hist_range, color_type, y2)
+ self.show()
+ def gauss(self, prop):
+ self.plot_generic.show_fit(prop)
+ self.show()
+ self.plot_generic.figure.canvas.mpl_connect('motion_notify_event',
+ self.move_annotation)
+ def move_annotation(self, event):
+ self.plot_generic.move_annotation(event)
+ self.show()
+ def set_y_rel(self, value):
+ if type(value) == bool:
+ self.plot_generic.relative = value
+
+class plotScatter(plot):
+ """
+ Display the scatter plot of the choosen data.
+ """
+
+ def __init__(self, window, widget, title='Scatter Plot',
+ xlabel='', ylabel=''):
+ _plot_generic = plot_generic.Scatter(xlabel, ylabel, title)
+ plot.__init__(self, window, widget, _plot_generic)
+ self.window.set_title(title)
+
+ self.widget['button_modify_plot'].set_label('Density plot')
+ self.widget['button_modify_plot'].connect_object(
+ 'clicked',
+ self.toogle_plot,
+ self.widget['button_modify_plot'])
+ self.widget['button_modify_plot'].set_property('visible', True)
+ def display(self, data, data_type):
+ """
+ Display it.
+ Data has to be a PlotData object.
+ """
+ self.plot_generic.plot(data, data_type)
+ self.show()
+ def toogle_plot(self, widget):
+ """
+ Toogle between density and scatter plot
+ """
+ if widget.get_label() == 'Density plot':
+ widget.set_label('Scatter plot')
+ self.plot_generic.type = 'density'
+ self.plot_generic.refresh()
+ self.show()
+ elif widget.get_label() == 'Scatter plot':
+ widget.set_label('Density plot')
+ self.plot_generic.type = 'scatter'
+ self.plot_generic.refresh()
+ self.show()
+ self.show()
+class plotTomo(plot):
+ '''
+ Plot the stiffness tomography slices.
+ '''
+ def __init__(self, window, widget, tomo_array, scan_size=None,
+ title='Tomography'):
+ _plot_generic = plot_generic.Tomo(tomo_array, scan_size)
+ plot.__init__(self, window, widget, _plot_generic)
+ self.window = window
+ self.widget = widget
+ self.slice = [0, 0, 0]
+ self.window.set_property('visible', True)
+ self.window.connect('destroy', self.on_win_plot_destroy)
+ self.move_cursor = 0
+ self.cur_size = 4
+ self.destroyed = 0
+ self.canvas.mpl_connect('motion_notify_event', self.onmouse_motion)
+ self.canvas.mpl_connect('button_press_event', self.on_gride_click)
+
+ self.widget['adjust_x'].connect_object("value-changed",
+ self.on_adjust_x_value_changed,
+ self.widget['adjust_x'])
+ self.widget['adjust_y'].connect_object("value-changed",
+ self.on_adjust_y_value_changed,
+ self.widget['adjust_y'])
+ self.widget['adjust_z'].connect_object("value-changed",
+ self.on_spin_z_change_value,
+ self.widget['adjust_z'])
+ self.widget['adjust_x'].upper = tomo_array.shape[0]-1
+ self.widget['adjust_y'].upper = tomo_array.shape[1]-1
+ self.widget['adjust_z'].upper = tomo_array.shape[2]-1
+
+ self.widget['scrollbar_min'].set_property('visible', True)
+ self.widget['scrollbar_max'].set_property('visible', True)
+ self.colorScaleMax = tomo_array.max()
+ self.colorScaleMin = 0
+ self.widget['scrollbar_min'].set_adjustment(
+ gtk.Adjustment(0.0, 0.00,
+ self.colorScaleMax,
+ self.colorScaleMax / 100,
+ self.colorScaleMax / 25,
+ 0))
+ self.widget['scrollbar_max'].set_adjustment(
+ gtk.Adjustment(self.colorScaleMax, 0.00,
+ self.colorScaleMax,
+ self.colorScaleMax / 100,
+ self.colorScaleMax / 25,
+ 0))
+ self.widget['scrollbar_min'].connect_object("change-value",
+ self.change_scale_min,
+ None)
+ self.widget['scrollbar_max'].connect_object("change-value",
+ self.change_scale_max,
+ None)
+ self.plot()
+ def on_win_plot_destroy(self, signal):
+ self.destroyed = 1
+ self.window.destroy()
+ def onmouse_motion(self, event):
+ '''
+ Catch the mouse motion in the axes. self.move_cursor has to be set to
+ True to have an action.
+ The other axis are refresh according to the movement of the mouse.
+ '''
+ if not self.move_cursor:
+ return
+ if event.inaxes == self.plot_generic.axis['display_x']:
+ y = self.plot_generic.scale['y'] > event.xdata
+ y = y.nonzero()[0][0]
+ z = self.plot_generic.scale['z'] > event.ydata
+ z = z.nonzero()[0][0]
+ self.widget['adjust_y'].set_value(y)
+ self.widget['adjust_z'].set_value(z)
+ elif event.inaxes == self.plot_generic.axis['display_y']:
+ x = self.plot_generic.scale['x'] > event.xdata
+ x = x.nonzero()[0][0]
+ z = self.plot_generic.scale['z'] > event.ydata
+ z = z.nonzero()[0][0]
+ x = numpy.floor(event.xdata)
+ z = numpy.floor(event.ydata)
+ self.widget['adjust_x'].set_value(x)
+ self.widget['adjust_z'].set_value(z)
+ elif event.inaxes == self.plot_generic.axis['display_z']:
+ x = self.plot_generic.scale['x'] > event.xdata
+ x = x.nonzero()[0][0]
+ y = self.plot_generic.scale['y'] > event.ydata
+ y = y.nonzero()[0][0]
+ self.widget['adjust_x'].set_value(x)
+ self.widget['adjust_y'].set_value(y)
+ def on_gride_click(self, event):
+ '''
+ Catch click in an axis and change the self.move_cursor value.
+ '''
+ if self.move_cursor:
+ self.get_pos()
+ self.move_cursor = False
+ else:
+ self.move_cursor = True
+ def plot(self):
+ self.plot_generic.plot(self.slice,
+ self.colorScaleMin, self.colorScaleMax)
+ self.show()
+ def on_spin_z_change_value(self, widget):
+ self.slice[2] = widget.get_value()
+ self.plot()
+ def on_adjust_x_value_changed(self, widget):
+ self.slice[0] = widget.get_value()
+ self.plot()
+ def on_adjust_y_value_changed(self,widget):
+ self.slice[1] = widget.get_value()
+ self.plot()
+ def change_scale_min(self, widget, jump, value):
+ self.colorScaleMin = value
+ self.plot()
+ def change_scale_max(self, widget, jump, value):
+ self.colorScaleMax = value
+ self.plot()
+ def get_pos(self):
+ '''
+ Function to be overwritten by the writer in order to get the internal
+ values.
+ '''
+ pass
+
+class plotInteractiveCurve(plot):
+ """
+ Plots several curves in as many subplot as the number of curves.
+ """
+ def __init__(self, window, widget, win_title='Slices'):
+ _plot_generic = plot_generic.InteractiveLinePlot()
+ plot.__init__(self, window, widget, _plot_generic)
+ self.window.set_title(win_title)
+ self.window.resize(500, 500)
+ def show(self):
+ if not self.window.get_property('visible'):
+ raise StandardError
+ self.canvas.draw_idle()
+ def plot(self, array_x, arrays, labels):
+ self.plot_generic.plot(array_x, arrays, labels)
+
+ self.plot_generic.figure.canvas.mpl_connect('button_press_event',
+ self.button_press_event)
+ self.plot_generic.figure.canvas.mpl_connect('button_release_event',
+ self.button_release_event)
+ self.plot_generic.figure.canvas.mpl_connect('motion_notify_event',
+ self.mouse_move)
+ self.plot_generic.figure.canvas.mpl_connect('pick_event', self.on_pick)
+ self.show()
+ def modify_plot(self, array_x, arrays):
+ self.plot_generic.modify_plot(array_x, arrays)
+ self.show()
+ def button_press_event(self, event):
+ self.plot_generic.button_press_event(event)
+ self.show()
+ def mouse_move(self, event):
+ self.plot_generic.mouse_move(event)
+ self.show()
+ def on_pick(self, event):
+ self.plot_generic.on_pick(event)
+ self.show()
+ def button_release_event(self, event):
+ self.plot_generic.button_release_event(event)
+
+class FoveaNavToolbar(NavigationToolbar):
+
+ def __init__(self, plotCanvas, plotWindow):
+ NavigationToolbar.__init__(self, plotCanvas, plotWindow)
+ self.plot_gen = None
+ self.fig_size = [800, 800]
+ self.auto = 1
+ def home(self, event):
+ self.auto = 1
+ NavigationToolbar.home(self, event)
+ def press_zoom(self, event):
+ self.auto = 0
+ NavigationToolbar.press_zoom(self, event)
+ def save_figure(self, event):
+ # get the old size...
+ old_size = self.plot_gen.figure.get_size_inches().copy()
+ self.plot_gen.figure.set_size_inches(self.plot_gen.win_size)
+ #self.plot_gen.set_size()
+ NavigationToolbar.save_figure(self, event)
+ # return to the old size...
+ self.plot_gen.figure.set_size_inches(old_size)
+
+def regenerate_plot(self, type='normal'):
+ """
+ Create the gtk window where to plot the curves.
+ """
+
+ builder = gtk.Builder()
+ if type == 'normal':
+ _glade_filename = resource_filename('openfovea', 'glade/plot.glade')
+ elif type == 'tomography':
+ _glade_filename = resource_filename('openfovea',
+ 'glade/stiffness_plot.glade')
+ builder.add_from_file(_glade_filename)
+ window = builder.get_object("WindowPlot")
+ builder.connect_signals(self)
+ if type == 'normal':
+ widget = {
+ 'plot_area' : builder.get_object('boxPlot'),
+ 'scrollbar_min' : builder.get_object('scrollbar_min'),
+ 'scrollbar_max' : builder.get_object('scrollbar_max'),
+ 'scrollbar_depth' : builder.get_object('scrollbar_depth'),
+ 'box_nav_tool' : builder.get_object('box_nav_tool'),
+ 'button_modify_plot' : builder.get_object('button_modify_plot')
+ }
+ elif type == 'tomography':
+ widget = {
+ 'plot_area' : builder.get_object('boxPlot'),
+ 'display_x': builder.get_object('display_x'),
+ 'display_y': builder.get_object('display_y'),
+ 'display_z': builder.get_object('display_z'),
+ 'adjust_x': builder.get_object('adjust_x'),
+ 'adjust_y': builder.get_object('adjust_y'),
+ 'adjust_z': builder.get_object('adjust_z'),
+ 'scrollbar_min' : builder.get_object('scrollbar_min'),
+ 'scrollbar_max' : builder.get_object('scrollbar_max')
+ }
+
+ return [window, widget]
+
+def main():
+ gtk.main()
+ return 0
+if __name__=='__main__':
+ import sys
+ import numpy
+ [window, widget] = regenerate_plot('normal')
+ window.connect('destroy', gtk.main_quit)
+ if sys.argv[1] == 'curve':
+ print('Creating vectors...')
+ X=numpy.asanyarray([1,2,3,4,5,6,7,8,9,10])
+ Y=numpy.asanyarray([1,3,4,3,2,4,5,6,7,8])
+ print('Plotting vectors...')
+ newPlot=plotCurves(window, widget)
+ newPlot.displayPlot(X,Y)
+ newPlot.set_size(800, 800)
+ main()
+ elif sys.argv[1] == 'gride':
+ gride = numpy.random.randn(64, 64)+2
+ newPlot = plotGride(window, widget)
+ newPlot.plot(gride, 2000)
+ main()
+ elif sys.argv[1] == 'mosaic':
+ mosaic = [numpy.random.randn(32, 32)+2 for i in range(10)]
+ list_mask = [1,0,0,0,1,1,0,0,1,0]
+ newPlot = plotMosaic(window, widget)
+ newPlot.plot(mosaic, list_mask=list_mask)
+ main()
diff --git a/openfovea/simple_window_gtk.py b/openfovea/simple_window_gtk.py
new file mode 100644
index 0000000..d8ae696
--- /dev/null
+++ b/openfovea/simple_window_gtk.py
@@ -0,0 +1,80 @@
+import pygtk
+pygtk.require('2.0')
+import gtk
+#from gtk.gtkgl.apputils import *
+
+class FileGroup(object):
+ def __init__(self, file_list, group_list=None):
+
+ if group_list == None:
+ group_list = [0 for i in file_list]
+ self.lock = True # defines if we modify all the following value in the list
+ self.dialog = gtk.Dialog(title='File Group', buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK,
+ gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
+ self.dialog.set_size_request(300, 300)
+ # Create scrolling window
+ self.scroll = gtk.ScrolledWindow()
+ self.scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+
+ self.scroll.show()
+ # Create the table.
+ self.table = gtk.Table(3, 2)
+ self.table.set_border_width(5)
+ self.table.set_col_spacings(5)
+ self.table.set_row_spacings(5)
+ self.table.show()
+ check = gtk.CheckButton(label='Lock')
+ check.set_active(True)
+ check.connect('toggled', self.on_check_toggled)
+ check.show()
+ self.table.attach(check, 0, 1, 0, 1)
+ # Fill the table with label and spin button
+ self.spin_button = []
+ index_list = range(1, len(file_list)+1)
+ for (item, index, group) in zip(file_list, index_list, group_list):
+ text = gtk.Label(str=item)
+ text.show()
+ self.table.attach(text, 0, 1, index, index + 1)
+ spin_button = gtk.SpinButton(gtk.Adjustment(value = group,
+ lower = 0,
+ upper = 10,
+ step_incr = 1,
+ page_incr = 1,
+ page_size = 0))
+ spin_button.connect('value_changed', self.on_spin_value_changed)
+ spin_button.show()
+ self.table.attach(spin_button, 1, 2, index, index + 1)
+ self.spin_button.append(spin_button)
+ self.scroll.add_with_viewport(self.table)
+ self.dialog.vbox.pack_start(self.scroll, True, True, 0)
+ #self.dialog.vbox.pack_start(self.table, True, True, 0)
+ def on_spin_value_changed(self, widget):
+ """
+ Change the value of the following items in the list
+ """
+ if not self.lock:
+ return
+ start = self.spin_button.index(widget)
+ if start+1 < len(self.spin_button):
+ self.spin_button[start+1].set_value(widget.get_value())
+ def on_check_toggled(self, widget):
+ self.lock = widget.get_active()
+ def run(self):
+ """
+ Runs the dialog
+ """
+ response = self.dialog.run()
+ if response == gtk.RESPONSE_OK:
+ group_list = []
+ for item in self.spin_button:
+ group_list.append(int(item.get_value()))
+ return group_list
+ def destroy(self):
+ self.dialog.destroy()
+
+if __name__ == '__main__':
+ file_list = ['File number ' + str(i) for i in range(20)]
+ group_list = None
+ window = FileGroup(file_list, group_list)
+ response = window.run()
+ print response
diff --git a/script/openfovea b/script/openfovea
new file mode 100755
index 0000000..c3110fc
--- /dev/null
+++ b/script/openfovea
@@ -0,0 +1,21 @@
+#!/usr/bin/env python
+#
+import sys
+
+if __name__ == '__main__':
+ if len(sys.argv) == 2:
+ if sys.argv[1] in ['-h', '--help']:
+ print "Launch openfovea and follow the instructions."
+ elif sys.argv[1] in ['-v', '--version']:
+ from openfovea import common
+ print("Version : OpenFovea %s")%(common.APP_VERSION)
+ print("Homepage : http://www.freesbi.ch/openfovea")
+ print("Copyright: Copyright (C) 1999-2012 Charles Roduit")
+ else:
+ from openfovea import openfovea_gui
+
+ openfovea_gui.main(sys.argv[1])
+ else:
+ from openfovea import openfovea_gui
+
+ openfovea_gui.main()
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..ed9b549
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,29 @@
+#!/usr/bin/env python
+"""
+ Setup.py package the source in order to distriburte the software.
+ Just run python setup.py to install the software in your computer.
+"""
+
+from distutils.core import setup
+from openfovea.common import DEB_NAME, APP_VERSION
+
+setup(name=DEB_NAME,
+ version=APP_VERSION,
+ license='GNU Public License v3',
+ description='Postprocessing of force volume data acquired with AFM./n'+\
+ 'OpenFovea is a software to postprocess AFM force volume/n'+\
+ 'experiment. It is able to compute the stiffness of the/n'+\
+ 'scanned area, detect unbinding event and the zero force/n'+\
+ 'topography. It comes with a user friendly graphical interface/n'+\
+ 'that allow the user to select and display the data.',
+ author="Charles Roduit",
+ author_email="charles.roduit at gmail.com",
+ url="http://www.freesbi.ch/openfovea",
+ packages = ['openfovea',
+ 'openfovea.fovea_toolbox',
+ 'openfovea.fovea_toolbox.file_util'],
+ package_data={'openfovea':['glade/*.glade', 'Icon/*']},
+ data_files=[('share/applications', ['openfovea.desktop']),
+ ('share/pixmaps', ['openfovea/Icon/openfovea.png'])],
+ scripts = ['script/openfovea']
+ )
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-med/openfovea.git
More information about the debian-med-commit
mailing list