iroki 0.0.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.
@@ -0,0 +1,64 @@
1
+ # Copyright 2016 Ryan Moore
2
+ # Contact: moorer@udel.edu
3
+ #
4
+ # This file is part of Iroki.
5
+ #
6
+ # Iroki is free software: you can redistribute it and/or modify it
7
+ # under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # Iroki is distributed in the hope that it will be useful, but
12
+ # WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14
+ # General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with Iroki. If not, see <http://www.gnu.org/licenses/>.
18
+
19
+ module Iroki
20
+ module CoreExt
21
+ module File
22
+ def check_file arg, which
23
+ help = " Try iroki --help for help."
24
+
25
+ abort_if arg.nil?,
26
+ "You must provide a #{which} file.#{help}"
27
+
28
+ abort_unless Object::File.exists?(arg),
29
+ "The file #{arg} doesn't exist.#{help}"
30
+
31
+ arg
32
+ end
33
+
34
+ def parse_name_map fname
35
+ check_file fname, :name_map
36
+
37
+ name_map = {}
38
+ Object::File.open(fname).each_line do |line|
39
+ oldname, newname = line.chomp.split "\t"
40
+
41
+
42
+ abort_if oldname.nil? || oldname.empty?,
43
+ "Column 1 missing for line: #{line.inspect}"
44
+
45
+ abort_if newname.nil? || newname.empty?,
46
+ "Column 2 missing for line: #{line.inspect}"
47
+
48
+ oldname = clean oldname
49
+ newname = clean newname
50
+
51
+ abort_if name_map.has_key?(oldname),
52
+ "#{oldname} is repeated in column 1"
53
+
54
+ name_map[oldname] = newname
55
+ end
56
+
57
+ abort_if duplicate_values?(name_map),
58
+ "Names in column 2 of name map file must be unique"
59
+
60
+ name_map
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,28 @@
1
+ # Copyright 2016 Ryan Moore
2
+ # Contact: moorer@udel.edu
3
+ #
4
+ # This file is part of Iroki.
5
+ #
6
+ # Iroki is free software: you can redistribute it and/or modify it
7
+ # under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # Iroki is distributed in the hope that it will be useful, but
12
+ # WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14
+ # General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with Iroki. If not, see <http://www.gnu.org/licenses/>.
18
+
19
+ module Iroki
20
+ module CoreExt
21
+ module Hash
22
+ def duplicate_values? hash
23
+ values = hash.values
24
+ values.count != 1 && values.count != values.uniq.count
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,34 @@
1
+ module Iroki
2
+ module CoreExt
3
+ module String
4
+
5
+ def hex? str
6
+ str.match(/^#[0-9A-Fa-f]{6}$/)
7
+ end
8
+
9
+ def clean str
10
+ str.gsub(/[^\p{Alnum}_]+/, "_").gsub(/_+/, "_")
11
+ end
12
+
13
+ def has_color? name
14
+ name.match(/(.*)(\[&!color="#[0-9A-Fa-f]{6}"\])/)
15
+ end
16
+ alias already_checked? has_color?
17
+
18
+ def clean_name name
19
+ if name.nil?
20
+ nil
21
+ else
22
+ if (match = has_color? name)
23
+ name = match[1]
24
+ color = match[2]
25
+
26
+ clean(name) + color
27
+ else
28
+ clean(name)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,232 @@
1
+ # Copyright 2016 Ryan Moore
2
+ # Contact: moorer@udel.edu
3
+ #
4
+ # This file is part of Iroki.
5
+ #
6
+ # Iroki is free software: you can redistribute it and/or modify it
7
+ # under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # Iroki is distributed in the hope that it will be useful, but
12
+ # WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14
+ # General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with Iroki. If not, see <http://www.gnu.org/licenses/>.
18
+
19
+ require "bio"
20
+ require "set"
21
+
22
+ module Iroki
23
+ module Main
24
+ def self.main(color_branches: nil,
25
+ color_taxa_names: nil,
26
+ exact: nil,
27
+ remove_bootstraps_below: nil,
28
+ color_map_f: nil,
29
+ name_map_f: nil,
30
+ auto_color: nil,
31
+ display_auto_color_options: nil,
32
+ newick_f: nil,
33
+ out_f: nil)
34
+
35
+
36
+
37
+ if display_auto_color_options
38
+ puts "\n Choices for --auto-color ..."
39
+ print " basic, basic_light, basic_dark, funky, funky_light, " +
40
+ "funky_dark\n\n"
41
+ exit
42
+ end
43
+
44
+ auto_color_options =
45
+ ["basic", "basic_light", "basic_dark",
46
+ "funky", "funky_light", "funky_dark",]
47
+
48
+
49
+ if(!auto_color.nil? &&
50
+ !auto_color_options.include?(auto_color))
51
+ puts "\n Choices for --auto-color ..."
52
+ print " basic, basic_light, basic_dark, funky, funky_light, " +
53
+ "funky_dark\n\n"
54
+
55
+ Trollop.die :auto_color, "#{auto_color} is not a valid option"
56
+ end
57
+
58
+ case auto_color
59
+ when nil
60
+ auto_colors = BASIC
61
+ when "basic"
62
+ auto_colors = BASIC
63
+ when "basic_light"
64
+ auto_colors = BASIC_LIGHT
65
+ when "basic_dark"
66
+ auto_colors = BASIC_DARK
67
+ when "funky"
68
+ auto_colors = FUNKY
69
+ when "funky_light"
70
+ auto_colors = FUNKY_LIGHT
71
+ when "funky_dark"
72
+ auto_colors = FUNKY_DARK
73
+ end
74
+
75
+ # color_branches = true
76
+ # color_taxa_names = true
77
+ # exact = false
78
+ # color_map_f = "test_files/500.patterns_with_name_map"
79
+ # name_map_f = "test_files/500.name_map"
80
+ # ARGV[0] = "test_files/500.zetas.tre"
81
+ newick = check_file newick_f, :newick
82
+
83
+ color_f = nil
84
+ if color_taxa_names || color_branches
85
+ color_f = check_file color_map_f, :color_map_f
86
+ end
87
+
88
+ check = color_map_f &&
89
+ !color_taxa_names &&
90
+ !color_branches
91
+
92
+ abort_if check,
93
+ "A pattern file was provided without specifying " +
94
+ "any coloring options"
95
+
96
+
97
+ # if passed color other than one defined, return black
98
+ black = "#000000"
99
+ red = "#FF1300"
100
+ yellow = "#FFD700"
101
+ blue = "#5311FF"
102
+ green = "#00FF2C"
103
+ color2hex = Hash.new "[&!color=\"#{black}\"]"
104
+ color2hex.merge!({
105
+ "black" => "[&!color=\"#{black}\"]",
106
+ "red" => "[&!color=\"#{red}\"]",
107
+ "blue" => "[&!color=\"#{blue}\"]",
108
+ "yellow" => "[&!color=\"#{yellow}\"]",
109
+ "green" => "[&!color=\"#{green}\"]"
110
+ })
111
+
112
+ # check if complementary colors requested
113
+ if color_f
114
+ colors = Set.new
115
+ File.open(color_f).each_line do |line|
116
+ _, color = line.chomp.split "\t"
117
+
118
+ colors << color
119
+ end
120
+
121
+ auto_color = colors.all? { |color| color.match /\A[0-4]\Z/ }
122
+ end
123
+
124
+ # get the color patterns
125
+ if color_f
126
+ patterns = {}
127
+ File.open(color_f).each_line do |line|
128
+ pattern, color = line.chomp.split "\t"
129
+
130
+ color = "black" if color.nil? || color.empty?
131
+
132
+ if name_map_f || color_taxa_names || color_branches
133
+ pattern = clean_name pattern
134
+ end
135
+
136
+ if !exact
137
+ pattern = Regexp.new pattern
138
+ end
139
+
140
+ if auto_color
141
+ patterns[pattern] = "[&!color=\"#{auto_colors[color]}\"]"
142
+ else
143
+ if hex? color
144
+ patterns[pattern] = "[&!color=\"#{color}\"]"
145
+ else
146
+ patterns[pattern] = color2hex[color]
147
+ end
148
+ end
149
+ end
150
+ end
151
+
152
+ treeio = Bio::FlatFile.open(Bio::Newick, newick)
153
+
154
+ newick = treeio.next_entry
155
+ tree = newick.tree
156
+
157
+ # do this first cos everything after will use the "new" names
158
+ if name_map_f
159
+ name_map = parse_name_map name_map_f
160
+
161
+ tree.collect_node! do |node|
162
+ unless node.name.nil?
163
+ # every name is cleaned no matter what
164
+ node.name = clean node.name
165
+
166
+ if name_map.has_key?(node.name)
167
+ node.name = name_map[node.name]
168
+ end
169
+ end
170
+
171
+ node
172
+ end
173
+ end
174
+
175
+ if color_taxa_names
176
+ leaves = tree.leaves.map do |n|
177
+ name = clean_name n.name
178
+
179
+ if (color = add_color_to_leaf_branch(patterns, name, exact))
180
+ name + color
181
+ else
182
+ name
183
+ end
184
+ end
185
+ else
186
+ leaves = tree.leaves.map { |n| clean_name n.name }
187
+ end
188
+
189
+ if color_branches
190
+ total = tree.nodes.count
191
+ n = 0
192
+ tree.collect_node! do |node|
193
+ n += 1
194
+ $stderr.printf "Node: %d of %d\r", n, total
195
+
196
+ color_nodes patterns, tree, node, exact
197
+ end
198
+ end
199
+ $stderr.puts
200
+
201
+ if remove_bootstraps_below
202
+ tree.collect_node! do |node|
203
+ if node.bootstrap && node.bootstrap < remove_bootstraps_below
204
+ node.bootstrap_string = ""
205
+ end
206
+
207
+ node
208
+ end
209
+ end
210
+
211
+
212
+
213
+ tre_str = tree.newick(indent: false).gsub(/'/, '')
214
+
215
+ nexus = "#NEXUS
216
+ begin taxa;
217
+ dimensions ntax=#{leaves.count};
218
+ taxlabels
219
+ #{leaves.join("\n")}
220
+ ;
221
+ end;
222
+
223
+ begin trees;
224
+ tree tree_1 = [&R] #{tre_str}
225
+ end;
226
+
227
+ #{FIG}"
228
+
229
+ File.open(out_f, "w") { |f| f.puts nexus }
230
+ end
231
+ end
232
+ end
@@ -0,0 +1,103 @@
1
+ # Copyright 2016 Ryan Moore
2
+ # Contact: moorer@udel.edu
3
+ #
4
+ # This file is part of Iroki.
5
+ #
6
+ # Iroki is free software: you can redistribute it and/or modify it
7
+ # under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # Iroki is distributed in the hope that it will be useful, but
12
+ # WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14
+ # General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with Iroki. If not, see <http://www.gnu.org/licenses/>.
18
+
19
+ module Iroki
20
+ module Utils
21
+ include AbortIf
22
+ include Iroki::CoreExt::String
23
+
24
+ def leaf? tree, node
25
+ tree.children(node).empty?
26
+ end
27
+
28
+ def add_color_to_leaf_branch patterns, node, exact
29
+ num_matches = 0
30
+ color = nil
31
+ already_matched = false
32
+
33
+ if exact # treat patterns as string matching
34
+ node_s = node.to_s
35
+ if patterns.has_key? node_s
36
+ color = patterns[node_s]
37
+
38
+ return color
39
+ else
40
+ return nil
41
+ end
42
+ else
43
+ node_s = node.to_s
44
+
45
+ patterns.each do |pattern, this_color|
46
+ if node_s =~ pattern
47
+ abort_if already_matched,
48
+ "Non specific matching for #{node_s}"
49
+
50
+ color = this_color
51
+ already_matched = true
52
+ end
53
+ end
54
+
55
+ return color
56
+ end
57
+ end
58
+
59
+ def get_color node
60
+ begin
61
+ node.name.match(/\[&!color="#[0-9A-Fa-f]{6}"\]/)[0]
62
+ rescue NoMethodError => e
63
+ nil
64
+ end
65
+ end
66
+
67
+ def color_nodes patterns, tree, node, exact
68
+ # # check if it needs color, if so set the color
69
+ # color = add_color_to_leaf_branch patterns, node, exact
70
+
71
+ # clean the name no matter what
72
+ node.name = clean_name node.name
73
+
74
+ # if its a leaf that hasnt been checked & needs color
75
+ if leaf?(tree, node) && !already_checked?(node.name) # && color
76
+ # check if it needs color, if so set the color
77
+
78
+ # NOTE: this was originally before cleaning the node name a
79
+ # couple lines up, does it matter that it is after?
80
+ color = add_color_to_leaf_branch patterns, node, exact
81
+
82
+ # add color to the name
83
+ node.name = node.name + color if color
84
+ elsif !leaf?(tree, node)
85
+ children = tree.children(node) # get the children
86
+ children_colors = []
87
+ children.each do |child|
88
+ # recurse to color the child if needed
89
+ color_nodes patterns, tree, child, exact
90
+ children_colors << get_color(child) # add color of the child
91
+ end
92
+
93
+ # if all the children have the same color
94
+ if children_colors.uniq.count == 1
95
+ # set the branch node to only the color name
96
+ node.name = children_colors[0]
97
+ end
98
+ end
99
+
100
+ return node
101
+ end
102
+ end
103
+ end