full_lengther_next 0.0.8 → 0.5.6

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.
Files changed (36) hide show
  1. data/.gemtest +0 -0
  2. data/History.txt +2 -2
  3. data/Manifest.txt +33 -18
  4. data/Rakefile +4 -2
  5. data/bin/download_fln_dbs.rb +310 -158
  6. data/bin/full_lengther_next +160 -103
  7. data/bin/make_test_dataset.rb +236 -0
  8. data/bin/make_user_db.rb +101 -117
  9. data/bin/plot_fln.rb +270 -0
  10. data/bin/plot_taxonomy.rb +70 -0
  11. data/lib/expresscanvas.zip +0 -0
  12. data/lib/full_lengther_next.rb +3 -3
  13. data/lib/full_lengther_next/classes/artifacts.rb +66 -0
  14. data/lib/full_lengther_next/classes/blast_functions.rb +326 -0
  15. data/lib/full_lengther_next/classes/cdhit.rb +154 -0
  16. data/lib/full_lengther_next/classes/chimeric_seqs.rb +315 -57
  17. data/lib/full_lengther_next/classes/common_functions.rb +105 -63
  18. data/lib/full_lengther_next/classes/exonerate_result.rb +258 -0
  19. data/lib/full_lengther_next/classes/fl_analysis.rb +226 -617
  20. data/lib/full_lengther_next/classes/fl_string_utils.rb +4 -2
  21. data/lib/full_lengther_next/classes/fln_stats.rb +598 -557
  22. data/lib/full_lengther_next/classes/handle_db.rb +30 -0
  23. data/lib/full_lengther_next/classes/my_worker.rb +308 -138
  24. data/lib/full_lengther_next/classes/my_worker_EST.rb +54 -0
  25. data/lib/full_lengther_next/classes/my_worker_manager_EST.rb +69 -0
  26. data/lib/full_lengther_next/classes/my_worker_manager_fln.rb +389 -0
  27. data/lib/full_lengther_next/classes/nc_rna.rb +5 -7
  28. data/lib/full_lengther_next/classes/reptrans.rb +210 -0
  29. data/lib/full_lengther_next/classes/sequence.rb +439 -80
  30. data/lib/full_lengther_next/classes/test_code.rb +15 -16
  31. data/lib/full_lengther_next/classes/types.rb +12 -0
  32. data/lib/full_lengther_next/classes/une_los_hit.rb +148 -230
  33. data/lib/full_lengther_next/classes/warnings.rb +40 -0
  34. metadata +207 -93
  35. data/lib/full_lengther_next/classes/lcs.rb +0 -33
  36. data/lib/full_lengther_next/classes/my_worker_manager.rb +0 -240
@@ -0,0 +1,30 @@
1
+ require 'scbi_fasta'
2
+
3
+
4
+ def load_isoform_hash(file)
5
+ isoform_hash = {}
6
+ if File.exists?(file)
7
+ fasta = FastaQualFile.new(file)
8
+ fasta.each do |name, seq, desc|
9
+ name =~ /(\w+\|(\w+)\-\d+\|)/
10
+ if isoform_hash[$2].nil?
11
+ isoform_hash[$2] = ">#{$1}#{desc}\n#{seq}"
12
+ else
13
+ isoform_hash[$2] += "\n>#{$1}#{desc}\n#{seq}"
14
+ end
15
+ end
16
+ fasta.close
17
+ end
18
+ return isoform_hash
19
+ end
20
+
21
+ def do_makeblastdb(seqs, output, dbtype)
22
+ cmd="makeblastdb -in - -out #{output} -title #{File.basename(output)} -dbtype #{dbtype} -parse_seqids"
23
+ IO.popen(cmd,'w+') {|makedb|
24
+ makedb.sync = TRUE
25
+ makedb.write(seqs)
26
+ makedb.close_write
27
+ puts makedb.readlines
28
+ makedb.close_read
29
+ }
30
+ end
@@ -1,15 +1,15 @@
1
1
  $: << File.expand_path(File.join(File.dirname(__FILE__)))
2
-
2
+
3
3
  require 'scbi_mapreduce'
4
4
  require 'scbi_blast'
5
- require 'json'
6
5
  require 'sequence'
7
6
  require 'fl_string_utils'
8
- require "lcs" # like the class simliar of seqtrim, return the longest common sequence
9
- require "test_code"
10
-
11
- require 'chimeric_seqs'
12
- include ChimericSeqs
7
+ require 'test_code'
8
+ require 'types'
9
+ require 'artifacts'
10
+ require 'blast_functions'
11
+ require 'exonerate_result'
12
+ require 'scbi_fasta'
13
13
 
14
14
  require 'fl_analysis'
15
15
  include FlAnalysis
@@ -18,166 +18,336 @@ require 'nc_rna'
18
18
  include NcRna
19
19
 
20
20
  class MyWorker < ScbiMapreduce::Worker
21
-
22
- def starting_worker
23
-
24
- # $WORKER_LOG.info "Loading actions"
25
- rescue Exception => e
26
- puts (e.message+ e.backtrace.join("\n"))
27
-
28
- end
29
-
30
- def receive_initial_config(obj)
31
-
21
+ #####################################################################################
22
+ # WORKER FUNCTIONS
23
+ #####################################################################################
24
+ def receive_initial_config(manager_options)
32
25
  # Reads the parameters
33
26
  # $WORKER_LOG.info "Params received: #{obj.to_json}"
34
- @options = obj
35
-
27
+ @options = manager_options
28
+ $verbose = manager_options[:verbose]
36
29
  end
37
30
 
38
- def process_object(obj)
39
-
40
- full_lenghter2(obj)
41
- return obj
42
-
31
+ def process_object(obj_sequence)
32
+ # Punto de arranque de FLN
33
+ $WORKER_LOG.info "Processing chunk: #{obj_sequence.first.seq_name}"
34
+ full_lenghter2(obj_sequence)
35
+ return obj_sequence
43
36
  end
44
37
 
45
38
  def closing_worker
46
39
 
47
40
  end
41
+
42
+ #####################################################################################
43
+ # FLN FUNCTIONS
44
+ #####################################################################################
45
+
46
+ #----------------------------------------------------------------------------------
47
+ # MAIN FUNCTION
48
+ #----------------------------------------------------------------------------------
49
+
50
+ def full_lenghter2(seqs)
51
+ #seqs.map{|seq| seq.change_degenerated_nt!} # Clean degenerated nt
48
52
 
49
- # ejecuta blast utilizando los parametros fichero de entrada, base de datos, fichero de salida y tipo de blast
50
- def run_blast(input, database, blast_type, evalue)
51
-
52
- if (@options[:chimera].nil?)
53
- blast=BatchBlast.new("-db #{database}",blast_type,"-evalue #{evalue} -max_target_seqs 1")
53
+ # User database
54
+ #--------------------------------------------
55
+ # if the user has included his own database in the parameters entry,
56
+ # the location of the database is tested, and blast and the results analysis is done
57
+ check_seqs = seqs
58
+ if @options[:user_db]
59
+ user_db = File.basename(@options[:user_db])
60
+ check_seqs = check_prot_db(seqs, @options[:user_db], 'blastx', 1, user_db, @options[:blast])
61
+ end
62
+
63
+ # UniProt (sp)
64
+ #--------------------------------------------
65
+ if @options[:acess_db].include?('s')
66
+ sp_db = 'sp_'+@options[:tax_group]
67
+ sp_path = File.join(sp_db, 'sp_'+@options[:tax_group])
68
+ check_seqs = check_prot_db(check_seqs, sp_path, 'blastx', 1, sp_db, @options[:blast])
69
+ end
70
+
71
+ # UniProt (tr)
72
+ #--------------------------------------------
73
+ if @options[:acess_db].include?('t')
74
+ tr_db = 'tr_'+@options[:tax_group]
75
+ tr_path = File.join(tr_db,'tr_'+@options[:tax_group])
76
+ check_seqs = check_prot_db(check_seqs, tr_path, 'blastx', 1, tr_db, @options[:blast])
77
+ end
78
+
79
+ # nc RNA
80
+ #--------------------------------------------
81
+ if @options[:acess_db].include?('n')
82
+ check_seqs = seqs.select{|s| s.type == UNKNOWN}
83
+ ncrna_path = File.join('nc_rna_db','ncrna')
84
+ check_ncRNA(check_seqs, ncrna_path, 'blastn', 1e-3)
85
+ end
86
+
87
+ # Test Code
88
+ #--------------------------------------------
89
+ # the sequences without a reliable similarity with an orthologue are processed with Test Code
90
+ if @options[:acess_db].include?('c')
91
+ check_seqs = seqs.select{|s| s.type == UNKNOWN }
92
+ check_testcode(check_seqs)
93
+ end
94
+ end
95
+ #----------------------------------------------------------------------------------
96
+ # END MAIN
97
+ #----------------------------------------------------------------------------------
98
+
99
+ def check_prot_db(seqs, db_path, blast_type, evalue, db_name, additional_blast_options)
100
+
101
+ if $verbose > 0
102
+ puts "\e[33m=========================================\e[0m",
103
+ "\e[33m#{db_name}\t#{seqs.length}\e[0m",
104
+ "\e[33m=========================================\e[0m"
105
+ end
106
+ my_blast = run_blast(seqs, db_path, blast_type, evalue, additional_blast_options, @options[:exonerate]) # do blast
107
+ new_seqs = []
108
+ seqs.each_with_index do |seq, i| # parse blast
109
+ puts "\e[31m#{seq.seq_name}\e[0m" if $verbose > 0 ## VERBOSE
110
+ if !my_blast.querys[i].hits.first.nil?
111
+ status='Artifact analysis'
112
+ begin
113
+ check_blast(seq, my_blast.querys[i]) # Check if seq and query are the same
114
+ if !artifact?(seq, my_blast.querys[i], db_name, db_path, @options, new_seqs)
115
+ status = 'Full length analysis'
116
+ best_hits = filter_hits(my_blast.querys[i], 100)
117
+ record_position = seqs.index(seq)
118
+ seq = search_best_orf_y_fl(seq, best_hits, @options, db_name)# FULL LENGTH ANALYSIS
119
+ seqs[record_position]= seq #Replace the old seq by the new seq
120
+ seq.area_without_annotation? if @options[:chimera] != 'd' && !seq.hit.nil?
121
+ end
122
+ rescue Exception => e
123
+ rescue_sequence(e, seq, status)
124
+ end
125
+ end
126
+ end
127
+ seqs.concat(new_seqs)
128
+ check_seqs = seqs.select{|s| !s.ignore || (s.type == COMPLETE && s.area_without_annotation)}
129
+ return check_seqs
130
+ end
131
+
132
+
133
+ # ejecuta blast utilizando los parametros fichero de entrada, base de datos, tipo de blast y evalue
134
+ def run_blast(input, database, blast_type, evalue, additional_blast_options, do_exonerate, filter = TRUE)
135
+ if !input.empty? && !input.nil?
136
+ $WORKER_LOG.info "DB: #{File.basename(database)} #{input.length}"
137
+ blast = BatchBlast.new("-db #{database}", blast_type, "-evalue #{evalue} #{additional_blast_options}")
138
+ chunk_name = input.first.seq_name.gsub(/\W+/,'_')
139
+ file_path = File.join('temp', File.basename(database)+'_'+chunk_name)
140
+ if @options[:hdd] #Write/parse blast on Disk
141
+ file_name = file_path+'.blast' #Each blast is identified with database_name and first sequence's name on chunk
142
+ if !File.exists?(file_name)
143
+ blast_result = blast.do_blast_seqs(input, :table, TRUE, file_name)
144
+ else
145
+ blast = nil
146
+ blast_result=BlastTableResult.new(file_name)
147
+ end
148
+ else
149
+ blast_result = blast.do_blast_seqs(input, :table)
150
+ end
151
+ refine_analysis_with_exonerate(blast_result, input, file_path, database, @options[:ident]) if do_exonerate
152
+ if filter #Delete hits with low identity, this enables ident filter on normal FLN execution and disables it when RepTrans with my_workerEST
153
+ clean_by_identity(blast_result, @options[:ident])
154
+ #clean_by_query_length_match(blast_result, 1000)#60 is min length of the match in nt
155
+ end
156
+ $WORKER_LOG.info "#BLAST ENDED"
157
+ return blast_result
54
158
  else
55
- blast=BatchBlast.new("-db #{database}",blast_type,"-evalue #{evalue}")
159
+ return nil
56
160
  end
57
-
58
- blast_result = blast.do_blast_seqs(input, :xml)
59
-
60
- return blast_result
61
161
  end
62
162
 
163
+ def rescue_sequence(e, seq, status)
164
+ seq.save_fasta = FALSE
165
+ seq.ignore = TRUE
166
+ seq.type = FAILED
167
+ puts '-- '+seq.seq_name+' FAILED ANALYSIS -- '+status,
168
+ e.message,
169
+ e.backtrace.join("\n")
170
+ end
171
+
172
+ def check_ncRNA(check_seqs, ncrna_path, blast_type, evalue)
173
+ my_blast = run_blast(check_seqs, ncrna_path, blast_type, evalue, '', FALSE, nil)
174
+ if !my_blast.nil?
175
+ check_seqs.each_with_index do |seq,i|
176
+ find_nc_rna(seq, my_blast.querys[i])
177
+ end
178
+ end
179
+ end
63
180
 
64
- def full_lenghter2(seqs)
65
-
66
- # -------------------------------------------- User database
67
- # if the user has included his own database in the parameters entry,
68
- # the location of the database is tested, and blast and the results analysis is done
69
-
70
- if (@options[:user_db])
71
-
72
- if (@options[:user_db] =~ /\//)
73
- user_db_name = @options[:user_db].sub(/.+\//,'')
181
+ def check_testcode(check_seqs)
182
+ check_seqs.map{|seq| TestCode.new(seq)}
183
+ end
184
+
185
+ def check_blast(seq, blast_query)
186
+ if seq.seq_name != blast_query.query_def # used to detect if the sequence and the blast are from different query
187
+ raise "BLAST query name and sequence are different"
188
+ end
189
+ end
190
+
191
+ def search_best_orf_y_fl(seq, best_hits, options, db_name)
192
+ warning = nil
193
+ if best_hits.length > 1
194
+ all_options = []
195
+ best_hits.map{|hit|
196
+ new_seq = seq.clone
197
+ puts "\n\t\e[35mCheck protein #{hit.first.acc}\e[0m" if $verbose > 1 ## VERBOSE
198
+ analiza_orf_y_fl(new_seq, hit, options, db_name)
199
+ all_options << new_seq
200
+ }
201
+ all_options.select!{|option| option.type > UNKNOWN}
202
+ best_type = all_options.map{|option| option.type}.min
203
+ best_options = all_options.select{|option| option.type == best_type}
204
+ filtered_options = best_options.select{|option| option.status} # Select sure options
205
+ filtered_options = best_options if filtered_options.empty? # All options are putative
206
+ #best_option = filtered_options.first # select hit with big perc ident query
207
+ best_option = filtered_options.sort{|seq1, seq2| seq2.hit.ident <=> seq1.hit.ident}.first # select hit with big perc ident query
208
+ if !all_options.empty? # There is one sequence unless
209
+ warning = [['PositionResult', all_options.index(best_option)+1]]
210
+ else
211
+ best_option = seq
74
212
  end
75
-
76
- if !File.exists?("#{File.expand_path(@options[:user_db])}.psq")
77
- puts "user database: #{@options[:user_db]} was not found"
78
- exit
213
+ else
214
+ analiza_orf_y_fl(seq, best_hits.first, options, db_name)
215
+ best_option = seq
216
+ warning = 'SingleResult'
217
+ end
218
+
219
+ if seq.type == FAILED
220
+ seq.type = UNKNOWN
221
+ seq.ignore = FALSE
222
+ else
223
+ best_option.warnings(warning) if !warning.nil?
224
+ end
225
+ return best_option
226
+ end
227
+
228
+ def refine_analysis_with_exonerate(blast_result, input, file_path, database, ident)
229
+ querys_stats = hits_statistics(blast_result)
230
+ if !querys_stats.empty?
231
+ querys, targets = select_sequences(querys_stats)
232
+ if !querys.empty? && !targets.empty?
233
+ write_querys(querys, input, file_path)
234
+ write_targets(targets, file_path, database)
235
+ file_name = file_path + '.exonerate'
236
+ system("exonerate --useaatla 0 --showalignment 0 --model protein2dna #{file_path+'.prot'} #{file_path+'.dna'} > #{file_name}") if !File.exists?(file_name)
237
+ seqs = {}
238
+ querys.map{|position| seqs[input[position].seq_name] = input[position].seq_fasta}
239
+ exonerate_result = ExonerateResult.new(file_name, seqs, get_prot_sequences(file_path))
240
+ clean_subjec_ids_name(exonerate_result)
241
+ replace_hits(blast_result, exonerate_result)
79
242
  end
80
-
81
- # do blast
82
- my_blast = run_blast(seqs, "#{@options[:user_db]}", 'blastx', '1e-6')
83
-
84
- # chimera detection
85
- if (!@options[:chimera].nil?)
86
- seqs.each_with_index do |seq,i|
87
- if (!my_blast.querys[i].hits[0].nil?)
88
- search_chimeras(seq, my_blast.querys[i], @options, user_db_name)
243
+ end
244
+ end
245
+
246
+ def replace_hits(blast_result, exonerate_result)
247
+ blast_result.querys.each do |query|
248
+ exonerate_query = blast_result.find_query(exonerate_result.querys, query.query_def)
249
+ if !exonerate_query.nil?
250
+ blast_hits = cluster_hsps(query.hits)
251
+ exonerate_hits = cluster_hsps(exonerate_query.hits)
252
+ blast_hits.map! {|hit|
253
+ num_hsps = hit.length
254
+ if num_hsps > 1
255
+ exonerate_hit = find_hit(hit.first.acc, exonerate_hits)
256
+ if !exonerate_hit.nil? && exonerate_hit.length < num_hsps #We replace hits with by hits with less hsps because we supose that exonerate has merged them
257
+ exonerate_hit.map{|ex_hit|
258
+ ex_hit.s_len = hit.first.s_len
259
+ ex_hit.q_len = hit.first.q_len
260
+ ex_hit.definition = hit.first.definition
261
+ }
262
+ exonerate_hit
263
+ else
264
+ hit
265
+ end
266
+ else
267
+ hit
89
268
  end
90
- end
91
-
92
- seqs=seqs.select{|s| s.get_annotations(:chimera).empty?}
93
- my_blast = select_best_blast(my_blast, seqs)
94
- end
95
-
96
- # split and parse blast
97
- seqs.each_with_index do |seq,i|
98
- analiza_orf_y_fl(seq, my_blast.querys[i], @options, user_db_name)
269
+ }
270
+ query.hits = blast_hits.flatten
99
271
  end
100
-
101
- new_seqs=seqs.select{|s| (s.get_annotations(:complete).empty? && s.get_annotations(:chimera).empty?)}
102
-
103
- else
104
- new_seqs = seqs
105
272
  end
273
+ end
106
274
 
107
- return if new_seqs.empty?
108
-
109
- # -------------------------------------------- UniProt (sp)
110
- # blast
111
- sp_path=File.join("sp_#{@options[:tax_group]}","sp_#{@options[:tax_group]}.fasta")
112
- my_blast = run_blast(new_seqs, sp_path, 'blastx', '1e-6')
113
-
114
- # chimera detection
115
- if (!@options[:chimera].nil?)
116
- new_seqs.each_with_index do |seq,i|
117
- if (!my_blast.querys[i].hits[0].nil?)
118
- search_chimeras(seq, my_blast.querys[i], @options, "sp_#{@options[:tax_group]}")
275
+ def clean_subjec_ids_name(exonerate_result)
276
+ exonerate_result.querys.each do |query|
277
+ query.hits.map{|hit|
278
+ hit.subject_id.sub!('lcl|','')
279
+ hit.acc.sub!('lcl|','')
280
+ }
281
+ end
282
+ end
283
+
284
+ def hits_statistics(blast_result)
285
+ querys_stats = []
286
+ blast_result.querys.each_with_index do |query, ind|
287
+ if !query.hits.empty?
288
+ query.hits.each do |hit|
289
+ if querys_stats[ind].nil?
290
+ querys_stats[ind] = {hit.acc => 1}
291
+ else
292
+ if querys_stats[ind][hit.acc].nil?
293
+ querys_stats[ind][hit.acc] = 1
294
+ else
295
+ querys_stats[ind][hit.acc] += 1
296
+ end
297
+ end
119
298
  end
120
299
  end
121
- new_seqs=seqs.select{|s| s.get_annotations(:chimera).empty?}
122
- my_blast = select_best_blast(my_blast, new_seqs)
123
300
  end
124
-
125
- # split and parse blast
126
- new_seqs.each_with_index do |seq,i|
127
- analiza_orf_y_fl(seq, my_blast.querys[i], @options, "sp_#{@options[:tax_group]}")
128
- end
129
-
130
- new_seqs=seqs.select{|s| (s.get_annotations(:complete).empty? && s.get_annotations(:chimera).empty?)}
131
- return if new_seqs.empty?
132
-
133
- # -------------------------------------------- UniProt (tr)
134
- # blast
135
- tr_path=File.join("tr_#{@options[:tax_group]}","tr_#{@options[:tax_group]}.fasta")
136
- my_blast = run_blast(new_seqs, tr_path, 'blastx', '1e-6')
137
-
138
- # chimera detection
139
- if (!@options[:chimera].nil?)
140
- new_seqs.each_with_index do |seq,i|
141
- if (!my_blast.querys[i].hits[0].nil?)
142
- search_chimeras(seq, my_blast.querys[i], @options, "tr_#{@options[:tax_group]}")
301
+ return querys_stats
302
+ end
303
+
304
+ def select_sequences(querys_stats)
305
+ querys = []
306
+ targets = []
307
+ querys_stats.each_with_index do |hits, query_position|
308
+ if !hits.nil?
309
+ hits.each do |hit_id, n_hsps|
310
+ if n_hsps > 1
311
+ querys << query_position if !querys.include?(query_position)
312
+ targets << hit_id if !targets.include?(hit_id)
313
+ end
143
314
  end
144
315
  end
145
- new_seqs=new_seqs.select{|s| s.get_annotations(:chimera).empty?}
146
- my_blast = select_best_blast(my_blast, new_seqs)
147
316
  end
148
-
149
- # split and parse blast
150
- new_seqs.each_with_index do |seq,i|
151
- analiza_orf_y_fl(seq, my_blast.querys[i], @options, "tr_#{@options[:tax_group]}")
317
+ return querys, targets
318
+ end
319
+
320
+ def write_querys(querys, input, file_path)
321
+ file_name = file_path+'.dna'
322
+ if !File.exists?(file_name)
323
+ fasta = File.open(file_name, 'w')
324
+ querys.each do |query_position|
325
+ seq = input[query_position]
326
+ fasta.puts ">#{seq.seq_name}\n#{seq.seq_fasta}"
327
+ end
328
+ fasta.close
152
329
  end
153
-
154
- # -------------------------------------------- Test Code
155
- # the sequences without a reliable similarity with an orthologue are processed with Test Code
156
- testcode_input=seqs.select{|s| (!s.get_annotations(:apply_tcode).empty? && s.get_annotations(:chimera).empty?)}
157
- return if testcode_input.empty?
158
-
159
- # active this line to test tcode, and comment all lines above in this function
160
- # testcode_input=seqs
161
-
162
- testcode_input.each do |seq|
163
- TestCode.new(seq)
330
+ end
331
+
332
+ def write_targets(targets, file_path, database)
333
+ puts "-- This batch has not unigenes for exonerate: #{file_path}" if targets.empty?
334
+ file_name = file_path+'.prot'
335
+ if !File.exists?(file_name)
336
+ targets.each_slice(400) do |slice| #This loop avoids shell buffered out when the list of entries is huge
337
+ entries = slice.join(',')
338
+ system("blastdbcmd -db #{database} -entry #{entries} >> #{file_name}")
339
+ end
164
340
  end
165
-
166
- # -------------------------------------------- nc RNA
167
- unknown_seqs=seqs.select{|s| !s.get_annotations(:tcode_unknown).empty?}
168
- return if unknown_seqs.empty?
169
-
170
- # run blastn
171
- ncrna_path=File.join('nc_rna_db','ncrna_fln_100.fasta')
172
- my_blast = run_blast(unknown_seqs, ncrna_path, 'blastn', '1e-3')
173
-
174
- # split and parse blast
175
- unknown_seqs.each_with_index do |seq,i|
176
- find_nc_rna(seq, my_blast.querys[i])
341
+ end
342
+
343
+ def get_prot_sequences(file_path)
344
+ sequences = {}
345
+ file_name = file_path+'.prot'
346
+ fqr = FastaQualFile.new(file_name)
347
+ fqr.each do |name,seq_fasta|
348
+ sequences[name] = seq_fasta
177
349
  end
178
- # ---------------------------------------------------
179
-
350
+ fqr.close
351
+ return sequences
180
352
  end
181
-
182
353
  end
183
-