File Coverage

blib/lib/PomBase/Chobo/OntologyData.pm
Criterion Covered Total %
statement 126 137 91.9
branch 26 36 72.2
condition 6 11 54.5
subroutine 17 17 100.0
pod 2 12 16.6
total 177 213 83.1


line stmt bran cond sub pod time code
1             package PomBase::Chobo::OntologyData;
2              
3             =head1 NAME
4              
5             PomBase::Chobo::OntologyData - An in memory representation of an Ontology
6              
7             =head1 SYNOPSIS
8              
9             Objects of this class represent the part of an ontology that can be stored in
10             a Chado database.
11              
12             =head1 AUTHOR
13              
14             Kim Rutherford C<< >>
15              
16             =head1 BUGS
17              
18             Please report any bugs or feature requests to C.
19              
20             =head1 SUPPORT
21              
22             You can find documentation for this module with the perldoc command.
23              
24             perldoc PomBase::Chobo::OntologyData
25              
26             =over 4
27              
28             =back
29              
30             =head1 COPYRIGHT & LICENSE
31              
32             Copyright 2012 Kim Rutherford, all rights reserved.
33              
34             This program is free software; you can redistribute it and/or modify it
35             under the same terms as Perl itself.
36              
37             =head1 FUNCTIONS
38              
39             =cut
40              
41             our $VERSION = '0.038'; # VERSION
42              
43 4     4   78959 use Mouse;
  4         27607  
  4         25  
44              
45 4     4   3272 use Clone qw(clone);
  4         9464  
  4         237  
46 4     4   1654 use Try::Tiny;
  4         6380  
  4         216  
47 4     4   36 use Carp;
  4         11  
  4         195  
48              
49 4     4   1975 use PomBase::Chobo::OntologyTerm;
  4         34  
  4         6742  
50              
51              
52             has terms_by_id => (is => 'rw', init_arg => undef, isa => 'HashRef',
53             default => sub { {} });
54             has terms_by_name => (is => 'rw', init_arg => undef, isa => 'HashRef',
55             default => sub { {} });
56             has terms_by_cv_name => (is => 'rw', init_arg => undef, isa => 'HashRef',
57             default => sub { {} });
58             has relationship_terms_by_cv_name => (is => 'rw', init_arg => undef, isa => 'HashRef',
59             default => sub { {} });
60             has terms_by_db_name => (is => 'rw', init_arg => undef, isa => 'HashRef',
61             default => sub { {} });
62             has metadata_by_namespace => (is => 'rw', init_arg => undef, isa => 'HashRef',
63             default => sub { {} });
64             has _term_relationships => (is => 'rw', init_arg => undef, isa => 'HashRef',
65             default => sub { {} });
66              
67             =head2 add
68              
69             Usage : $ontology_data->add(metadata => {..}, terms => [...]);
70             Function: Add some terms, often all terms from one OBO file
71             Args : metadata - the metadata for the terms
72             terms - an array of OntologyTerm objects
73             Return : Nothing, dies on error
74              
75             =cut
76              
77             sub add
78             {
79 11     11 1 27 my $self = shift;
80              
81 11         39 my %args = @_;
82              
83 11         27 my $metadata = $args{metadata};
84 11         22 my $terms = $args{terms};
85              
86 11         42 my $terms_by_id = $self->terms_by_id();
87 11         27 my $terms_by_name = $self->terms_by_name();
88 11         28 my $terms_by_cv_name = $self->terms_by_cv_name();
89 11         29 my $relationship_terms_by_cv_name = $self->relationship_terms_by_cv_name();
90              
91 11         27 my $metadata_by_namespace = $self->metadata_by_namespace();
92              
93 11         40 for my $term (@$terms) {
94 72         216 my @new_term_ids = ($term->{id});
95              
96 72         189 push @new_term_ids, map { $_->{id}; } $term->alt_ids();
  19         48  
97              
98 72         145 my @found_existing_terms = ();
99              
100 72         116 for my $id (@new_term_ids) {
101 91         156 my $existing_term = $terms_by_id->{$id};
102              
103 91 100       213 if (defined $existing_term) {
104 6 100       27 if (!grep { $_ == $existing_term } @found_existing_terms) {
  2         8  
105 4         9 push @found_existing_terms, $existing_term;
106             }
107             }
108             }
109              
110 72 50       147 if (@found_existing_terms > 1) {
111 0         0 die "two previously read terms match an alt_id field from:\n" .
112             $term->to_string() . "\n\nmatching term 1:\n" .
113             $found_existing_terms[0]->to_string() . "\n\nmatching term 2:\n" .
114             $found_existing_terms[1]->to_string() . "\n";
115             } else {
116 72 100       144 if (@found_existing_terms == 1) {
117 4         8 my $existing_term = $found_existing_terms[0];
118              
119 4 50 33     72 if (!$term->is_obsolete() && !$existing_term->is_obsolete()) {
120 4         16 my $old_namespace = $existing_term->namespace();
121              
122 4         22 $existing_term->merge($term);
123              
124 4 100       29 if ($old_namespace ne $existing_term->namespace()) {
125 2         13 delete $self->terms_by_cv_name()->{$old_namespace}->{$existing_term->name()};
126             }
127              
128 4         10 $term = $existing_term;
129             }
130             }
131             }
132              
133 72         182 for my $id_details ($term->alt_ids(),
134             { id => $term->{id},
135             db_name => $term->{db_name},
136             accession => $term->{accession},
137             } ) {
138 93         287 $terms_by_id->{$id_details->{id}} = $term;
139              
140 93         316 $self->terms_by_db_name()->{$id_details->{db_name}}->{$id_details->{accession}} = $term;
141             }
142              
143 72         217 my $def = $term->def();
144              
145             map {
146 60         86 my $def_dbxref = $_;
147 60 50       240 if ($def_dbxref =~ /^(.+?):(.*)/) {
148 60         158 my ($def_db_name, $def_accession) = ($1, $2);
149 60         231 $self->terms_by_db_name()->{$def_db_name}->{$def_accession} = $term;
150             } else {
151 0         0 die qq(can't parse dbxref from "def:" line: $def_dbxref);
152             }
153 72         110 } @{$def->{dbxrefs}};
  72         139  
154              
155 72         139 my $name = $term->{name};
156              
157 72 50       176 if (defined $name) {
158 72 100 66     198 if (!exists $terms_by_name->{$name} ||
159 4         21 !grep { $_ == $term } @{$terms_by_name->{$name}}) {
  4         18  
160 68         94 push @{$terms_by_name->{$name}}, $term;
  68         219  
161             }
162             } else {
163 0         0 warn "term without a name tag ignored:\n", $term->to_string(), "\n\n";
164 0         0 next;
165             }
166              
167 72         183 my $term_namespace = $term->namespace();
168              
169 72 50       159 if (defined $term_namespace) {
170 72         216 my $existing_term_by_name = $terms_by_cv_name->{$term_namespace}->{$name};
171 72 50 66     167 if ($existing_term_by_name && $existing_term_by_name != $term) {
172             warn qq(more than one Term with the name "$name" in namespace "$term_namespace" -\n) .
173             "existing:\n" . $term->to_string() . "\n\nand:\n" .
174 0         0 $terms_by_cv_name->{$term_namespace}->{$name}->to_string() . "\n\n";
175             } else {
176 72         156 $terms_by_cv_name->{$term_namespace}->{$name} = $term;
177             }
178              
179 72 100       137 if ($term->{is_relationshiptype}) {
180 10         21 $relationship_terms_by_cv_name->{$term_namespace}->{$name} = $term;
181             }
182              
183 72 100       155 if (!exists $metadata_by_namespace->{$term_namespace}) {
184 12         146 $metadata_by_namespace->{$term_namespace} = clone $metadata;
185             }
186             }
187              
188 72 50       164 if ($term->{relationship}) {
189 72         101 for my $rel (@{$term->{relationship}}) {
  72         188  
190             my $key = $term->{id} . '<' . $rel->{relationship_name} .
191 64         191 '>' . $rel->{other_term};
192 64         347 $self->_term_relationships()->{$key} = 1;
193             }
194             }
195             }
196             }
197              
198             sub get_terms_by_name
199             {
200 1     1 0 3 my $self = shift;
201 1         2 my $name = shift;
202              
203 1   50     1 return @{$self->terms_by_name()->{$name} // []};
  1         9  
204             }
205              
206             sub get_term_by_id
207             {
208 35     35 0 16209 my $self = shift;
209 35         50 my $id = shift;
210              
211 35         96 return $self->terms_by_id()->{$id};
212             }
213              
214             sub get_cv_names
215             {
216 22     22 0 44 my $self = shift;
217              
218 22         40 return keys %{$self->terms_by_cv_name()};
  22         110  
219             }
220              
221             sub get_terms_by_cv_name
222             {
223 21     21 0 1395 my $self = shift;
224 21         43 my $cv_name = shift;
225              
226 21         28 return values %{$self->terms_by_cv_name()->{$cv_name}};
  21         159  
227             }
228              
229             sub get_db_names
230             {
231 5     5 0 1413 my $self = shift;
232              
233 5         9 return keys %{$self->terms_by_db_name()};
  5         33  
234             }
235              
236             sub accessions_by_db_name
237             {
238 8     8 0 13 my $self = shift;
239 8         14 my $db_name = shift;
240              
241 8         12 return sort keys %{$self->terms_by_db_name()->{$db_name}};
  8         52  
242             }
243              
244             sub get_terms
245             {
246 19     19 0 783 my $self = shift;
247              
248 19         91 return map { $self->get_terms_by_cv_name($_); } $self->get_cv_names();
  20         56  
249             }
250              
251             sub get_namespaces
252             {
253 3     3 0 3518 my $self = shift;
254              
255 3         10 return keys %{$self->metadata_by_namespace()};
  3         28  
256             }
257              
258             sub get_metadata_by_namespace
259             {
260 3     3 0 9 my $self = shift;
261 3         47 my $namespace = shift;
262              
263 3         23 return $self->metadata_by_namespace()->{$namespace};
264             }
265              
266             sub relationships
267             {
268 6     6 0 1717 my $self = shift;
269              
270 6 100       50 if ($self->{_relationships}) {
271 2         5 return @{$self->{_relationships}}
  2         11  
272             }
273              
274             $self->{_relationships} = [map {
275 32         164 my ($subject_id, $rel_name, $object_id) = /(.*)<(.*)>(.*)/;
276              
277 32         70 my $object_term = $self->get_term_by_id($object_id);
278              
279 32 50       69 if (!$object_term) {
280 0         0 my $subject_term = $self->get_term_by_id($subject_id);
281             warn qq(ignoring relation where object isn't defined: "$object_id" line ) .
282             $subject_term->{source_file_line_number} . ' of ' .
283 0         0 $subject_term->{source_file} . "\n";
284 0         0 ();
285             } else {
286 32         102 [$subject_id, $rel_name, $object_id];
287             }
288 4         15 } sort keys %{$self->_term_relationships()}];
  4         46  
289              
290 4         14 return @{$self->{_relationships}};
  4         20  
291             }
292              
293             =head2 finish
294              
295             Usage : $self->finish();
296             Function: remove namespaces that are empty due to merging and check that
297             objects and subjects of relationships exist
298              
299             =cut
300              
301             sub finish
302             {
303 3     3 1 9 my $self = shift;
304              
305 3         20 my @relationships = $self->relationships();
306              
307 3 50       15 if (@relationships == 0) {
308 0         0 warn "note: no relationships read\n";
309             }
310              
311             # find and remove namespaces that are empty due to merging
312             my @empty_namespaces =
313             map {
314 4 50       8 if (scalar(keys %{$self->terms_by_cv_name()->{$_}}) == 0) {
  4         17  
315 0         0 $_;
316             } else {
317 4         21 ();
318             }
319 3         7 } keys %{$self->terms_by_cv_name()};
  3         12  
320              
321             map {
322 3         12 delete $self->terms_by_cv_name()->{$_};
  0            
323             } @empty_namespaces;
324             }
325              
326             1;