File Coverage

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