ctioga 1.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/COPYING +340 -0
- data/ctioga/bin/ctable +28 -0
- data/ctioga/bin/ctioga +37 -0
- data/ctioga/doc/ctable.1 +156 -0
- data/ctioga/doc/ctioga.1 +2363 -0
- data/ctioga/examples/README +46 -0
- data/ctioga/examples/ctioga.gnuplot +4 -0
- data/ctioga/examples/ctioga_within_tioga.rb +53 -0
- data/ctioga/examples/ctiogarc.rb +24 -0
- data/ctioga/examples/include_1.rb +15 -0
- data/ctioga/examples/noise.dat +100 -0
- data/ctioga/examples/noise.rb +13 -0
- data/ctioga/examples/trig.csv +100 -0
- data/ctioga/examples/trig.dat +100 -0
- data/ctioga/examples/trig.rb +14 -0
- data/ctioga/examples/trigh.dat +100 -0
- data/ctioga/examples/trigh.rb +10 -0
- data/ctioga/examples/tutorial +763 -0
- data/ctioga/examples/tutorial.sh +269 -0
- data/ctioga/tests/README +14 -0
- data/ctioga/tests/axes.sh +40 -0
- data/ctioga/tests/basic.sh +11 -0
- data/ctioga/tests/draw.sh +24 -0
- data/ctioga/tests/histograms.sh +14 -0
- data/ctioga/tests/insets.sh +41 -0
- data/ctioga/tests/layouts.sh +29 -0
- data/ctioga/tests/legends.sh +113 -0
- data/ctioga/tests/styles.sh +43 -0
- data/ctioga/tests/test_style.sh +8 -0
- data/ctioga/tests/tests.sh +24 -0
- data/ctioga/tests/text_backend.sh +83 -0
- data/ctioga/tests/tioga_defaults.rb +18 -0
- data/lib/CTioga/axes.rb +904 -0
- data/lib/CTioga/backends.rb +88 -0
- data/lib/CTioga/boundaries.rb +224 -0
- data/lib/CTioga/ctable.rb +134 -0
- data/lib/CTioga/curve_style.rb +246 -0
- data/lib/CTioga/debug.rb +199 -0
- data/lib/CTioga/dimension.rb +133 -0
- data/lib/CTioga/elements.rb +17 -0
- data/lib/CTioga/elements/base.rb +84 -0
- data/lib/CTioga/elements/containers.rb +578 -0
- data/lib/CTioga/elements/curves.rb +368 -0
- data/lib/CTioga/elements/tioga_primitives.rb +440 -0
- data/lib/CTioga/layout.rb +595 -0
- data/lib/CTioga/legends.rb +29 -0
- data/lib/CTioga/legends/cmdline.rb +187 -0
- data/lib/CTioga/legends/item.rb +164 -0
- data/lib/CTioga/legends/style.rb +257 -0
- data/lib/CTioga/log.rb +73 -0
- data/lib/CTioga/movingarrays.rb +131 -0
- data/lib/CTioga/partition.rb +271 -0
- data/lib/CTioga/plot_style.rb +230 -0
- data/lib/CTioga/plotmaker.rb +1677 -0
- data/lib/CTioga/shortcuts.rb +69 -0
- data/lib/CTioga/structures.rb +82 -0
- data/lib/CTioga/styles.rb +140 -0
- data/lib/CTioga/themes.rb +581 -0
- data/lib/CTioga/themes/classical.rb +82 -0
- data/lib/CTioga/themes/demo.rb +63 -0
- data/lib/CTioga/themes/fits.rb +91 -0
- data/lib/CTioga/themes/mono.rb +33 -0
- data/lib/CTioga/tioga.rb +32 -0
- data/lib/CTioga/utils.rb +173 -0
- data/lib/MetaBuilder/Parameters/dates.rb +38 -0
- data/lib/MetaBuilder/Parameters/lists.rb +132 -0
- data/lib/MetaBuilder/Parameters/numbers.rb +69 -0
- data/lib/MetaBuilder/Parameters/strings.rb +86 -0
- data/lib/MetaBuilder/Parameters/styles.rb +75 -0
- data/lib/MetaBuilder/Qt4/Parameters/dates.rb +51 -0
- data/lib/MetaBuilder/Qt4/Parameters/numbers.rb +65 -0
- data/lib/MetaBuilder/Qt4/Parameters/strings.rb +106 -0
- data/lib/MetaBuilder/Qt4/parameter.rb +172 -0
- data/lib/MetaBuilder/Qt4/parameters.rb +9 -0
- data/lib/MetaBuilder/descriptions.rb +603 -0
- data/lib/MetaBuilder/factory.rb +101 -0
- data/lib/MetaBuilder/group.rb +57 -0
- data/lib/MetaBuilder/metabuilder.rb +10 -0
- data/lib/MetaBuilder/parameter.rb +374 -0
- data/lib/MetaBuilder/parameters.rb +11 -0
- data/lib/MetaBuilder/qt4.rb +8 -0
- data/lib/SciYAG/Backends/backend.rb +379 -0
- data/lib/SciYAG/Backends/binner.rb +168 -0
- data/lib/SciYAG/Backends/cache.rb +102 -0
- data/lib/SciYAG/Backends/dataset.rb +158 -0
- data/lib/SciYAG/Backends/descriptions.rb +469 -0
- data/lib/SciYAG/Backends/filters.rb +25 -0
- data/lib/SciYAG/Backends/filters/average.rb +134 -0
- data/lib/SciYAG/Backends/filters/cumulate.rb +37 -0
- data/lib/SciYAG/Backends/filters/filter.rb +70 -0
- data/lib/SciYAG/Backends/filters/norm.rb +39 -0
- data/lib/SciYAG/Backends/filters/smooth.rb +63 -0
- data/lib/SciYAG/Backends/filters/sort.rb +43 -0
- data/lib/SciYAG/Backends/filters/strip.rb +34 -0
- data/lib/SciYAG/Backends/filters/trim.rb +64 -0
- data/lib/SciYAG/Backends/gnuplot.rb +131 -0
- data/lib/SciYAG/Backends/math.rb +108 -0
- data/lib/SciYAG/Backends/mdb.rb +462 -0
- data/lib/SciYAG/Backends/multitext.rb +96 -0
- data/lib/SciYAG/Backends/source.rb +64 -0
- data/lib/SciYAG/Backends/text.rb +339 -0
- data/lib/SciYAG/backends.rb +16 -0
- metadata +191 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# trim.rb : A trimming filter
|
|
2
|
+
# Copyright (C) 2007 Vincent Fourmond
|
|
3
|
+
|
|
4
|
+
# This program is free software; you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
|
6
|
+
# the Free Software Foundation; either version 2 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
|
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU General Public License for more details.
|
|
13
|
+
|
|
14
|
+
# You should have received a copy of the GNU General Public License
|
|
15
|
+
# along with this program; if not, write to the Free Software
|
|
16
|
+
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
17
|
+
|
|
18
|
+
module SciYAG
|
|
19
|
+
|
|
20
|
+
module Backends
|
|
21
|
+
|
|
22
|
+
# Removes a given ratio of points
|
|
23
|
+
class TrimFilter < Filter
|
|
24
|
+
include Dobjects
|
|
25
|
+
|
|
26
|
+
describe "trim", "Trim", "Removes a certain number of points"
|
|
27
|
+
|
|
28
|
+
init_param 'nb', "Number", Float,
|
|
29
|
+
"Keep one point for each nb points"
|
|
30
|
+
|
|
31
|
+
def initialize(nb)
|
|
32
|
+
@nb = Float(nb)
|
|
33
|
+
if @nb <= 1
|
|
34
|
+
warn "Invalid parameter nb #{nb}, using 3"
|
|
35
|
+
@nb = 3
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# There you go: a simple averageing filter.
|
|
40
|
+
def apply(f)
|
|
41
|
+
new_x = Dvector.new
|
|
42
|
+
new_y = Dvector.new
|
|
43
|
+
nb = 0.0
|
|
44
|
+
for x,y in f
|
|
45
|
+
if nb < 1
|
|
46
|
+
new_x << x
|
|
47
|
+
new_y << y
|
|
48
|
+
end
|
|
49
|
+
nb += 1
|
|
50
|
+
if nb >= @nb
|
|
51
|
+
nb -= @nb
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
return Function.new(new_x, new_y)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def apply!(f)
|
|
58
|
+
new_f = apply(f)
|
|
59
|
+
f.x.replace(new_f.x)
|
|
60
|
+
f.y.replace(new_f.y)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# gnuplot.rb: a backend to extract plots from gnuplot's files
|
|
2
|
+
# Copyright (C) 2007 Vincent Fourmond
|
|
3
|
+
|
|
4
|
+
# This program is free software; you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
|
6
|
+
# the Free Software Foundation; either version 2 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
|
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU General Public License for more details.
|
|
13
|
+
|
|
14
|
+
# You should have received a copy of the GNU General Public License
|
|
15
|
+
# along with this program; if not, write to the Free Software
|
|
16
|
+
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
require 'SciYAG/Backends/backend'
|
|
21
|
+
require 'Dobjects/Dvector'
|
|
22
|
+
require 'Dobjects/Function'
|
|
23
|
+
|
|
24
|
+
# To feed data to fancyread
|
|
25
|
+
require 'stringio'
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
module SciYAG
|
|
29
|
+
|
|
30
|
+
module Backends
|
|
31
|
+
|
|
32
|
+
class GnuplotBackend < Backend
|
|
33
|
+
|
|
34
|
+
include Dobjects
|
|
35
|
+
|
|
36
|
+
describe 'gnuplot', 'Gnuplot files', <<EOD
|
|
37
|
+
This backend hijacks gnuplot files to make extract data which they plot.
|
|
38
|
+
No information is taken about the style !
|
|
39
|
+
EOD
|
|
40
|
+
|
|
41
|
+
param_accessor :variables_overrides, 'vars',
|
|
42
|
+
"Variable overrides", {:type => :string},
|
|
43
|
+
"A colon-separated override of local variables, " +
|
|
44
|
+
"such as a=1;b=3;c=5"
|
|
45
|
+
|
|
46
|
+
param_accessor :range, 'range',
|
|
47
|
+
"Plotting X range", {:type => :string},
|
|
48
|
+
"The plotting X range, such as (0:2)"
|
|
49
|
+
|
|
50
|
+
# This is called by the architecture to get the data. It splits
|
|
51
|
+
# the set name into filename@cols, reads the file if necessary and
|
|
52
|
+
# calls get_data
|
|
53
|
+
def query_xy_data(set)
|
|
54
|
+
if set =~ /(.*)@(\d+)/
|
|
55
|
+
filename = $1
|
|
56
|
+
number = $2.to_i - 1
|
|
57
|
+
else
|
|
58
|
+
filename = set
|
|
59
|
+
number = 0
|
|
60
|
+
end
|
|
61
|
+
plots = run_gnuplot(filename)
|
|
62
|
+
return Function.new(*(plots[number]))
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Runs gnuplot on the file, and returns all datasets found
|
|
66
|
+
# inside it. Cached.
|
|
67
|
+
def run_gnuplot(filename)
|
|
68
|
+
date = File::mtime(filename)
|
|
69
|
+
# Get it from the cache !
|
|
70
|
+
get_cached_entry(filename, [], {:date => date}) do
|
|
71
|
+
debug "Running gnuplot on file #{filename}"
|
|
72
|
+
f = File.open(filename)
|
|
73
|
+
# We open a bidirectionnal connection to gnuplot:
|
|
74
|
+
gnuplot = IO.popen("gnuplot", "r+")
|
|
75
|
+
output = ""
|
|
76
|
+
gnuplot.puts "set term table"
|
|
77
|
+
for line in f
|
|
78
|
+
next if line =~ /set\s+term/
|
|
79
|
+
if @variables_overrides and line =~ /plot\s+/
|
|
80
|
+
debug "Found a plot, inserting variable overrides :#{@variables_overrides}"
|
|
81
|
+
line.gsub!(/plot\s+/, "#{@variables_overrides};plot ")
|
|
82
|
+
end
|
|
83
|
+
if @range and line =~ /plot\s+/
|
|
84
|
+
debug "Found a plot, inserting range :#{@range}"
|
|
85
|
+
line.gsub!(/plot\s+(\[[^\]]+\])?/,
|
|
86
|
+
"plot [#{@range}]")
|
|
87
|
+
end
|
|
88
|
+
gnuplot.print line
|
|
89
|
+
gnuplot.flush
|
|
90
|
+
output += slurp(gnuplot)
|
|
91
|
+
end
|
|
92
|
+
# Output a "\n" in the end.
|
|
93
|
+
gnuplot.puts ""
|
|
94
|
+
gnuplot.flush
|
|
95
|
+
gnuplot.close_write
|
|
96
|
+
# Then we get all that is remaining:
|
|
97
|
+
output += gnuplot.read
|
|
98
|
+
gnuplot.close
|
|
99
|
+
|
|
100
|
+
# Now, interaction with gnuplot is finished, and we want to
|
|
101
|
+
# parse that:
|
|
102
|
+
outputs = output.split("\n\n")
|
|
103
|
+
plots = []
|
|
104
|
+
for data in outputs
|
|
105
|
+
plots << Dvector.fancy_read(StringIO.new(data), [0,1])
|
|
106
|
+
end
|
|
107
|
+
# This block evaluates to plots:
|
|
108
|
+
plots
|
|
109
|
+
# DO NOT USE return !!!
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Gets all data from the given file until it blocks, and returns it.
|
|
114
|
+
def slurp(f, size = 10240)
|
|
115
|
+
str = ""
|
|
116
|
+
begin
|
|
117
|
+
while IO::select([f],[],[],0)
|
|
118
|
+
ret = f.readpartial(size)
|
|
119
|
+
if ret.empty?
|
|
120
|
+
return str
|
|
121
|
+
end
|
|
122
|
+
str += ret
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
return str
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
end
|
|
131
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# math.rb :This files provides the Math backend, which provides data based on
|
|
2
|
+
# mathematical formulas.
|
|
3
|
+
# Copyright (C) 2006 Vincent Fourmond
|
|
4
|
+
|
|
5
|
+
# This program is free software; you can redistribute it and/or modify
|
|
6
|
+
# it under the terms of the GNU General Public License as published by
|
|
7
|
+
# the Free Software Foundation; either version 2 of the License, or
|
|
8
|
+
# (at your option) any later version.
|
|
9
|
+
|
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
# GNU General Public License for more details.
|
|
14
|
+
|
|
15
|
+
# You should have received a copy of the GNU General Public License
|
|
16
|
+
# along with this program; if not, write to the Free Software
|
|
17
|
+
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
require 'SciYAG/Backends/backend'
|
|
21
|
+
require 'Dobjects/Dvector'
|
|
22
|
+
require 'Dobjects/Function'
|
|
23
|
+
|
|
24
|
+
module SciYAG
|
|
25
|
+
|
|
26
|
+
module Backends
|
|
27
|
+
|
|
28
|
+
class MathBackend < Backend
|
|
29
|
+
|
|
30
|
+
include Dobjects
|
|
31
|
+
include Math
|
|
32
|
+
|
|
33
|
+
describe 'math', 'Mathematical functions', <<EOD
|
|
34
|
+
This backend returns computations of mathematical formulas.
|
|
35
|
+
EOD
|
|
36
|
+
|
|
37
|
+
param_accessor :samples, 'samples', "Samples",
|
|
38
|
+
{:type => :integer}, "The number of points"
|
|
39
|
+
param_accessor :x_range, 'xrange', "X Range",
|
|
40
|
+
{:type => :float_range}, "X range (a:b)"
|
|
41
|
+
param_accessor :t_range, 'trange', "T Range",
|
|
42
|
+
{:type => :float_range}, "T range (a:b) (parametric plot)"
|
|
43
|
+
|
|
44
|
+
param_accessor :log, 'log', "Logarithmic scale",
|
|
45
|
+
{:type => :boolean}, "Space samples logarithmically"
|
|
46
|
+
|
|
47
|
+
def initialize
|
|
48
|
+
super()
|
|
49
|
+
@samples = 100
|
|
50
|
+
@x_range = -10.0..10.0
|
|
51
|
+
@t_range = -10.0..10.0
|
|
52
|
+
@log = false
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# This is called by the architecture to get the data. It splits
|
|
56
|
+
# the set name into func@range, reads the file if necessary and
|
|
57
|
+
# calls get_data.
|
|
58
|
+
def query_xy_data(set)
|
|
59
|
+
if set =~ /(.*)@(.*)/
|
|
60
|
+
@x_range = ParamRange.new($2)
|
|
61
|
+
set = $1
|
|
62
|
+
end
|
|
63
|
+
return Function.new(*get_data(set))
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def get_data(set)
|
|
68
|
+
# Parametric plot with a : in the middle
|
|
69
|
+
if set =~ /:/
|
|
70
|
+
x_block, y_block = set.split(/:/).map { |s|
|
|
71
|
+
eval "proc {|t| #{s}}"
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
t_v = make_dvector(@t_range, @samples)
|
|
75
|
+
x_v = t_v.collect(&x_block)
|
|
76
|
+
y_v = t_v.collect(&y_block)
|
|
77
|
+
else
|
|
78
|
+
y_block = eval "proc {|x| #{set} }"
|
|
79
|
+
x_v = make_dvector(@x_range, @samples)
|
|
80
|
+
y_v = x_v.collect(&y_block)
|
|
81
|
+
end
|
|
82
|
+
return [x_v,y_v]
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Turns a Range and a number of points into a Dvector
|
|
86
|
+
def make_dvector(range, nb_points, log = @log)
|
|
87
|
+
n = nb_points -1
|
|
88
|
+
a = Dvector.new(nb_points) { |i|
|
|
89
|
+
i.to_f/(n.to_f)
|
|
90
|
+
}
|
|
91
|
+
# a is in [0:1] inclusive...
|
|
92
|
+
if log
|
|
93
|
+
delta = range.last/range.first
|
|
94
|
+
# delta is positive necessarily
|
|
95
|
+
a *= delta.log
|
|
96
|
+
a.exp!
|
|
97
|
+
a *= range.first
|
|
98
|
+
else
|
|
99
|
+
delta = range.last - range.first
|
|
100
|
+
a *= delta
|
|
101
|
+
a += range.first
|
|
102
|
+
end
|
|
103
|
+
return a
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
# mdb.rb : a backend that can extract electrochemical data stored within
|
|
2
|
+
# MS Access databases created by the PAR PowerSuite utilities. I doubt
|
|
3
|
+
# it will be useful to many people, but, well no one knows !
|
|
4
|
+
|
|
5
|
+
# Copyright (C) 2006 Vincent Fourmond
|
|
6
|
+
|
|
7
|
+
# This program is free software; you can redistribute it and/or modify
|
|
8
|
+
# it under the terms of the GNU General Public License as published by
|
|
9
|
+
# the Free Software Foundation; either version 2 of the License, or
|
|
10
|
+
# (at your option) any later version.
|
|
11
|
+
|
|
12
|
+
# This program is distributed in the hope that it will be useful,
|
|
13
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
15
|
+
# GNU General Public License for more details.
|
|
16
|
+
|
|
17
|
+
# You should have received a copy of the GNU General Public License
|
|
18
|
+
# along with this program; if not, write to the Free Software
|
|
19
|
+
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
require 'SciYAG/Backends/backend'
|
|
23
|
+
require 'Dobjects/Dvector'
|
|
24
|
+
require 'Dobjects/Function'
|
|
25
|
+
|
|
26
|
+
module SciYAG
|
|
27
|
+
|
|
28
|
+
module Backends
|
|
29
|
+
|
|
30
|
+
class MDBBackend < Backend
|
|
31
|
+
|
|
32
|
+
include Dobjects
|
|
33
|
+
|
|
34
|
+
describe 'mdb', 'PAR PowerSuite MDB files', <<EOD, false
|
|
35
|
+
Reads the MS Access files produced by the PAR electrochemistry suite.
|
|
36
|
+
Abstract Backend, needs to be redefined by children.
|
|
37
|
+
EOD
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# The table of the file where we want to look for data.
|
|
42
|
+
attr_accessor :data_set_table_name
|
|
43
|
+
|
|
44
|
+
# The name of the X, Y and Z columns in the table.
|
|
45
|
+
attr_accessor :data_set_x_col, :data_set_y_col, :data_set_z_col
|
|
46
|
+
|
|
47
|
+
# If this attribute is set, the sets are further split
|
|
48
|
+
# for the different values of that column
|
|
49
|
+
attr_accessor :data_set_name_col
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# A small class to hold a data set
|
|
53
|
+
class DataSet
|
|
54
|
+
include Dobjects
|
|
55
|
+
|
|
56
|
+
# The name of the dataset
|
|
57
|
+
attr_accessor :name
|
|
58
|
+
|
|
59
|
+
# The DataSetID property
|
|
60
|
+
attr_accessor :data_set_id
|
|
61
|
+
|
|
62
|
+
# Used internally to create a new dvector
|
|
63
|
+
def new_dvector
|
|
64
|
+
return Dvector.new(1000).resize(0)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Returns the keys
|
|
68
|
+
def subsets
|
|
69
|
+
# return @x_data.keys.compact # removes the nil key...
|
|
70
|
+
return @subsets.compact
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def initialize(name, data_set_id)
|
|
74
|
+
@name = name
|
|
75
|
+
@data_set_id = data_set_id
|
|
76
|
+
|
|
77
|
+
@subsets = []
|
|
78
|
+
|
|
79
|
+
@x_data = {} # A hash indexed on the set name
|
|
80
|
+
@y_data = {} # A hash indexed on the set name
|
|
81
|
+
@z_data = {} # A hash indexed on the set name
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Returns the x_data for the given set
|
|
85
|
+
def x_data(set = nil)
|
|
86
|
+
return @x_data[set]
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Returns the y_data for the given set
|
|
90
|
+
def y_data(set = nil)
|
|
91
|
+
return @y_data[set]
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Returns the z_data for the given set
|
|
95
|
+
def z_data(set = nil)
|
|
96
|
+
return @z_data[set]
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Adds a point to the set
|
|
100
|
+
def add_point(x,y,z,set = nil)
|
|
101
|
+
if not @x_data[set]
|
|
102
|
+
@subsets << set
|
|
103
|
+
@x_data[set] = new_dvector
|
|
104
|
+
@y_data[set] = new_dvector
|
|
105
|
+
@z_data[set] = new_dvector
|
|
106
|
+
end
|
|
107
|
+
@x_data[set] << x
|
|
108
|
+
@y_data[set] << y
|
|
109
|
+
@z_data[set] << z
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def initialize
|
|
115
|
+
super()
|
|
116
|
+
@data_set_table_name = nil # has to be redefined by children
|
|
117
|
+
# A hash holding dataset_name -> DataSet object
|
|
118
|
+
@data_sets = {}
|
|
119
|
+
@data_set_id = [] # The same as upper,
|
|
120
|
+
# but with the set number
|
|
121
|
+
|
|
122
|
+
# The column for different sets
|
|
123
|
+
@data_set_name_col = "Series"
|
|
124
|
+
|
|
125
|
+
# The current database
|
|
126
|
+
@current_database = ""
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# The separator for columns when reading the file.
|
|
130
|
+
COL_SEP = "###"
|
|
131
|
+
# The command to run mdb-export
|
|
132
|
+
MDB_EXPORT = "mdb-export"
|
|
133
|
+
|
|
134
|
+
# Reads a table and returns the table of lines, optionnally beginning
|
|
135
|
+
# with the header
|
|
136
|
+
def mdb_export_table(file, table, header = false)
|
|
137
|
+
cmd_line = "#{MDB_EXPORT} -Q -d '#{COL_SEP}' " +
|
|
138
|
+
if header
|
|
139
|
+
" "
|
|
140
|
+
else
|
|
141
|
+
"-H "
|
|
142
|
+
end +
|
|
143
|
+
"'#{file}' '#{table}' " +
|
|
144
|
+
"| sort -n" # we sort numerically with the program sort,
|
|
145
|
+
# decently faster
|
|
146
|
+
mdb = IO.popen(cmd_line)
|
|
147
|
+
l = mdb.readlines
|
|
148
|
+
return l
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Reads a table and turns it into a list of hashes
|
|
152
|
+
def mdb_table_to_hash(file, table)
|
|
153
|
+
pipe = IO.popen("#{MDB_EXPORT} -Q -d '#{COL_SEP}' -R '&&&&&' " +
|
|
154
|
+
"#{file} #{table}")
|
|
155
|
+
header = pipe.readline.chomp.split(COL_SEP)
|
|
156
|
+
raw_entries = pipe.read.split('&&&&&').map {|l| l.split(COL_SEP)}
|
|
157
|
+
entries = []
|
|
158
|
+
for raw_entry in raw_entries
|
|
159
|
+
entry = {}
|
|
160
|
+
header.length.times do |i|
|
|
161
|
+
entry[header[i]] = raw_entry[i]
|
|
162
|
+
end
|
|
163
|
+
entries << entry
|
|
164
|
+
end
|
|
165
|
+
return entries
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Turns the first line returned by mdb_export_table into a nice header
|
|
169
|
+
# hash
|
|
170
|
+
def turn_header_into_hash(lines)
|
|
171
|
+
header = lines.shift
|
|
172
|
+
header = header.split(COL_SEP)
|
|
173
|
+
header_hash = {}
|
|
174
|
+
i = 0
|
|
175
|
+
for name in header
|
|
176
|
+
header_hash[name] = i
|
|
177
|
+
i+=1
|
|
178
|
+
end
|
|
179
|
+
return header_hash
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Small helper function
|
|
183
|
+
def safe_float(x)
|
|
184
|
+
begin
|
|
185
|
+
Float(x)
|
|
186
|
+
rescue
|
|
187
|
+
0.0/0.0
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Inherit the baseline stuff.
|
|
192
|
+
inherit_parameters :base_line
|
|
193
|
+
|
|
194
|
+
attr_reader :current_database
|
|
195
|
+
param :read_mdb_file, :current_database, "readdb", "Read Database",
|
|
196
|
+
{:type => :file, :filters => "Databases (*.mdb)"},
|
|
197
|
+
"Reads a database into memory"
|
|
198
|
+
|
|
199
|
+
# Reads the contents of a MDB file according to different defaults.
|
|
200
|
+
def read_mdb_file(file)
|
|
201
|
+
# First, make out the datasets from the file
|
|
202
|
+
if @current_database == file
|
|
203
|
+
return # We already read it !!
|
|
204
|
+
end
|
|
205
|
+
@current_database = file
|
|
206
|
+
|
|
207
|
+
data_sets_properties = mdb_table_to_hash(file, 'DataSetProperties')
|
|
208
|
+
|
|
209
|
+
prospective_data_sets = []
|
|
210
|
+
for set in data_sets_properties
|
|
211
|
+
prospective_data_sets << DataSet.new(set["DataSetName"],
|
|
212
|
+
set["DataSetID"])
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# We turn it into another array indexed on DataSetID for faster lookup
|
|
216
|
+
@data_sets_ids = []
|
|
217
|
+
for ds in prospective_data_sets
|
|
218
|
+
@data_sets_ids[ds.data_set_id.to_i] = ds
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Now, we need to get data from the files
|
|
222
|
+
raise "@data_set_table_name has to be set" unless @data_set_table_name
|
|
223
|
+
raise "@data_set_y_col has to be set" unless @data_set_y_col
|
|
224
|
+
raise "@data_set_x_col has to be set" unless @data_set_x_col
|
|
225
|
+
raise "@data_set_z_col has to be set" unless @data_set_z_col
|
|
226
|
+
|
|
227
|
+
l = mdb_export_table(file, @data_set_table_name, true)
|
|
228
|
+
h = turn_header_into_hash(l)
|
|
229
|
+
if h.key?(@data_set_x_col) and
|
|
230
|
+
h.key?(@data_set_y_col) and
|
|
231
|
+
h.key?(@data_set_z_col)
|
|
232
|
+
# We first sort the data respective to the first column
|
|
233
|
+
# l.sort! do |a,b|
|
|
234
|
+
# i_a = a[0,a.index(COL_SEP)].to_i
|
|
235
|
+
# i_b = b[0,b.index(COL_SEP)].to_i
|
|
236
|
+
# i_a <=> i_b
|
|
237
|
+
# end
|
|
238
|
+
x_col = h[@data_set_x_col]
|
|
239
|
+
y_col = h[@data_set_y_col]
|
|
240
|
+
z_col = h[@data_set_z_col]
|
|
241
|
+
set_col = if @data_set_name_col
|
|
242
|
+
h[@data_set_name_col]
|
|
243
|
+
else
|
|
244
|
+
false
|
|
245
|
+
end
|
|
246
|
+
id = h["DataSetID"]
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
l.each do |l|
|
|
250
|
+
a = l.split(COL_SEP)
|
|
251
|
+
ds = @data_sets_ids[a[id].to_i]
|
|
252
|
+
x = safe_float(a[x_col])
|
|
253
|
+
y = safe_float(a[y_col])
|
|
254
|
+
z = safe_float(a[z_col])
|
|
255
|
+
if data_set_name_col
|
|
256
|
+
ds.add_point(x,y,z,a[set_col])
|
|
257
|
+
else
|
|
258
|
+
ds.add_point(x,y,z)
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
else
|
|
263
|
+
raise "It looks like #{@data_set_x_col} or #{@data_set_y_col} is " +
|
|
264
|
+
"missing from the table #{@data_set_table_name} of file #{file}"
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# Now, we populate the data_sets hash
|
|
268
|
+
for ds in prospective_data_sets
|
|
269
|
+
@data_sets[ds.name] = ds
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
# Baseline stuff: we inherit it from the parents.
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
# This RE tells if a set looks like it might contain a subset.
|
|
277
|
+
# Slurps anything until the last @.
|
|
278
|
+
SUBSET_RE = /(.+)@(.+)/
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
# An internal function saying how to make a X,Y dataset from
|
|
282
|
+
# a x,y,z dataset. Can depend on many things. We just output
|
|
283
|
+
# X and Y cols here. Better be redefined by children.
|
|
284
|
+
def internal_to_external(x,y,z)
|
|
285
|
+
return Function.new(x,y)
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
# Internal function to get the data corresponding to one set.
|
|
289
|
+
def get_data(set)
|
|
290
|
+
if set =~ SUBSET_RE
|
|
291
|
+
s = string_to_set($1)
|
|
292
|
+
raise "Set #{$1} is unkown" unless s
|
|
293
|
+
return internal_to_external(s.x_data($2),
|
|
294
|
+
s.y_data($2),
|
|
295
|
+
s.z_data($2))
|
|
296
|
+
else
|
|
297
|
+
s = string_to_set(set)
|
|
298
|
+
raise "Set #{set} is unkown" unless s
|
|
299
|
+
return internal_to_external(s.x_data, s.y_data, s.z_data)
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
# This is called by the architecture to get the data. It splits
|
|
304
|
+
# the set name into filename@cols, reads the file if necessary and
|
|
305
|
+
# calls get_data
|
|
306
|
+
def query_xy_data(set)
|
|
307
|
+
data = get_data(set)
|
|
308
|
+
return Function.new(data.x.dup, data.y.dup)
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
# Transforms a string into a set. If the set starts with a #, it means
|
|
312
|
+
# we're directly interested in the number, not in the name. Can be a great
|
|
313
|
+
# deal useful, and much shorter...
|
|
314
|
+
def string_to_set(str)
|
|
315
|
+
if str =~ /^\#(\d+)$/
|
|
316
|
+
return @data_sets_ids[$1.to_i]
|
|
317
|
+
else
|
|
318
|
+
return @data_sets[str]
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
def expand_sets(set)
|
|
323
|
+
if s = string_to_set(set)
|
|
324
|
+
subs = s.subsets # we sort so that everything ids
|
|
325
|
+
# looking fine.
|
|
326
|
+
if subs.empty?
|
|
327
|
+
return [set]
|
|
328
|
+
else
|
|
329
|
+
return subs.collect {|s| "#{set}@#{s}"}
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
return [set]
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
# I think it is better not to take the cycles into account: they will
|
|
336
|
+
# hinder the display for no good reason.
|
|
337
|
+
def sets_available
|
|
338
|
+
return @data_sets.keys.sort
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
class MDBCyclicVoltammogram < MDBBackend
|
|
344
|
+
|
|
345
|
+
describe 'mdbcv', 'PAR PowerSuite MDB files, Cyclic Voltammetry', <<EOD
|
|
346
|
+
Reads the MS Access files produced by the PAR electrochemistry suite,
|
|
347
|
+
to extract cyclic voltammetry data.
|
|
348
|
+
EOD
|
|
349
|
+
def initialize
|
|
350
|
+
super
|
|
351
|
+
@data_set_table_name = "CVDataPoints_280"
|
|
352
|
+
@data_set_x_col = "E"
|
|
353
|
+
@data_set_y_col = "I"
|
|
354
|
+
@data_set_z_col = "T"
|
|
355
|
+
|
|
356
|
+
@type = :cv
|
|
357
|
+
@neg = true
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
# We import parameters from the parent
|
|
361
|
+
inherit_parameters :read_mdb_file, :base_line
|
|
362
|
+
|
|
363
|
+
param_accessor :type, 'type', "Type",
|
|
364
|
+
{ :type => :list, :list => {
|
|
365
|
+
:cv => "I = f(E)",
|
|
366
|
+
:et => "E = f(t)",
|
|
367
|
+
:it => "I = f(t)"} },
|
|
368
|
+
"The kind of plot wanted"
|
|
369
|
+
|
|
370
|
+
param_accessor :neg, 'polarity', "Current polarity",
|
|
371
|
+
{ :type => :boolean } , "True inverses current polarity (on by default)"
|
|
372
|
+
|
|
373
|
+
# Returns the set wanted. (x = E, y = I, z = T);
|
|
374
|
+
def internal_to_external(x,y,z)
|
|
375
|
+
if @neg
|
|
376
|
+
y = y.neg
|
|
377
|
+
end
|
|
378
|
+
case @type
|
|
379
|
+
when :cv
|
|
380
|
+
return Function.new(x,y)
|
|
381
|
+
when :et
|
|
382
|
+
return Function.new(z.sub(z.min).mul!(86400),x)
|
|
383
|
+
when :it
|
|
384
|
+
return Function.new(z.sub(z.min).mul!(86400),y)
|
|
385
|
+
end
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
class MDBPStep < MDBCyclicVoltammogram
|
|
391
|
+
|
|
392
|
+
describe 'mdbps', 'PAR PowerSuite MDB files, Potential Steps', <<EOD
|
|
393
|
+
Reads the MS Access files produced by the PAR electrochemistry suite,
|
|
394
|
+
to extract potential step data.
|
|
395
|
+
EOD
|
|
396
|
+
|
|
397
|
+
inherit_parameters :read_mdb_file, :base_line, :type, :neg
|
|
398
|
+
|
|
399
|
+
def initialize
|
|
400
|
+
super
|
|
401
|
+
@data_set_table_name = "CADataPoints_28"
|
|
402
|
+
@type = :it
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
class MDBEIS < MDBBackend
|
|
409
|
+
|
|
410
|
+
describe 'mdbeis', 'PAR PowerSuite MDB files, Impedance Spectroscopy', <<EOD
|
|
411
|
+
Reads the MS Access files produced by the PAR electrochemistry suite,
|
|
412
|
+
to extract impedance spectroscopy data.
|
|
413
|
+
EOD
|
|
414
|
+
def initialize
|
|
415
|
+
super
|
|
416
|
+
@data_set_table_name = "ImpDataPoints_20"
|
|
417
|
+
@data_set_x_col = "Freq"
|
|
418
|
+
@data_set_y_col = "Zre"
|
|
419
|
+
@data_set_z_col = "Zim"
|
|
420
|
+
|
|
421
|
+
@type = :wzm
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
# We import parameters from the parent
|
|
425
|
+
inherit_parameters :read_mdb_file, :base_line
|
|
426
|
+
|
|
427
|
+
param_accessor :type, 'type', "Type", {:type => :list,
|
|
428
|
+
:list => {
|
|
429
|
+
:wzm => "|Z| = f(f)",
|
|
430
|
+
:wzi => "Zi = f(f)",
|
|
431
|
+
:wzr => "Zr = f(f)",
|
|
432
|
+
:wphi => "Phi = f(f)",
|
|
433
|
+
:zrzi => "Zi = f(Zr)",
|
|
434
|
+
:yryi => "- Yi = f(Yr)",
|
|
435
|
+
}}, "The kind of plot wanted"
|
|
436
|
+
|
|
437
|
+
# Returns the set wanted. (x = Freq, y = Zre, z = Zim);
|
|
438
|
+
def internal_to_external(x,y,z)
|
|
439
|
+
case @type
|
|
440
|
+
when :wzm
|
|
441
|
+
return Function.new(x,y.pow(2).add!(z.pow(2)).sqrt)
|
|
442
|
+
when :wzi
|
|
443
|
+
return Function.new(x,z)
|
|
444
|
+
when :wzr
|
|
445
|
+
return Function.new(x,y)
|
|
446
|
+
when :zrzi
|
|
447
|
+
return Function.new(y,z)
|
|
448
|
+
when :wphi
|
|
449
|
+
return Function.new(x,z.div(y).atan!)
|
|
450
|
+
when :yryi
|
|
451
|
+
# We need to compute real and imaginary part of admittance
|
|
452
|
+
den = y**2 + z**2
|
|
453
|
+
return Function.new(y.div(den),
|
|
454
|
+
z.div(den))
|
|
455
|
+
end
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
end
|
|
459
|
+
end
|
|
460
|
+
end
|
|
461
|
+
|
|
462
|
+
|