File Coverage

blib/lib/Bio/MUST/Core/SeqMask.pm
Criterion Covered Total %
statement 233 295 78.9
branch 20 38 52.6
condition 21 32 65.6
subroutine 43 46 93.4
pod 24 24 100.0
total 341 435 78.3


line stmt bran cond sub pod time code
1             package Bio::MUST::Core::SeqMask;
2             # ABSTRACT: Sequence mask for selecting specific sites
3             # CONTRIBUTOR: Catherine COLSON <ccolson@doct.uliege.be>
4             # CONTRIBUTOR: Raphael LEONARD <rleonard@doct.uliege.be>
5             $Bio::MUST::Core::SeqMask::VERSION = '0.212650';
6 17     17   131 use Moose;
  17         42  
  17         139  
7 17     17   121299 use namespace::autoclean;
  17         51  
  17         199  
8              
9 17     17   1725 use autodie;
  17         48  
  17         181  
10 17     17   93135 use feature qw(say);
  17         60  
  17         1775  
11              
12             # use Smart::Comments '###';
13              
14 17     17   134 use Carp;
  17         37  
  17         1429  
15 17     17   142 use File::Basename;
  17         49  
  17         1641  
16 17     17   11285 use IPC::System::Simple qw(system);
  17         107767  
  17         1408  
17 17     17   3342 use List::AllUtils 0.08 qw(uniq max sum natatime first_index last_index pairmap mesh);
  17         53463  
  17         2167  
18 17     17   151 use Path::Class qw(file);
  17         93  
  17         870  
19 17     17   115 use POSIX qw(ceil floor);
  17         48  
  17         119  
20              
21 17     17   1297 use Bio::MUST::Core::Types;
  17         52  
  17         561  
22 17     17   112 use Bio::MUST::Core::Constants qw(:gaps :files);
  17         49  
  17         4261  
23 17     17   9105 use Bio::MUST::Core::Utils qw(change_suffix);
  17         52  
  17         1144  
24 17     17   154 use aliased 'Bio::MUST::Core::Seq';
  17         39  
  17         171  
25 17     17   3872 use aliased 'Bio::MUST::Core::Ali';
  17         45  
  17         85  
26 17     17   2325 use aliased 'Bio::MUST::Core::IdList';
  17         42  
  17         117  
27             with 'Bio::MUST::Core::Roles::Commentable';
28              
29              
30             # public array
31             # Note: mask methods use the 0-based C/Perl array indexing
32             # ...but block methods encode bounds using the usual 1-based indexing
33             has 'mask' => (
34             traits => ['Array'],
35             is => 'ro',
36             isa => 'ArrayRef[Bool]',
37             default => sub { [] },
38             writer => '_set_mask',
39             handles => {
40             mask_len => 'count',
41             all_states => 'elements',
42             add_state => 'push',
43             set_state => 'set',
44             state_at => 'get',
45             },
46             );
47              
48              
49              
50             sub first_site {
51 2     2 1 6 my $self = shift;
52 2     8   97 return first_index { $_ } $self->all_states;
  8         104  
53             }
54              
55              
56              
57             sub last_site {
58 2     2 1 5 my $self = shift;
59 2     46   94 return last_index { $_ } $self->all_states;
  46         77  
60             }
61              
62              
63              
64             sub count_sites {
65 76     76 1 42024 my $self = shift;
66 76         3417 my $count = grep { $_ } $self->all_states;
  70226         108874  
67 76         3335 return $count;
68             }
69              
70              
71              
72             sub coverage {
73 2     2 1 6 my $self = shift;
74 2         7 return $self->count_sites / $self->mask_len;
75             }
76              
77              
78              
79             sub empty_mask {
80 48     48 1 136 my $class = shift;
81 48   100     163 my $len = shift // 0;
82              
83 48         13267 return $class->new( mask => [ (0) x $len ] );
84             }
85              
86              
87              
88             sub custom_mask {
89 33     33 1 153 my $class = shift;
90 33         89 my $len = shift;
91 33         64 my $sites = shift;
92              
93 33         163 my $mask = $class->empty_mask($len);
94 33         100 $mask->set_state($_, 1) for @{$sites};
  33         2043  
95              
96 33         3381 return $mask;
97             }
98              
99              
100              
101             sub mark_block { ## no critic (RequireArgUnpacking)
102 40     40 1 124 return shift->_change_block(1, @_);
103             }
104              
105              
106              
107             sub unmark_block { ## no critic (RequireArgUnpacking)
108 0     0 1 0 return shift->_change_block(0, @_);
109             }
110              
111              
112             sub _change_block {
113 40     40   72 my $self = shift;
114 40         63 my $state = shift;
115 40         71 my $start = shift;
116 40         67 my $end = shift;
117              
118 40         2061 $self->set_state($_, $state) for $start-1 .. $end-1;
119              
120 40         112 return $self;
121             }
122              
123              
124              
125             sub mask2blocks {
126 14     14 1 67 my $self = shift;
127              
128 14         25 my @blocks;
129 14         24 my $start = -1;
130 14         25 my $site = 0;
131 14         542 while ($site < $self->mask_len) {
132              
133             # open a new block (if not yet open)
134 217 100       7065 if ( $self->state_at($site) ) {
135 123 100       302 $start = $site+1 if $start < 0;
136             }
137              
138             # close the current block (if any open)
139             else {
140 94 100       198 if ($start >= 0) {
141 34         87 push @blocks, [ $start, $site ];
142 34         58 $start = -1;
143             }
144             }
145              
146 217         7334 $site++;
147             }
148              
149             # close last block (if still open)
150 14 100       238 push @blocks, [ $start, $site ] if $start >= 0;
151              
152 14         71 return \@blocks;
153             }
154              
155              
156              
157             sub blocks2mask {
158 14     14 1 13548 my $class = shift;
159 14         39 my $blocks = shift;
160              
161             # old version: only handled non-overlapping ordered blocks
162             # my @mask;
163             # for my $block ( @{$blocks} ) {
164             # my ($start, $end) = @{$block};
165             # push @mask, (0) x ($start-@mask-1); # pad last block
166             # push @mask, (1) x ($end-$start+1); # mark new block
167             # }
168             # return \@mask;
169              
170             # Note: blocks can now overlap and come in any order
171             # this allows using SeqMask for computing seq coverages
172              
173             # setup empty mask going up to max end (second slot)
174 14         25 my $max = max map { $_->[1] } @{$blocks};
  37         125  
  14         46  
175 14         63 my $mask = $class->empty_mask($max);
176              
177             # mark all states included in each block
178 14         32 $mask->mark_block( @{$_} ) for @{$blocks};
  14         46  
  37         130  
179              
180 14         481 return $mask;
181             }
182              
183              
184             # Ali-based SeqMask factory methods
185              
186              
187             sub neutral_mask {
188 1     1 1 12 my $class = shift;
189 1         3 my $ali = shift;
190              
191 1         5 my $width = $ali->width;
192 1         122 return $class->new( mask => [ (1) x $width ] );
193             }
194              
195              
196              
197             sub variable_mask {
198 3     3 1 1330 my $class = shift;
199 3         8 my $ali = shift;
200              
201             # TODO: profile and optimize as ideal_mask below (if needed)
202              
203 3         9 my @mask;
204 3         15 my $width = $ali->width;
205 3         21 my $regex = $ali->gapmiss_regex;
206              
207             # filter out sites with only one state
208 3         23 for (my $site = 0; $site < $width; $site++) {
209             my $state_n = uniq # count unique states
210 432032         1240495 grep { $_ !~ m/$regex/xms } # which are valid
211 53696         1703907 map { $_->state_at($site) } # and found at that site
  432032         921576  
212             $ali->all_seqs # across all seqs
213             ;
214 53696 100       225897 push @mask, $state_n > 1 ? 1 : 0;
215             }
216              
217 3         122 return $class->new( mask => \@mask );
218             }
219              
220              
221              
222             sub parsimony_mask {
223 1     1 1 29 my $class = shift;
224 1         5 my $ali = shift;
225              
226             # TODO: profile and optimize as ideal_mask below (if needed)
227              
228 1         2 my @mask;
229 1         6 my $width = $ali->width;
230 1         7 my $regex = $ali->gapmiss_regex;
231              
232             # filter out sites with only one state
233 1         18 for (my $site = 0; $site < $width; $site++) {
234             my @states = # get states
235 209856         597354 grep { $_ !~ m/$regex/xms } # which are valid
236 26232         868787 map { $_->state_at($site) } # and found at that site
  209856         445343  
237             $ali->all_seqs; # across all seqs
238             ;
239             #### @states
240              
241             # check whether at least two states are seen at least twice
242 26232         58384 my %count_for;
243 26232         101683 $count_for{$_}++ for @states;
244             #### %count_for
245 26232         56271 my $state_n = grep { $count_for{$_} >= 2 } keys %count_for;
  60682         115502  
246             #### $state_n
247 26232 100       114409 push @mask, $state_n >= 2 ? 1 : 0;
248             }
249              
250 1         64 return $class->new( mask => \@mask );
251             }
252              
253              
254             sub ideal_mask {
255 13     13 1 29 my $class = shift;
256 13         27 my $ali = shift;
257 13   50     41 my $max_res = shift // 0; # defaults to shared gaps only
258              
259             # convert fractional max_res to conservative integer (if needed)
260 13 100 66     426 $max_res = floor($max_res * $ali->count_seqs)
261             if 0 < $max_res && $max_res < 1;
262              
263             # pick up right regex based on sequence type
264 13         51 my $regex = $ali->gapmiss_regex;
265              
266             # determine count of non-gap-nor-missing states for each site
267              
268             # original version: easy to understand but much too slow on large matrices
269             # because of unavoidable regex recompilation for each character state
270             # (lots of calls to CORE::regcomp in output Devel::NYTProf)
271             # my @counts;
272             # for my $seq ($ali->all_seqs) {
273             # my $site = 0;
274             # $counts[$site++] += ($_ !~ $regex) for $seq->all_states;
275             # }
276              
277             # 'all magic comes with a price' version: the /o regex modifier does speed
278             # up things but compiles the regex once for all, which leads to bugs when
279             # dealing with both nucleotide and protein alignments in the same run
280             # my @counts;
281             # for my $seq ($ali->all_seqs) {
282             # my $site = 0;
283             # $counts[$site++] += ($_ !~ m/$regex/o) for $seq->all_states;
284             # }
285              
286             # improved version: more convoluted but much faster
287             # idea: directly search in sequence strings
288             # algorithm: set maximal count for each site
289             # then: decrease corrresponding count for each gap-or-missing state
290             # my @counts = ( $ali->count_seqs ) x $ali->width;
291             # for my $seq ($ali->all_seqs) {
292             # my $str = $seq->seq;
293             # while ($str =~ m/$regex/xmsg) {
294             # $counts[ pos($str) - 1 ]--;
295             # }
296             # }
297              
298             # parallel version: quite complex and inefficient
299             # setup threading
300             # my $thread_n = 2;
301             # my $pl = Parallel::Loops->new($thread_n);
302             # my %counts;
303             # $pl->share( \%counts );
304             #
305             # # define sites ranges
306             # my $site_n = $ali->width;
307             # my $chunk = ceil( $site_n / $thread_n );
308             # my @ranges;
309             # for (my $start = 0; $start < $site_n; $start += $chunk) {
310             # my $end = min( $start + $chunk, $site_n );
311             # push @ranges, [ $start..$end-1 ];
312             # }
313             #
314             # # process ranges in parallel
315             # my @seqs = $ali->all_seqs;
316             # $pl->foreach( \@ranges, sub {
317             # for my $seq (@seqs) {
318             # $counts{$_} += $seq->state_at($_) !~ $regex for @{$_};
319             # }
320             # } );
321             #
322             # # filter out sites with at most max_res non-gap-nor-missing states
323             # my @mask = map { $counts{$_} > $max_res ? 1 : 0 }
324             # sort { $a <=> $b } keys %counts; # watch the sorting cost!
325              
326             # current version: even more complex but even more faster
327             # idea: search for (long) gaps instead of individual states
328             # algorithm: set maximal count for each site
329             # then: decrease counts for each stretch of gap-or-missing states
330 13         453 my @counts = ( $ali->count_seqs ) x $ali->width;
331 13         483 for my $seq ($ali->all_seqs) {
332 160         4661 my $str = $seq->seq;
333 160         1064 while ($str =~ m/($regex+)/xmsg) { # look for next gap
334 7532         12431 my $end = pos($str) - 1; # compute gap start
335 7532         12624 my $begin = $end - length($1) + 1; # and gap end
336 7532         97422 $counts[$_]-- for $begin..$end; # decrease count over the gap
337             }
338             }
339              
340             # filter out sites with at most max_res non-gap-nor-missing states
341 13 100       189 my @mask = map { $_ > $max_res ? 1 : 0 } @counts;
  86155         134783  
342              
343 13         849 return $class->new( mask => \@mask );
344             }
345              
346              
347             my %gb_parms_for = (
348             strict => [ 0.85, 8, 10, 'N' ], # emulate original MUST settings
349             medium => [ 0.75, 5, 5, 'H' ],
350             loose => [ 0.55, 5, 5, 'H' ],
351             );
352              
353             sub gblocks_mask {
354 0     0 1 0 my $class = shift;
355 0         0 my $ali = shift;
356 0   0     0 my $mode = shift // 'strict';
357              
358             # check Gblocks settings
359 0 0       0 if ( !defined $gb_parms_for{$mode} ) {
360 0         0 carp "[BMC] Warning: invalid Gblocks settings: $mode; using strict!";
361 0         0 $mode = 'strict';
362             }
363              
364             # create temp .fasta infile for Gblocks
365 0         0 my $infile = $ali->temp_fasta;
366              
367             # compute Gblocks parms depending on specified mode
368             my ($t, $b1, $b2, $b3, $b4, $b5) = (
369             ($ali->is_protein ? 'p' : 'd'), # Sequence Type
370             int($ali->count_seqs / 2) + 1, # Conserved Position
371             int($ali->count_seqs * $gb_parms_for{$mode}[0]), # Flank Position
372 0 0       0 @{ $gb_parms_for{$mode} }[1..3], # other block parms
  0         0  
373             );
374 0         0 $b2 = max($b1, $b2); # with few seqs 0.55*n can be smaller than n/2+1
375              
376             # create Gblocks command
377 0         0 my $cmd = "Gblocks $infile -t=$t"
378             . " -b1=$b1 -b2=$b2 -b3=$b3 -b4=$b4 -b5=$b5"
379             . ' -s=n > /dev/null 2> /dev/null' # minimal output
380             ;
381              
382             # try to robustly execute Gblocks
383 0         0 my $ret_code = system( [ 1, 127 ], $cmd); # Gblocks always returns 1?!?
384 0 0       0 if ($ret_code == 127) {
385 0         0 carp '[BMC] Warning: cannot execute Gblocks command;'
386             . ' returning neutral mask!';
387 0         0 return $class->neutral_mask($ali);
388             }
389             # TODO: try to bypass shell (need for absolute path to executable then)
390              
391             # parse Gblocks output to get blocks
392 0         0 my $outfile = $infile . '-gb.htm';
393 0         0 open my $out, '<', $outfile;
394              
395 0         0 my @blocks;
396 0         0 while (my $line = <$out>) {
397              
398             # only use the 'Flanks:' line
399 0         0 my ($flanks) = $line =~ m/\AFlanks:\s+(.*)/xms;
400 0 0       0 if (defined $flanks) {
401              
402             # isolate numbers
403 0         0 chomp $flanks;
404 0         0 $flanks =~ s/[\[\]]//xmsg;
405 0         0 my @bound_pairs = split /\s+/xms, $flanks;
406              
407             # take number pairs as block bounds
408 0         0 my $it = natatime 2, @bound_pairs;
409 0         0 while ( my @bounds = $it->() ) {
410 0         0 push @blocks, \@bounds;
411             }
412             }
413              
414             # ...and the 'New number of positions' line
415             # to check Gblocks didn't shrink the original Ali due to shared gaps
416             # which would left-shift all blocks bounds!
417 0         0 my ($width) = $line =~ m/original\s+(\d+)\s+positions/xms;
418 0 0 0     0 if (defined $width && $width != $ali->width) {
419 0         0 carp '[BMC] Warning: shared gaps detected; returning neutral mask!';
420 0         0 return $class->neutral_mask($ali);
421             }
422             }
423              
424             # unlink temp files
425 0         0 file( $infile)->remove;
426 0         0 file($outfile)->remove;
427              
428             # compute SeqMask from Gblocks blocks
429 0         0 return $class->blocks2mask( \@blocks );
430             }
431              
432              
433             my %bmge_parms_for = (
434             strict => [ 0.4, 0.05 ],
435             medium => [ 0.5, 0.20 ],
436             loose => [ 0.6, 0.40 ],
437             );
438              
439             sub bmge_mask {
440 0     0 1 0 my $class = shift;
441 0         0 my $ali = shift;
442 0   0     0 my $mode = shift // 'strict';
443              
444             # check BMGE settings
445 0 0       0 if ( !defined $bmge_parms_for{$mode} ) {
446 0         0 carp "[BMC] Warning: invalid BMGE settings: $mode; using strict!";
447 0         0 $mode = 'strict';
448             }
449              
450             # create temp .fasta infile for BMGE
451 0         0 my $infile = $ali->temp_fasta;
452              
453             # compute BMGE parms depending on specified mode
454 0         0 my ($entropy_cutoff, $gap_cutoff) = @{ $bmge_parms_for{$mode} };
  0         0  
455 0 0       0 my $coding_type = $ali->is_protein ? 'AA' : 'DNA';
456 0         0 my $outfile = $infile . '-bmge.htm';
457              
458             # create BMGE command
459 0         0 my $cmd = "bmge.sh $infile"
460             . " $coding_type $entropy_cutoff $gap_cutoff $outfile > /dev/null";
461              
462             # try to robustly execute BMGE
463 0         0 my $ret_code = system( [ 0, 127 ], $cmd);
464 0 0       0 if ($ret_code == 127) {
465 0         0 carp '[BMC] Warning: cannot execute BMGE command;'
466             . ' returning neutral mask!';
467 0         0 return $class->neutral_mask($ali);
468             }
469             # TODO: try to bypass shell (need for absolute path to executable then)
470              
471             # parse BMGE output to get selected sites
472 0         0 open my $out, '<', $outfile;
473 0         0 my $len = $ali->width;
474 0         0 my $sites;
475              
476             LINE:
477 0         0 while (my $line = <$out>) {
478 0 0       0 if ($line =~ m/\s+ selected: \s+ ([\ (0-9)]+)/xms) {
479 0         0 $sites = [ map { $_ - 1 } split /\s+/xms, $1 ];
  0         0  
480 0         0 last LINE;
481             }
482             }
483              
484             # unlink temp files
485 0         0 file($infile)->remove;
486 0         0 file($outfile)->remove;
487              
488 0         0 return $class->custom_mask($len, $sites);
489             }
490              
491              
492             sub and_mask {
493 1     1 1 22 my $class = shift;
494 1         3 my $mask1 = shift;
495 1         3 my $mask2 = shift;
496              
497 1         2 my @mask;
498 1   100 13   15 @mask = pairmap { ($a && $b) // 0 } mesh ( @{$mask1}, @{$mask2} );
  13   100     43  
  1         5  
  1         29  
499              
500 1         41 return $class->new( mask => \@mask );
501             }
502              
503              
504             sub or_mask {
505 1     1 1 8 my $class = shift;
506 1         3 my $mask1 = shift;
507 1         9 my $mask2 = shift;
508              
509 1         4 my @mask;
510 1   100 13   9 @mask = pairmap { ($a || $b) // 0 } mesh ( @{$mask1}, @{$mask2} );
  13   100     39  
  1         9  
  1         12  
511              
512 1         46 return $class->new( mask => \@mask );
513             }
514              
515              
516             sub negative_mask {
517 13     13 1 83 my $self = shift;
518 13         26 my $ali = shift;
519              
520             # negate all states over the whole Ali width
521             # Note the '+ 0' to ensure proper numeric context (0 or 1)
522 13         44 my $width = $ali->width;
523 13         83 my @mask = map { ( not $self->state_at($_) ) + 0 } 0..$width-1;
  1364         37021  
524 13         446 return $self->new( mask => \@mask );
525             }
526              
527              
528             # SeqMask-based Ali factory methods
529              
530              
531             before 'filtered_ali' => sub {
532             ## no critic (ProtectPrivateSubs)
533             return Bio::MUST::Core::Ali::_premask_check( @_[1,0,2] ); # swap args
534             ## use critic
535             };
536              
537             sub filtered_ali {
538             my $self = shift;
539             my $ali = shift;
540             my $list = shift; # optional: enable masking vs. filtering
541              
542             # setup filtering or masking
543             my $char = $list ? 'X' : q{};
544              
545             # create new Ali object (extending header comment)
546             # TODO: allow custom comments
547             my $new_ali = Ali->new(
548             comments => [ $ali->all_comments,
549             'built by ' . ($list ? 'masked_ali' : 'filtered_ali')
550             ],
551             );
552              
553             SEQ:
554             for my $seq ($ali->all_seqs) {
555              
556             # for non-listed Seqs clone Seq to new Ali
557             # Note: when filtering all Seqs will be affected
558             if ( $list and not $list->is_listed($seq->full_id) ) {
559             $new_ali->add_seq($seq->clone);
560             next SEQ;
561             }
562              
563             # for listed Seqs copy marked sites from original Seq...
564             # ... and either filter or mask others (set them to missing state)
565             my $new_seq;
566             my $site = 0;
567             for my $state ($self->all_states) {
568             $new_seq .= $state ? $seq->state_at($site) : $char;
569             $site++;
570             }
571              
572             # add Seq to new Ali
573             $new_ali->add_seq(
574             Seq->new( seq_id => $seq->full_id, seq => $new_seq )
575             );
576             }
577              
578             return $new_ali;
579             }
580              
581              
582             # I/O methods
583              
584              
585             sub load {
586 1     1 1 160 my $class = shift;
587 1         10 my $infile = shift;
588              
589 1         8 open my $in, '<', $infile;
590              
591 1         760 my $mask = $class->new();
592              
593             LINE:
594 1         27 while (my $line = <$in>) {
595 26234         32090 chomp $line;
596              
597             # skip empty lines and process comment lines
598 26234 100 66     108456 next LINE if $line =~ $EMPTY_LINE
599             || $mask->is_comment($line);
600              
601 26232         723066 $mask->add_state($line);
602             }
603              
604 1         28 return $mask;
605             }
606              
607              
608              
609             sub store {
610 4     4 1 16 my $self = shift;
611 4         20 my $outfile = shift;
612              
613 4         45 open my $out, '>', $outfile;
614              
615 4         7095 print {$out} $self->header;
  4         52  
616 4         13 say {$out} join "\n", $self->all_states;
  4         163  
617              
618 4         4726 return;
619             }
620              
621              
622              
623             # sub load_bor {
624             # #~ my $self = shift;
625             # my $class = shift;
626             # my $bor = shift;
627             # my $ali = shift;
628             #
629             # #~ my $class = 'Bio::MUST::Core::SeqMask';
630             #
631             # #~ open my $in, '<', $infile;
632             # my $bor_file = $bor->stringify;
633             # #~ tie my @rows, 'Tie::File', $bor_file;
634             # #~ tie my @rows, 'Tie::File', $in;
635             # #~ my $delims = $rows[-1];
636             # #~ ### $delims
637             # my $delims;
638             # open my $bof, '<', $bor_file;
639             # while ( my $i = <$bof> ) { $delims = $i; }
640             # my @blocks = pairmap { [ $a , $b ] } ( split ' ', $delims );
641             # #~ ### @blocks
642             # my $mask = $class->blocks2mask(\@blocks)->mask;
643             # #~ ### $mask
644             # my $seq_mask = $class->new( mask => $mask);
645             # my $negative = $seq_mask->negative_mask($ali);
646             # return $negative;
647             # }
648              
649              
650              
651              
652              
653              
654             sub store_una {
655 1     1 1 5 my $self = shift;
656 1         4 my $outfile = shift;
657 1   50     4 my $args = shift // {}; # HashRef (should not be empty...)
658              
659             # TODO: check arguments as there are no default values?
660 1         3 my $ali = $args->{ali};
661 1         3 my $id = $args->{id};
662              
663             # search for non-gap positions in reference seq
664 1         6 my $seq = $ali->get_seq_with_id($id);
665 1         45 my @pos_bases = grep { !( $seq->is_gap($_) ) } 0..($seq->seq_len-1);
  99         222  
666              
667             # delete gap positions in mask
668 1         6 my @mask_degap = @{ $self->mask }[ @pos_bases ];
  1         31  
669              
670             # build mask and compute blocks
671 1         32 my $new_mask = $self->new( mask => \@mask_degap );
672 1         5 my $new_blocks = $new_mask->mask2blocks;
673              
674 1         6 open my $out, '>', $outfile;
675              
676 1         331 my $filename = $ali->file;
677 1         34 my ($basename, $dir, $ext) = fileparse($filename, qr{\.[^.]*}xms);
678              
679 1         165 say {$out} "Unambigous numbering for $basename$ext based on $id";
  1         10  
680 1         3 for my $new_block ( @{$new_blocks} ) {
  1         8  
681 3         7 say {$out} join '-', @{$new_block};
  3         5  
  3         10  
682             }
683              
684 1         89 return;
685             }
686              
687              
688             sub load_blocks {
689 2     2 1 281 my $class = shift;
690 2         5 my $infile = shift;
691              
692 2         11 open my $in, '<', $infile;
693              
694 2         692 my $mask = $class->new();
695 2         5 my @blocks;
696              
697             LINE:
698 2         1456 while (my $line = <$in>) {
699 10         27 chomp $line;
700              
701             # skip empty lines and process comment lines
702 10 100 100     117 next LINE if $line =~ $EMPTY_LINE
703             || $mask->is_comment($line);
704              
705             # process block specifications (one or more by line)
706 5     6   75 push @blocks, pairmap { [ $a , $b ] } split /\s+/xms, $line;
  6         52  
707             }
708              
709             # build mask from blocks and replace empty mask
710             # this is needed to preserve potential comments
711 2         26 $mask->_set_mask( $class->blocks2mask( \@blocks )->mask );
712              
713 2         147 return $mask;
714             }
715              
716              
717             sub store_blocks {
718 1     1 1 3 my $self = shift;
719 1         4 my $outfile = shift;
720              
721 1         9 open my $out, '>', $outfile;
722              
723 1         371 say {$out} "# Coordinates of blocks \n#";
  1         327  
724              
725             # compute blocks from mask
726 1         15 my $blocks = $self->mask2blocks;
727              
728 1         6 for my $block ( @{$blocks} ) {
  1         8  
729 6         12 say {$out} join "\t", @{$block};
  6         9  
  6         17  
730             }
731              
732 1         75 return;
733             }
734              
735             __PACKAGE__->meta->make_immutable;
736             1;
737              
738             __END__
739              
740             =pod
741              
742             =head1 NAME
743              
744             Bio::MUST::Core::SeqMask - Sequence mask for selecting specific sites
745              
746             =head1 VERSION
747              
748             version 0.212650
749              
750             =head1 SYNOPSIS
751              
752             # TODO
753              
754             =head1 DESCRIPTION
755              
756             # TODO
757              
758             =head1 METHODS
759              
760             =head2 first_site
761              
762             =head2 last_site
763              
764             =head2 count_sites
765              
766             =head2 coverage
767              
768             =head2 empty_mask
769              
770             =head2 custom_mask
771              
772             =head2 mark_block
773              
774             =head2 unmark_block
775              
776             =head2 mask2blocks
777              
778             =head2 blocks2mask
779              
780             =head2 neutral_mask
781              
782             =head2 variable_mask
783              
784             =head2 parsimony_mask
785              
786             =head2 ideal_mask
787              
788             =head2 gblocks_mask
789              
790             =head2 bmge_mask
791              
792             =head2 and_mask
793              
794             =head2 or_mask
795              
796             =head2 negative_mask
797              
798             =head2 filtered_ali
799              
800             =head2 load
801              
802             =head2 store
803              
804             =head2 load_bor
805              
806             =head2 store_bor
807              
808             =head2 load_una
809              
810             =head2 store_una
811              
812             =head2 load_blocks
813              
814             =head2 store_blocks
815              
816             =head1 AUTHOR
817              
818             Denis BAURAIN <denis.baurain@uliege.be>
819              
820             =head1 CONTRIBUTORS
821              
822             =for stopwords Catherine COLSON Raphael LEONARD
823              
824             =over 4
825              
826             =item *
827              
828             Catherine COLSON <ccolson@doct.uliege.be>
829              
830             =item *
831              
832             Raphael LEONARD <rleonard@doct.uliege.be>
833              
834             =back
835              
836             =head1 COPYRIGHT AND LICENSE
837              
838             This software is copyright (c) 2013 by University of Liege / Unit of Eukaryotic Phylogenomics / Denis BAURAIN.
839              
840             This is free software; you can redistribute it and/or modify it under
841             the same terms as the Perl 5 programming language system itself.
842              
843             =cut