File Coverage

lib/App/Sandy/DB/Handle/Quality.pm
Criterion Covered Total %
statement 18 170 10.5
branch 0 50 0.0
condition 0 18 0.0
subroutine 6 16 37.5
pod 0 5 0.0
total 24 259 9.2


line stmt bran cond sub pod time code
1             package App::Sandy::DB::Handle::Quality;
2             # ABSTRACT: Class to handle quality database schemas.
3              
4 6     6   47 use App::Sandy::Base 'class';
  6         12  
  6         36  
5 6     6   2768 use App::Sandy::DB;
  6         1352  
  6         290  
6 6     6   3420 use IO::Compress::Gzip 'gzip';
  6         160778  
  6         474  
7 6     6   2845 use IO::Uncompress::Gunzip 'gunzip';
  6         73917  
  6         437  
8 6     6   52 use Storable qw/nfreeze thaw/;
  6         17  
  6         446  
9              
10             use constant {
11 6         15465 PARTIL => 10,
12             DEFAULT_PROFILE => {
13             poisson => {
14             'mean' => '-m',
15             'stdd' => '-d',
16             'error' => '-e',
17             'type' => 'both',
18             'source' => 'default',
19             'provider' => 'vendor',
20             'date' => '2018-08-08'
21             }
22             }
23 6     6   42 };
  6         23  
24              
25             with qw{
26             App::Sandy::Role::IO
27             App::Sandy::Role::Statistic
28             App::Sandy::Role::Counter
29             };
30              
31             our $VERSION = '0.22'; # VERSION
32              
33             sub insertdb {
34 0     0 0   my ($self, $file, $name, $source, $is_user_provided, $error, $single_molecule, $type) = @_;
35 0           my $schema = App::Sandy::DB->schema;
36 0           my %default_profile = %{ &DEFAULT_PROFILE };
  0            
37              
38 0           log_msg ":: Checking if there is already a quality-profile '$name' ...";
39 0           my $rs = $schema->resultset('QualityProfile')->find({ name => $name });
40 0 0 0       if ($rs || $default_profile{$name}) {
41 0           die "There is already a quality-profile '$name'\n";
42             } else {
43 0           log_msg ":: quality-profile '$name' not found";
44             }
45              
46 0 0         if ($type !~ /^(fastq|raw)$/) {
47 0           die "Unknown indexing type '$type': Valids are 'raw' and 'fastq'\n";
48             }
49              
50 0           log_msg ":: Indexing '$file'. It may take several minutes ...";
51 0           my ($arr, $mean, $stdd, $deepth) = $self->_index_quality_type($file, $type);
52              
53 0           log_msg ":: Converting array to bytes ...";
54 0           my $bytes = nfreeze $arr;
55 0           log_msg ":: Compressing bytes ...";
56 0           gzip \$bytes => \my $compressed;
57              
58             # Begin transation
59 0           my $guard = $schema->txn_scope_guard;
60              
61 0           log_msg ":: Storing quality matrix entry at '$name' ...";
62 0           $rs = $schema->resultset('QualityProfile')->create({
63             'name' => $name,
64             'source' => $source,
65             'is_user_provided' => $is_user_provided,
66             'is_single_molecule' => $single_molecule,
67             'mean' => $mean,
68             'stdd' => $stdd,
69             'error' => $error,
70             'deepth' => $deepth,
71             'partil' => PARTIL,
72             'matrix' => $compressed
73             });
74              
75             # End transation
76 0           $guard->commit;
77             }
78              
79             sub _index_quality {
80 0     0     my ($self, $quality_ref) = @_;
81 0           my (@partil, @sizes);
82              
83 0           for my $entry (@$quality_ref) {
84 0           my @q = split // => $entry;
85              
86 0           my $size = scalar @q;
87 0           push @sizes => $size;
88              
89 0           my $bin = int($size / PARTIL);
90 0           my $left = $size % PARTIL;
91              
92 0           my $pos = 0;
93 0           my $skip = $self->with_make_counter($size - $left, $left);
94              
95 0           for (my $i = 0; $i < PARTIL; $i++) {
96 0           for (my $j = 0; $j < $bin; $j++) {
97 0 0         $pos++ if $skip->();
98 0           push @{ $partil[$i] } => $q[$pos++];
  0            
99             }
100             }
101             }
102              
103 0           my $deepth = scalar @{ $partil[0] };
  0            
104 0           for (my $i = 1; $i < PARTIL; $i++) {
105 0 0         if ($deepth != scalar(@{ $partil[$i] })) {
  0            
106 0           croak "deepth differs at partil $i";
107             }
108             }
109              
110             # Basic stats
111 0           my $mean = int($self->with_mean(\@sizes) + 0.5);
112 0           my $stdd = int($self->with_stdd(\@sizes) + 0.5);
113              
114 0           return (\@partil, $mean, $stdd, $deepth);
115             }
116              
117             sub _index_quality_type {
118 0     0     my ($self, $file, $type) = @_;
119 0           my $fh = $self->with_open_r($file);
120              
121 0           log_msg ":: Counting number of lines in '$file' ...";
122 0           my $num_lines = $self->_wcl($file);
123              
124 0 0         if ($num_lines == 0) {
125 0           die "The file '$file' is empty\n";
126             }
127              
128 0           log_msg ":: Number of lines: $num_lines";
129              
130 0           my $num_left = 0;
131 0           my $line = 0;
132 0           my $acm = 0;
133              
134 0           my $getter;
135              
136 0 0         if ($type eq 'fastq') {
137 0           log_msg ":: Setting fastq validation and getter";
138 0           $num_left = int($num_lines / 4);
139              
140             $getter = sub {
141 0 0   0     return if eof($fh);
142              
143 0           my @stack;
144              
145 0           for (1..4) {
146 0           $line++;
147 0 0         defined(my $entry = <$fh>)
148             or die "Truncated fastq entry in '$file' at line $line\n";
149 0           push @stack => $entry;
150             }
151              
152 0           chomp @stack;
153              
154 0 0 0       if ($stack[0] !~ /^\@/ || $stack[2] !~ /^\+/) {
155 0           die "Fastq entry at '$file' line '", $line - 3, "' not seems to be a valid read\n";
156             }
157              
158 0           return $stack[3];
159 0           };
160             } else {
161 0           log_msg ":: Setting raw validation and getter";
162 0           $num_left = $num_lines;
163              
164             $getter = sub {
165 0 0   0     defined(my $entry = <$fh>)
166             or return;
167              
168 0           $line++;
169 0           chomp $entry;
170              
171 0           return $entry;
172 0           };
173             }
174              
175 0           log_msg ":: Calculating the number of entries to pick ...";
176 0 0         my $picks = $num_left < 1000 ? $num_left : 1000;
177 0           my $picks_left = $picks;
178              
179 0           my @quality;
180 0           my $do_pick = $self->with_make_counter($num_left, $picks);
181              
182 0           log_msg ":: Picking $picks entries in '$file' ...";
183 0           while (my $entry = $getter->()) {
184 0 0         if ($do_pick->()) {
185 0 0         push @quality => $entry if length($entry) >= PARTIL;
186 0 0 0       if ($picks >= 10 && (++$acm % int($picks/10) == 0)) {
187 0           log_msg sprintf " ==> %d%% processed", ($acm / $picks) * 100;
188             }
189 0 0         last if --$picks_left <= 0;
190             }
191             }
192              
193 0 0         if (scalar(@quality) == 0) {
194 0           die "All quality entries have length lesser then 10 in file '$file'.\n" .
195             "quality-profile constraints the quality length to values greater or equal to 10.\n" .
196             "If you need somehow profiles with length lesser than 10. Please use --quality-profile=poisson\n";
197             }
198              
199             $fh->close
200 0 0         or die "Cannot close file '$file'\n";
201              
202 0           return $self->_index_quality(\@quality);
203             }
204              
205             sub _wcl {
206 0     0     my ($self, $file) = @_;
207 0           my $fh = $self->with_open_r($file);
208 0           my $num_lines = 0;
209 0           $num_lines++ while <$fh>;
210 0           $fh->close;
211 0           return $num_lines;
212             }
213              
214             sub retrievedb {
215 0     0 0   my ($self, $quality_profile) = @_;
216 0           my $schema = App::Sandy::DB->schema;
217 0           my %default_profile = %{ &DEFAULT_PROFILE };
  0            
218              
219 0 0         if ($default_profile{$quality_profile}) {
220 0           die "Cannot retrieve '$quality_profile' because it is based on a theoretical distribution\n";
221             }
222              
223 0           my $rs = $schema->resultset('QualityProfile')->find({ name => $quality_profile });
224 0 0         die "'$quality_profile' not found into database\n" unless defined $rs;
225              
226 0           my $compressed = $rs->matrix;
227 0 0         die "quality-profile entry '$quality_profile' exists, but the related data is missing\n"
228             unless defined $compressed;
229              
230 0           my $deepth = $rs->deepth;
231 0           my $partil = $rs->partil;
232              
233 0           gunzip \$compressed => \my $bytes;
234 0           my $matrix = thaw $bytes;
235 0           return ($matrix, $deepth, $partil);
236             }
237              
238             sub deletedb {
239 0     0 0   my ($self, $quality_profile) = @_;
240 0           my $schema = App::Sandy::DB->schema;
241 0           my %default_profile = %{ &DEFAULT_PROFILE };
  0            
242              
243 0           log_msg ":: Checking if there is a quality-profile '$quality_profile' ...";
244 0           my $rs = $schema->resultset('QualityProfile')->find({ name => $quality_profile });
245              
246 0 0 0       unless ($rs || $default_profile{$quality_profile}) {
247 0           die "'$quality_profile' not found into database\n";
248             }
249              
250 0           log_msg ":: Found '$quality_profile'";
251              
252 0 0 0       if (($rs && !$rs->is_user_provided) || $default_profile{$quality_profile}) {
      0        
253 0           die "'$quality_profile' is not a user provided entry. Cannot be deleted\n";
254             }
255              
256 0           log_msg ":: Removing '$quality_profile' entry ...";
257              
258             # Begin transation
259 0           my $guard = $schema->txn_scope_guard;
260              
261 0           $rs->delete;
262              
263             # End transation
264 0           $guard->commit;
265             }
266              
267             sub restoredb {
268 0     0 0   my $self = shift;
269 0           my $schema = App::Sandy::DB->schema;
270              
271 0           log_msg ":: Searching for user-provided entries ...";
272 0           my $user_provided = $schema->resultset('QualityProfile')->search(
273             { is_user_provided => 1 },
274             );
275              
276 0           my $entry = $user_provided->next;
277 0 0         if ($entry) {
278 0           log_msg ":: Found:";
279 0           do {
280 0           log_msg ' ==> ' . $entry->name;
281             } while ($entry = $user_provided->next);
282             } else {
283 0           die "Not found user-provided entries. There is no need to restoring\n";
284             }
285              
286 0           log_msg ":: Removing all user-provided entries ...";
287              
288             # Begin transation
289 0           my $guard = $schema->txn_scope_guard;
290              
291 0           $user_provided->delete;
292              
293             # End transation
294 0           $guard->commit;
295             }
296              
297             sub make_report {
298 0     0 0   my $self = shift;
299 0           my $schema = App::Sandy::DB->schema;
300 0           my %report = %{ &DEFAULT_PROFILE };
  0            
301              
302 0           my $rs = $schema->resultset('QualityProfile')->search(undef);
303              
304 0           while (my $quality = $rs->next) {
305 0 0         my %hash = (
    0          
306             'mean' => $quality->mean,
307             'stdd' => $quality->stdd,
308             'error' => $quality->error,
309             'type' => $quality->is_single_molecule ? 'single-molecule' : 'fragment',
310             'source' => $quality->source,
311             'provider' => $quality->is_user_provided ? "user" : "vendor",
312             'date' => $quality->date
313             );
314 0           $report{$quality->name} = \%hash;
315             }
316              
317 0           return \%report;
318             }
319              
320             __END__
321              
322             =pod
323              
324             =encoding UTF-8
325              
326             =head1 NAME
327              
328             App::Sandy::DB::Handle::Quality - Class to handle quality database schemas.
329              
330             =head1 VERSION
331              
332             version 0.22
333              
334             =head1 AUTHORS
335              
336             =over 4
337              
338             =item *
339              
340             Thiago L. A. Miller <tmiller@mochsl.org.br>
341              
342             =item *
343              
344             J. Leonel Buzzo <lbuzzo@mochsl.org.br>
345              
346             =item *
347              
348             Felipe R. C. dos Santos <fsantos@mochsl.org.br>
349              
350             =item *
351              
352             Helena B. Conceição <hconceicao@mochsl.org.br>
353              
354             =item *
355              
356             Gabriela Guardia <gguardia@mochsl.org.br>
357              
358             =item *
359              
360             Fernanda Orpinelli <forpinelli@mochsl.org.br>
361              
362             =item *
363              
364             Pedro A. F. Galante <pgalante@mochsl.org.br>
365              
366             =back
367              
368             =head1 COPYRIGHT AND LICENSE
369              
370             This software is Copyright (c) 2018 by Teaching and Research Institute from Sírio-Libanês Hospital.
371              
372             This is free software, licensed under:
373              
374             The GNU General Public License, Version 3, June 2007
375              
376             =cut