[Bio] / FigKernelPackages / FIG.pm Repository:
ViewVC logotype

Diff of /FigKernelPackages/FIG.pm

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 1.463, Fri Apr 28 19:33:38 2006 UTC revision 1.464, Tue May 2 16:09:44 2006 UTC
# Line 6669  Line 6669 
6669    
6670  ############################  Similarities ###############################  ############################  Similarities ###############################
6671    
6672    
6673    =head3 nsims
6674    
6675    New sims code.
6676    
6677    This code takes advantage of a network similarity server if it is available.
6678    
6679    We gather sims in the following manner:
6680    
6681        If a local sims directory exists, gather the raw sims for our peg.
6682        If dynamic sims are available, gather the raw sims from there as well.
6683    
6684        Do an initial pruning of these raw sims, based on the conditions
6685        passed in to the sims call.
6686    
6687        Locally expand these sims.
6688    
6689        If we are using network sims, retrieve them now, and add to the local sims set.
6690    
6691        Do a final pruning of this set of sims, and sort.
6692    
6693    =cut
6694    
6695    sub nsims
6696    {
6697        my ( $self, $id, $maxN, $maxP, $select, $max_expand, $filters ) = @_;
6698    
6699        my $filter_func =  $self->create_sim_filter($maxP, $filters);
6700    
6701        $max_expand = defined( $max_expand ) ? $max_expand : 10000;
6702        return () if $self->is_deleted_fid( $id );
6703    
6704        my @raw_sims;
6705    
6706        #@raw_sims = $self->get_local_sims($id, $filter_func);
6707    
6708    
6709        my %seen = map { $_->[1] => 1 } @raw_sims;
6710    
6711        my @exp_sims;
6712        if ($select eq 'raw')
6713        {
6714            @exp_sims = @raw_sims;
6715        }
6716        else
6717        {
6718            @exp_sims = $self->expand_local_sims(\@raw_sims, \%seen, $select, $filters);
6719        }
6720    
6721        #
6722        # Retrieve network sims if we don't have a sims directory.
6723        #
6724    
6725        my $want_net_sims = ! -e "$FIG_Config::data/Sims";
6726        $want_net_sims = 1;
6727    
6728        if ($want_net_sims)
6729        {
6730            my @net_sims = $self->get_network_sims($id, \%seen, $maxN, $maxP, $select, $max_expand, $filters);
6731            push(@exp_sims, @net_sims);
6732        }
6733    
6734        #
6735        # Do a final filtering for dups.
6736        #
6737    
6738    
6739        #
6740        # And sort.
6741        #
6742    
6743        my @sims = $self->sort_sims(\@exp_sims, $filters);
6744    
6745    #    print STDERR "Returning for $id: ", Dumper(\@sims);
6746    
6747        return @sims;
6748    }
6749    
6750    #
6751    # Create a sim filter-function from the parameters passed.
6752    # Returns true if the sim passed as an argument meets all the requirements.
6753    #
6754    sub create_sim_filter
6755    {
6756        my($self, $maxP, $filters) = @_;
6757    
6758        my $txt = "sub { \$_ = shift;\n";
6759    
6760        my ( $show_env, $min_sim, $sim_meas, $min_q_cov, $min_s_cov, $sort_by );
6761        if ( $filters && ref( $filters ) eq "HASH" )
6762        {
6763            defined( $filters->{ maxP }      ) and $maxP      = $filters->{ maxP };
6764            defined( $filters->{ show_env }  ) and $show_env  = $filters->{ show_env };
6765            defined( $filters->{ min_sim }   ) and $min_sim   = $filters->{ min_sim };
6766            defined( $filters->{ sim_meas }  ) and $sim_meas  = $filters->{ sim_meas };
6767            defined( $filters->{ min_q_cov } ) and $min_q_cov = $filters->{ min_q_cov };
6768            defined( $filters->{ min_s_cov } ) and $min_s_cov = $filters->{ min_s_cov };
6769            defined( $filters->{ sort_by }   ) and $sort_by   = $filters->{ sort_by };
6770        }
6771        defined( $maxP )      or $maxP       =    10;
6772        defined( $show_env )  or $show_env   =     1;
6773        defined( $min_sim )   or $min_sim    =     0;
6774        defined( $sim_meas )  or $sim_meas   =   'id';
6775        defined( $min_q_cov ) or $min_q_cov  =     0;
6776        defined( $min_s_cov ) or $min_s_cov  =     0;
6777        defined( $sort_by )   or $sort_by = 'bits';
6778    
6779        #
6780        # Initial filter
6781        #
6782    
6783        $txt .= "return unless \$_->[10] <= $maxP;\n";
6784    
6785        if ($min_sim > 0)
6786        {
6787            if ($sim_meas eq 'id')
6788            {
6789                $txt .= "return unless \$_->[2] >= $min_sim;\n";
6790            }
6791            elsif ($sim_meas eq 'bpp')
6792            {
6793                $txt .= "return unless \$_->[2] >= $min_sim * ( \$_->[7] - \$_->[6] + 1); \n";
6794            }
6795        }
6796        #  Query coverage filter
6797    
6798        if ( $min_q_cov > 0 )
6799        {
6800            my $thresh = 0.01 * $min_q_cov;
6801            $txt .= "return unless ( abs( \$_->[7] - \$_->[6] ) + 1 ) >= ( $thresh * \$_->[12] ); \n";
6802        }
6803    
6804        #  Subject coverage filter
6805    
6806        if ( $min_s_cov > 0 )
6807        {
6808            my $thresh = 0.01 * $min_s_cov;
6809            $txt .= "return unless ( abs( \$_->[9] - \$_->[8] ) + 1 ) >= ( $thresh * \$_->[13] ); \n";
6810        }
6811    
6812        $txt .= " return 1; }\n";
6813    
6814        print STDERR "Filter text: $txt\n";
6815    
6816        my $initial_filter = eval $txt;
6817    
6818        return $initial_filter;
6819    }
6820    
6821  =head3 sims  =head3 sims
6822    
6823  usage: @sims = $fig->sims($peg,$maxN,$maxP,$select,$max_expand, $filters)  usage: @sims = $fig->sims($peg,$maxN,$maxP,$select,$max_expand, $filters)
# Line 6710  Line 6859 
6859    
6860  =cut  =cut
6861    
6862  sub sims {  sub osims {
6863      my ( $self, $id, $maxN, $maxP, $select, $max_expand, $filters ) = @_;      my ( $self, $id, $maxN, $maxP, $select, $max_expand, $filters ) = @_;
6864      my( $sim );      my( $sim );
6865    
# Line 6790  Line 6939 
6939      return expand_raw_sims( $self, \@raw_sims, $maxN, $maxP, $select, 1, $max_expand, $filters );      return expand_raw_sims( $self, \@raw_sims, $maxN, $maxP, $select, 1, $max_expand, $filters );
6940  }  }
6941    
6942    #
6943    # Choose the new sims code.
6944    #
6945    *sims = \&nsims;
6946    
6947    #
6948    # Choose the old sims code.
6949    #
6950    #*sims = \&osims;
6951    
6952    sub get_local_sims {
6953        my ($self, $id, $filter_func) = @_;
6954        my( $sim );
6955    
6956        return () if $self->is_deleted_fid( $id );
6957    
6958        #
6959        # Retrieve the list of synonyms for this peg. The first in the list
6960        # is the principal synonym.
6961        #
6962        my @maps_to = $self->mapped_prot_ids( $id );
6963        ( @maps_to > 0 ) or return ();
6964    
6965        my $rep_id = $maps_to[0]->[0];
6966        if ( ! defined( $maps_to[0]->[1] ) )
6967        {
6968            print STDERR &Dumper( \@maps_to );
6969            confess "bad";
6970        }
6971    
6972        #
6973        # Find my entry in the list.
6974        #
6975        my @entry = grep { $_->[0] eq $id } @maps_to;
6976        ( @entry == 1 ) and defined( $entry[0]->[1] ) or return ();
6977    
6978        #
6979        #  Get the similarities. They are based on the principal synonym.
6980        #
6981    
6982        my @raw_sims = get_raw_sims_new( $self, $rep_id, $filter_func);
6983    
6984        #  If the query is not the representative, make sims look like it is
6985        #  by replacing id1 and fixing match coordinates if lengths differ.
6986    
6987        my $delta = $maps_to[0]->[1] - $entry[0]->[1];
6988        if ( $id ne $rep_id )
6989        {
6990            foreach $sim ( @raw_sims )
6991            {
6992                $sim->[0]  = $id;
6993                $sim->[6] -= $delta;
6994                $sim->[7] -= $delta;
6995            }
6996        }
6997    
6998        #  The query must be present for expanding matches to identical sequences.
6999    
7000        unshift( @raw_sims, bless( [ $id,
7001                                    $rep_id,
7002                                    "100.00",
7003                                    $entry[0]->[1],
7004                                    0,
7005                                    0,
7006                                    1,        $entry[0]->[1],
7007                                    $delta+1, $maps_to[0]->[1],
7008                                    0.0,
7009                                    2 * $entry[0]->[1],
7010                                    $entry[0]->[1],
7011                                    $maps_to[0]->[1],
7012                                    "blastp"
7013                                    ], 'Sim'
7014                                 )
7015               );
7016    
7017        return @raw_sims;
7018    }
7019    
7020    sub get_network_sims
7021    {
7022        my($self, $id, $seen, $maxN, $maxP, $select, $max_expand, $filters) = @_;
7023    
7024        #
7025        # Construct the URL arguments for submitting to the sims server.
7026    
7027  #  maxP is not used.  It is checked by both functions that call here.      my(@ids, @args);
7028    
7029        my $url = "http://xenophilus.nmpdr.org/ss/sims.pl";
7030    
7031        if (ref($id) eq "ARRAY")
7032        {
7033            @ids = @$id;
7034        }
7035        else
7036        {
7037            @ids = ($id);
7038        }
7039    
7040        push(@args, map { "id=$_" } @ids);
7041        push(@args, "maxN=$maxN") if $maxN;
7042        push(@args, "maxP=$maxP") if $maxP;
7043        push(@args, "select=$select") if $select;
7044        push(@args, "max_expand=$max_expand") if $max_expand;
7045        if (ref($filters) eq 'HASH')
7046        {
7047            for my $k (keys(%$filters))
7048            {
7049                push(@args, "filter_$k=$filters->{$k}");
7050            }
7051        }
7052    
7053        my $args = join("&", @args);
7054    
7055        my $ua = LWP::UserAgent->new();
7056        my $req = "$url?$args";
7057        print STDERR "Request $req\n";
7058    
7059        my @sims;
7060        my $tail;
7061    
7062        #
7063        # Incrementally parse & create Sim objects for the sims
7064        # as they come in from the server.
7065        #
7066        my $cb = sub {
7067    #       eval {
7068                my $c = $tail . shift;
7069    
7070                #   print "GOT <$c>\n";
7071                while ($c =~ s/^([^\n]*)\n//gs)
7072                {
7073                    my @s = split(/\t/, $1);
7074                    @s > 8 or next;
7075                    my $id2 = $s[1];
7076                    next if ($self->is_deleted_fid($id2));
7077                    if ($seen->{$id2})
7078                    {
7079                        next;
7080                    }
7081                    else
7082                    {
7083                        $seen->{$id2} = 1;
7084                    }
7085    
7086                    #           print STDERR "netsim @s\n";
7087    #               $s[14] =~ s/blastp/blastp_net/;
7088                    push(@sims, bless \@s, 'Sim');
7089                }
7090                $tail = $c;
7091    #           print "TAIL <$tail>\n";
7092    #       }; $@ and print STDERR "Failed with $@";
7093        };
7094    
7095        my $resp = $ua->get($req, ':content_cb' => $cb);
7096    #    print "tail=$tail\n";
7097    #    print "sims=@sims\n";
7098        if ($resp->is_success)
7099        {
7100            my $x = $resp->content;
7101            return @sims;
7102        }
7103        else
7104        {
7105            print STDERR "Error getting sims: \n";
7106            print STDERR $resp->content;
7107        }
7108    }
7109    
7110    sub sort_sims
7111    {
7112        my($self, $sims, $filters) = @_;
7113        my @sorted;
7114    
7115        my $sort_by;
7116        if ( $filters && ref( $filters ) eq "HASH" )
7117        {
7118            defined( $filters->{ sort_by }   ) and $sort_by   = $filters->{ sort_by };
7119        }
7120        defined( $sort_by )   or $sort_by = 'bits';
7121    
7122        if    ( $sort_by eq 'id' )                        # Percent identity
7123        {
7124            @sorted = sort { $b->[2] <=> $a->[2] } @$sims;
7125        }
7126    
7127        elsif ( $sort_by eq 'id2' )                       # Percent identity adjusted
7128        {
7129            #  Lower percent identity by 2 standard deviations to prevent random
7130            #  fluctuation in short sequences from moving them up so often.
7131    
7132            my ( $p, $len, $sigma );
7133            @sorted = map  { $_->[0] }
7134                     sort { $b->[1] <=> $a->[1] }
7135                     map  { $p = 0.01 * $_->[2];                 # fraction identity
7136                            $len = abs( $_->[7] - $_->[6] ) + 1; # seq len
7137                            $sigma = sqrt( $p * ( 1 - $p ) / $len ); # binomial sigma
7138                            [ $_, $_->[2] - 200 * $sigma ]
7139                          }
7140                     @$sims;
7141        }
7142    
7143        elsif ( $sort_by eq 'bpp' )                       # Bits per position
7144        {
7145            @sorted = map  { $_->[0] }
7146                     sort { $b->[1] <=> $a->[1] }
7147                     map  { [ $_, $_->[11] / abs( $_->[7] - $_->[6] ) ] }
7148                     @$sims;
7149        }
7150    
7151        elsif ( $sort_by eq 'bpp2' )                      # Bits per position adjusted
7152        {
7153            #  Lower score by 2 standard deviations to prevent random
7154            #  fluctuation in short sequences from moving them up so often.
7155    
7156            my ( $bpp, $len, $sigma );
7157            @sorted = map  { $_->[0] }
7158                     sort { $b->[1] <=> $a->[1] }
7159                     map  { $len = abs( $_->[7] - $_->[6] ) + 1; # seq len
7160                            $bpp = $_->[11] / $len;              # bit per pos
7161                            $sigma = 2.5 * sqrt( 1 / $len );  # simple estimate
7162                            [ $_, $bpp - 2 * $sigma ]
7163                          }
7164                     @$sims;
7165        }
7166    
7167        else                                              # Bit score (bits)
7168        {
7169            @sorted = sort { $b->[11] <=> $a->[11] } @$sims;
7170        }
7171    
7172        return @sorted;
7173    }
7174    
7175    sub expand_local_sims {
7176        my( $self, $raw_sims, $seen, $select, $filters) = @_;
7177        my( $sim, $id1, $id2, %others, $x );
7178    
7179        my $show_env;
7180        if ( $filters && ref( $filters ) eq "HASH" )
7181        {
7182            defined( $filters->{ show_env }   ) and $show_env   = $filters->{ show_env };
7183        }
7184        defined( $show_env )   or $show_env   =       1;   # Show environmental by default
7185    
7186        my @sims = ();
7187        foreach $sim ( @$raw_sims )
7188        {
7189            $id2 = $sim->id2;
7190            $id1 = $sim->id1;
7191    
7192            next if ( $id1 eq $id2 ) || $self->is_deleted_fid( $id2 );
7193    
7194            my @relevant = ();
7195    
7196            #
7197            # If we are expanding, determine the set of proteins that
7198            # are equivalent to the protein that we are similar to.
7199            #
7200            # Depending on the options passed in, we filter the
7201            # equivalent proteins found.
7202            #
7203    
7204            my @maps_to = $self->mapped_prot_ids( $id2 );
7205            my $ref_len = $maps_to[0]->[1];
7206    
7207            @maps_to = grep { $_->[0] !~ /^xxx\d+/ } @maps_to;
7208    
7209            if ( $select =~ /^figx?$/ )          # Only fig
7210            {
7211                @relevant = grep { $_->[0] =~ /^fig/ } @maps_to;
7212            }
7213            elsif ( $select =~ /^figx?_?pref/ )  # FIG preferred
7214            {
7215                @relevant = grep { $_->[0] =~ /^fig/ } @maps_to;
7216                #
7217                # If this id doesn't map to any fig ids, and id2 isn't an xxx id,
7218                # go ahead and include this sim (and don't bother expanding).
7219                #
7220                if ( ! @relevant and $id2 !~ /^xxx\d+$/)
7221                {
7222                    if (not $seen->{$id2})
7223                    {
7224                        push @sims, $sim;
7225                        $seen->{$id2}++;
7226                    }
7227                    next;
7228                }
7229            }
7230            elsif ( $select =~ /^ext/i )         # Not fig
7231            {
7232                @relevant = grep { $_->[0] !~ /^fig/ } @maps_to;
7233            }
7234            else                                 # All
7235            {
7236                @relevant = @maps_to;
7237            }
7238    
7239            #
7240            # Include the relevant sims.
7241            #
7242    
7243            foreach $x ( @relevant )
7244            {
7245                my ( $x_id, $x_ln ) = @$x;
7246    
7247                next if $seen->{$x_id};
7248    
7249                $seen->{$x_id} = 1;
7250    
7251                defined( $x_ln ) || confess "x_ln id2='$id2' x_id='$x_id'";
7252                next if ( ! $show_env && ( $x_id =~ /^fig\|9999999/ ) );
7253                next if ( $id1 eq $x_id ) || $self->is_deleted_fid( $x_id );
7254    
7255                defined( $ref_len ) || confess "maps_to";
7256                my $delta2  = $ref_len - $x_ln;   # Coordinate shift
7257                my $sim1    = [ @$sim ];                  # Make a copy
7258                $sim1->[1]  = $x_id;
7259                $sim1->[8] -= $delta2;
7260                $sim1->[9] -= $delta2;
7261                bless( $sim1, "Sim" );
7262                push( @sims, $sim1 );
7263            }
7264        }
7265    
7266        return @sims;
7267    }
7268    
7269  sub expand_raw_sims {  sub expand_raw_sims {
7270      my( $self, $raw_sims, $maxN, $maxP, $select, $dups, $max_expand, $filters ) = @_;      my( $self, $raw_sims, $maxN, $maxP, $select, $dups, $max_expand, $filters ) = @_;
# Line 6846  Line 7319 
7319              # equivalent proteins found.              # equivalent proteins found.
7320              #              #
7321    
   
7322              my @maps_to = grep { $_->[0] !~ /^xxx\d+/ } $self->mapped_prot_ids( $id2 );              my @maps_to = grep { $_->[0] !~ /^xxx\d+/ } $self->mapped_prot_ids( $id2 );
7323              if ( $select =~ /^figx?$/ )          # Only fig              if ( $select =~ /^figx?$/ )          # Only fig
7324              {              {
# Line 6855  Line 7327 
7327              elsif ( $select =~ /^figx?_?pref/ )  # FIG preferred              elsif ( $select =~ /^figx?_?pref/ )  # FIG preferred
7328              {              {
7329                  @relevant = grep { $_->[0] =~ /^fig/ } @maps_to;                  @relevant = grep { $_->[0] =~ /^fig/ } @maps_to;
7330                    #
7331                    # If this id doesn't map to any fig ids, and id2 isn't an xxx id,
7332                    # go ahead and include this sim.
7333                    #
7334                  if ( ! @relevant and $id2 !~ /^xxx\d+$/)                  if ( ! @relevant and $id2 !~ /^xxx\d+$/)
7335                  {                  {
7336                      push @sims, $sim;                      push @sims, $sim;
# Line 6899  Line 7375 
7375  }  }
7376    
7377    
7378    sub get_raw_sims_new {
7379        my ( $self, $rep_id, $filter_func) = @_;
7380        my ( $sim_chunk, $seek, $fileN, $ln, $fh, $file, @lines, $sim );
7381    
7382    
7383        my $rdbH = $self->db_handle;
7384        my $relational_db_response = $rdbH->SQL("SELECT seek, fileN, len FROM sim_seeks WHERE id = \'$rep_id\' ");
7385    
7386        #  Gather all of the acceptable "lines" from the sim chunks
7387    
7388        foreach $sim_chunk ( @$relational_db_response )
7389        {
7390            ( $seek, $fileN, $ln ) = @$sim_chunk;
7391            $file = $self->N2file( $fileN );
7392            $fh   = $self->openF( $file );
7393            $fh or confess "could not open sims for $file";
7394    
7395            #  Read file, parse lines, sanity check values, and filter E-value
7396            #   0.  The query peg
7397            #   1.  The similar peg
7398            #   2.  The percent id
7399            #   3.  Alignment length
7400            #   4.  Mismatches
7401            #   5.  Gap openings
7402            #   6.  The start of the match in the query peg
7403            #   7.  The end of the match in the query peg
7404            #   8.  The start of the match in the similar peg
7405            #   9.  The end of the match in the similar peg
7406            #  10.  E-value
7407            #  11.  Bit score
7408            #  12.  Length of query peg
7409            #  13.  Length of similar peg
7410            #  14.  Method
7411    
7412            push @lines, grep { ( @$_ >= 15 ) &&
7413                                ( $_->[10] =~ /^[0-9.e-]+$/ ) &&  # E-value
7414                                ( $_->[11] =~ /^[0-9.]+$/ ) &&    # bit score
7415                                ( $_->[12] =~ /^\d+$/ ) &&        # query len
7416                                ( $_->[13] =~ /^\d+$/ ) &&        # subj len
7417                                ( $_->[6]  =~ /^\d+$/ ) &&        # q-match start
7418                                ( $_->[7]  =~ /^\d+$/ ) &&        # q-match end
7419                                ( $_->[8]  =~ /^\d+$/ ) &&        # s-match start
7420                                ( $_->[9]  =~ /^\d+$/ ) &&        # s-match end
7421                                ( $_->[2]  =~ /^[0-9.]+$/ ) &&    # percent id
7422                                ( &$filter_func($_) )             # compiled sim filter
7423                              }
7424                         map  { [ split( /\t/, $_ ), "blastp" ] }
7425                         @{ read_block( $fh, $seek, $ln-1 ) };
7426        }
7427    
7428        push(@lines,     grep { ( @$_ >= 15 ) &&
7429                                ( $_->[10] =~ /^[0-9.e-]+$/ ) &&  # E-value
7430                                ( $_->[11] =~ /^[0-9.]+$/ ) &&    # bit score
7431                                ( $_->[12] =~ /^\d+$/ ) &&        # query len
7432                                ( $_->[13] =~ /^\d+$/ ) &&        # subj len
7433                                ( $_->[6]  =~ /^\d+$/ ) &&        # q-match start
7434                                ( $_->[7]  =~ /^\d+$/ ) &&        # q-match end
7435                                ( $_->[8]  =~ /^\d+$/ ) &&        # s-match start
7436                                ( $_->[9]  =~ /^\d+$/ ) &&        # s-match end
7437                                ( $_->[2]  =~ /^[0-9.]+$/ ) &&    # percent id
7438                                ( &$filter_func($_) )             # compiled sim filter
7439                              }
7440                         &get_dynamic_sims($self,$rep_id));
7441    
7442    
7443    
7444        #  Bless the raw sims:
7445    
7446        return map { bless( $_, 'Sim' ); $_ } @lines;
7447    }
7448    
7449  sub get_raw_sims {  sub get_raw_sims {
7450      my ( $self, $rep_id, $maxP, $filters ) = @_;      my ( $self, $rep_id, $maxP, $filters ) = @_;
7451      my ( $sim_chunk, $seek, $fileN, $ln, $fh, $file, @lines, $sim );      my ( $sim_chunk, $seek, $fileN, $ln, $fh, $file, @lines, $sim );
# Line 7082  Line 7629 
7629      return map { bless( $_, 'Sim' ); $_ } @lines;      return map { bless( $_, 'Sim' ); $_ } @lines;
7630  }  }
7631    
7632    
7633  sub get_dynamic_sims {  sub get_dynamic_sims {
7634      my($self,$prot_id) = @_;      my($self,$prot_id) = @_;
7635      my $tuples;      my $tuples;
# Line 7949  Line 8497 
8497      {      {
8498          confess "could not open annotations for $file";          confess "could not open annotations for $file";
8499      }      }
8500    
8501        #
8502        # See if the seek address is after the end of the file. If it is,
8503        # we're likely looking at an annotation file that is older than the
8504        # database entry. This can come from instantaneous database replication
8505        # with file replication happening at a slower rate.
8506        #
8507    
8508        if ($seek > -s $fh)
8509        {
8510            warn "Attempting to seek past the end of $file\n";
8511            return "";
8512        }
8513    
8514      seek($fh,$seek,0);      seek($fh,$seek,0);
8515      $readN = read($fh,$readC,$ln);      $readN = read($fh,$readC,$ln);
8516      my $len2 = length $readC;      my $len2 = length $readC;

Legend:
Removed from v.1.463  
changed lines
  Added in v.1.464

MCS Webmaster
ViewVC Help
Powered by ViewVC 1.0.3