File Coverage

blib/lib/PomBase/Chobo/OntologyData.pm
Criterion Covered Total %
statement 131 143 91.6
branch 27 38 71.0
condition 9 14 64.2
subroutine 17 17 100.0
pod 2 12 16.6
total 186 224 83.0


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.041'; # VERSION
42              
43 5     5   121518 use Mouse;
  5         35191  
  5         38  
44              
45 5     5   5629 use Clone qw(clone);
  5         3444  
  5         399  
46 5     5   2140 use Try::Tiny;
  5         9116  
  5         355  
47 5     5   59 use Carp;
  5         11  
  5         347  
48              
49 5     5   3257 use PomBase::Chobo::OntologyTerm;
  5         25  
  5         11530  
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 13     13 1 28 my $self = shift;
80              
81 13         60 my %args = @_;
82              
83 13         36 my $metadata = $args{metadata};
84 13         29 my $terms = $args{terms};
85              
86 13         88 my $terms_by_id = $self->terms_by_id();
87 13         41 my $terms_by_name = $self->terms_by_name();
88 13         45 my $terms_by_cv_name = $self->terms_by_cv_name();
89 13         42 my $relationship_terms_by_cv_name = $self->relationship_terms_by_cv_name();
90              
91 13         40 my $metadata_by_namespace = $self->metadata_by_namespace();
92              
93 13         120 for my $term (@$terms) {
94 81         257 my @new_term_ids = ($term->{id});
95              
96 81         224 push @new_term_ids, map { $_->{id}; } $term->alt_ids();
  20         93  
97              
98 81         150 my @found_existing_terms = ();
99              
100 81         129 for my $id (@new_term_ids) {
101 101         305 my $existing_term = $terms_by_id->{$id};
102              
103 101 100 100     261 if (defined $existing_term && !$existing_term->{is_obsolete}) {
104 7 100       64 if (!grep { $_ == $existing_term } @found_existing_terms) {
  2         17  
105 5         13 push @found_existing_terms, $existing_term;
106             }
107             }
108             }
109              
110 81 50       175 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 81 100       248 if (@found_existing_terms == 1) {
117 5         10 my $existing_term = $found_existing_terms[0];
118              
119 5 50 33     89 if (!$term->is_obsolete() && !$existing_term->is_obsolete()) {
120 5         20 my $old_namespace = $existing_term->namespace();
121              
122 5         27 $existing_term->merge($term);
123              
124 5 100       35 if ($old_namespace ne $existing_term->namespace()) {
125 3         22 delete $self->terms_by_cv_name()->{$old_namespace}->{$existing_term->name()};
126             }
127              
128 5         17 $term = $existing_term;
129             }
130             }
131             }
132              
133 81         206 for my $id_details ($term->alt_ids(),
134             { id => $term->{id},
135             db_name => $term->{db_name},
136             accession => $term->{accession},
137             } ) {
138 103         295 $terms_by_id->{$id_details->{id}} = $term;
139              
140 103         434 $self->terms_by_db_name()->{$id_details->{db_name}}->{$id_details->{accession}} = $term;
141             }
142              
143 81         264 my $def = $term->def();
144              
145             map {
146 64         111 my $def_dbxref = $_;
147 64 50       282 if ($def_dbxref =~ /^(.+?):(.*)/) {
148 64         195 my ($def_db_name, $def_accession) = ($1, $2);
149 64         240 $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 81         130 } @{$def->{dbxrefs}};
  81         186  
154              
155             map {
156 81         229 my $xref = $_;
  15         116  
157              
158 15 50       89 if ($xref =~ /^(.+?):(.*)/) {
159 15         47 my ($def_db_name, $def_accession) = ($1, $2);
160 15         83 $self->terms_by_db_name()->{$def_db_name}->{$def_accession} = $term;
161             } else {
162 0         0 die qq(can't parse "xref:" line: $xref);
163             }
164             } $term->xrefs();
165              
166 81         160 my $name = $term->{name};
167              
168 81 50       155 if (defined $name) {
169 81 100 66     216 if (!exists $terms_by_name->{$name} ||
170 5         28 !grep { $_ == $term } @{$terms_by_name->{$name}}) {
  5         16  
171 76         205 push @{$terms_by_name->{$name}}, $term;
  76         305  
172             }
173             } else {
174 0         0 warn "term without a name tag ignored:\n", $term->to_string(), "\n\n";
175 0         0 next;
176             }
177              
178 81         217 my $term_namespace = $term->namespace();
179              
180 81 50       208 if (defined $term_namespace) {
181 81         202 my $existing_term_by_name = $terms_by_cv_name->{$term_namespace}->{$name};
182 81 50 66     201 if ($existing_term_by_name && $existing_term_by_name != $term) {
183             warn qq(more than one Term with the name "$name" in namespace "$term_namespace":\n) .
184             " " . $term->id() . "\nand:\n " .
185 0         0 $terms_by_cv_name->{$term_namespace}->{$name}->id() . "\n\n";
186             } else {
187 81         260 $terms_by_cv_name->{$term_namespace}->{$name} = $term;
188             }
189              
190 81 100       383 if ($term->{is_relationshiptype}) {
191 14         31 $relationship_terms_by_cv_name->{$term_namespace}->{$name} = $term;
192             }
193              
194 81 100       190 if (!exists $metadata_by_namespace->{$term_namespace}) {
195 17         248 $metadata_by_namespace->{$term_namespace} = clone $metadata;
196             }
197             }
198              
199 81 50       192 if ($term->{relationship}) {
200 81         109 for my $rel (@{$term->{relationship}}) {
  81         238  
201             my $key = $term->{id} . '<' . $rel->{relationship_name} .
202 65         184 '>' . $rel->{other_term};
203 65         302 $self->_term_relationships()->{$key} = 1;
204             }
205             }
206             }
207             }
208              
209             sub get_terms_by_name
210             {
211 1     1 0 2 my $self = shift;
212 1         3 my $name = shift;
213              
214 1   50     2 return @{$self->terms_by_name()->{$name} // []};
  1         8  
215             }
216              
217             sub get_term_by_id
218             {
219 35     35 0 18175 my $self = shift;
220 35         44 my $id = shift;
221              
222 35         164 return $self->terms_by_id()->{$id};
223             }
224              
225             sub get_cv_names
226             {
227 23     23 0 63 my $self = shift;
228              
229 23         39 return keys %{$self->terms_by_cv_name()};
  23         174  
230             }
231              
232             sub get_terms_by_cv_name
233             {
234 32     32 0 1283 my $self = shift;
235 32         57 my $cv_name = shift;
236              
237 32         41 return values %{$self->terms_by_cv_name()->{$cv_name}};
  32         205  
238             }
239              
240             sub get_db_names
241             {
242 5     5 0 1342 my $self = shift;
243              
244 5         21 return keys %{$self->terms_by_db_name()};
  5         48  
245             }
246              
247             sub accessions_by_db_name
248             {
249 12     12 0 20 my $self = shift;
250 12         22 my $db_name = shift;
251              
252 12         18 return sort keys %{$self->terms_by_db_name()->{$db_name}};
  12         80  
253             }
254              
255             sub get_terms
256             {
257 20     20 0 1071 my $self = shift;
258              
259 20         72 return map { $self->get_terms_by_cv_name($_); } $self->get_cv_names();
  31         82  
260             }
261              
262             sub get_namespaces
263             {
264 3     3 0 4247 my $self = shift;
265              
266 3         6 return keys %{$self->metadata_by_namespace()};
  3         24  
267             }
268              
269             sub get_metadata_by_namespace
270             {
271 4     4 0 9 my $self = shift;
272 4         9 my $namespace = shift;
273              
274 4         31 return $self->metadata_by_namespace()->{$namespace};
275             }
276              
277             sub relationships
278             {
279 6     6 0 2013 my $self = shift;
280              
281 6 100       63 if ($self->{_relationships}) {
282 2         4 return @{$self->{_relationships}}
  2         8  
283             }
284              
285             $self->{_relationships} = [map {
286 32         153 my ($subject_id, $rel_name, $object_id) = /(.*)<(.*)>(.*)/;
287              
288 32         88 my $object_term = $self->get_term_by_id($object_id);
289              
290 32 50       57 if (!$object_term) {
291 0         0 my $subject_term = $self->get_term_by_id($subject_id);
292             warn qq(ignoring relation where object isn't defined: "$object_id" line ) .
293             $subject_term->{source_file_line_number} . ' of ' .
294 0         0 $subject_term->{source_file} . "\n";
295 0         0 ();
296             } else {
297 32         78 [$subject_id, $rel_name, $object_id];
298             }
299 4         12 } sort keys %{$self->_term_relationships()}];
  4         56  
300              
301 4         54 return @{$self->{_relationships}};
  4         28  
302             }
303              
304             =head2 finish
305              
306             Usage : $self->finish();
307             Function: remove namespaces that are empty due to merging and check that
308             objects and subjects of relationships exist
309              
310             =cut
311              
312             sub finish
313             {
314 3     3 1 16 my $self = shift;
315              
316 3         12 my @relationships = $self->relationships();
317              
318 3 50       12 if (@relationships == 0) {
319 0         0 warn "note: no relationships read\n";
320             }
321              
322             # find and remove namespaces that are empty due to merging
323             my @empty_namespaces =
324             map {
325 5 50       9 if (scalar(keys %{$self->terms_by_cv_name()->{$_}}) == 0) {
  5         30  
326 0         0 $_;
327             } else {
328 5         13 ();
329             }
330 3         8 } keys %{$self->terms_by_cv_name()};
  3         53  
331              
332             map {
333 3         14 delete $self->terms_by_cv_name()->{$_};
  0            
334             } @empty_namespaces;
335             }
336              
337             1;