File Coverage

blib/lib/Bio/JBrowse/Store/NCList/IntervalStore.pm
Criterion Covered Total %
statement 19 125 15.2
branch 0 26 0.0
condition 0 10 0.0
subroutine 7 26 26.9
pod 6 12 50.0
total 32 199 16.0


line stmt bran cond sub pod time code
1             package Bio::JBrowse::Store::NCList::IntervalStore;
2             BEGIN {
3 1     1   123 $Bio::JBrowse::Store::NCList::IntervalStore::AUTHORITY = 'cpan:RBUELS';
4             }
5             {
6             $Bio::JBrowse::Store::NCList::IntervalStore::VERSION = '0.1';
7             }
8 1     1   6 use strict;
  1         3  
  1         30  
9 1     1   5 use warnings;
  1         2  
  1         26  
10 1     1   6 use Carp;
  1         1  
  1         70  
11              
12 1     1   1031 use POSIX ();
  1         11635  
  1         29  
13 1     1   9 use List::Util ();
  1         2  
  1         16  
14              
15 1     1   937 use Bio::JBrowse::Store::NCList::LazyNCList ();
  1         3  
  1         2356  
16              
17              
18             sub new {
19 0     0 1   my ($class, $args) = @_;
20              
21 0   0       my $self = {
      0        
22             store => $args->{store} || die,
23              
24             compress => $args->{compress},
25              
26             attrs => $args->{arrayRepr},
27             urlTemplate => $args->{urlTemplate} || ("lf-{Chunk}"
28             . $args->{store}->ext),
29             nclist => $args->{nclist},
30             minStart => $args->{minStart},
31             maxEnd => $args->{maxEnd},
32             loadedChunks => {}
33             };
34              
35 0 0         if( defined $args->{nclist} ) {
36             # we're already loaded
37             $self->{lazyNCList} =
38             Bio::JBrowse::Store::NCList::LazyNCList->importExisting(
39             $self->{attrs},
40             $args->{count},
41             $args->{minStart},
42             $args->{maxEnd},
43 0     0     sub { $self->_loadChunk( @_ ); },
44 0           $args->{nclist} );
45             }
46              
47 0           bless $self, $class;
48              
49 0           return $self;
50             }
51              
52             sub _loadChunk {
53 0     0     my ($self, $chunkId) = @_;
54 0           my $chunk = $self->{loadedChunks}->{$chunkId};
55 0 0         if (defined($chunk)) {
56 0           return $chunk;
57             } else {
58 0           (my $path = $self->{urlTemplate}) =~ s/\{Chunk\}/$chunkId/g;
59 0           $chunk = $self->{store}->get( $path );
60             # TODO limit the number of chunks that we keep in memory
61 0           $self->{loadedChunks}->{$chunkId} = $chunk;
62 0           return $chunk;
63             }
64             }
65              
66              
67             sub startLoad {
68 0     0 1   my ( $self, $measure, $chunkBytes ) = @_;
69              
70 0 0         if (defined($self->{nclist})) {
71 0           confess "loading into an already-loaded Bio::JBrowse::Store::NCList::IntervalStore";
72             } else {
73 0           my $lazyClass = $self->{attrs}->getClass( { start => 1, end => 2, chunk => 3 } );
74 0           $self->{lazyClass} = $lazyClass->{index};
75              
76             # add a new class for "fake" features
77             my $makeLazy = sub {
78 0     0     my ($start, $end, $chunkId) = @_;
79 0           my $f = { start => $start, end => $end, chunk => $chunkId };
80 0           return [ $lazyClass->{index}, map { $f->{$_} } @{$lazyClass->{attributes}} ];
  0            
  0            
81 0           };
82             my $output = sub {
83 0     0     my ($toStore, $chunkId) = @_;
84 0           (my $path = $self->{urlTemplate}) =~ s/\{Chunk\}/$chunkId/ig;
85 0           $self->{store}->put($path, $toStore);
86 0           };
87             $self->{lazyNCList} =
88             Bio::JBrowse::Store::NCList::LazyNCList->new( $self->{attrs},
89             $self->{lazyClass},
90             $makeLazy,
91 0     0     sub { $self->_loadChunk( @_); },
92 0           $measure,
93             $output,
94             $chunkBytes);
95             }
96             }
97              
98              
99             sub addSorted {
100 0     0 1   my ($self, $feat) = @_;
101 0           $self->{lazyNCList}->addSorted($feat);
102             }
103              
104              
105             sub finishLoad {
106 0     0 1   my ($self) = @_;
107 0           $self->{lazyNCList}->finish();
108 0           $self->{nclist} = $self->lazyNCList->topLevelList();
109              
110 0           my $trackData = {
111             featureCount => $self->count,
112             intervals => $self->descriptor,
113             histograms => $self->writeHistograms,
114             formatVersion => 1
115             };
116              
117 0 0         $self->store->put( 'trackData.'.( $self->{compress} ? 'jsonz' : 'json' ), $trackData);
118              
119             }
120              
121             sub writeHistograms {
122 0     0 0   my ( $self ) = @_;
123             #this series of numbers is used in JBrowse for zoom level relationships
124 0           my @multiples = (1, 2, 5, 10, 20, 50, 100, 200, 500,
125             1000, 2000, 5000, 10_000, 20_000, 50_000,
126             100_000, 200_000, 500_000, 1_000_000);
127 0           my $histChunkSize = 10_000;
128              
129 0           my $attrs = $self->{attrs};
130 0           my $getStart = $attrs->makeGetter("start");
131 0           my $getEnd = $attrs->makeGetter("end");
132              
133 0           my $jsonStore = $self->store;
134 0   0       my $refEnd = $self->lazyNCList->maxEnd || 0;
135 0           my $featureCount = $self->count;
136              
137             # $histBinThresh is the approximate the number of bases per
138             # histogram bin at the zoom level where FeatureTrack.js switches
139             # to the histogram view by default
140 0 0         my $histBinThresh = $featureCount ? ($refEnd * 2.5) / $featureCount : 999_999_999_999;
141 0   0 0     my $histBinBases = ( List::Util::first { $_ > $histBinThresh } @multiples ) || $multiples[-1];
  0            
142              
143             # initialize histogram arrays to all zeroes
144 0           my @histograms;
145 0           for (my $i = 0; $i < @multiples; $i++) {
146 0           my $binBases = $histBinBases * $multiples[$i];
147 0           $histograms[$i] = [(0) x POSIX::ceil($refEnd / $binBases)];
148             # somewhat arbitrarily cut off the histograms at 100 bins
149 0 0         last if $binBases * 100 > $refEnd;
150             }
151              
152             my $processFeat = sub {
153 0     0     my ($feature) = @_;
154 0           my $curHist;
155 0           my $start = List::Util::max(0, List::Util::min($getStart->($feature), $refEnd));
156 0           my $end = List::Util::min($getEnd->($feature), $refEnd);
157 0 0         return if ($end < 0);
158              
159 0           for (my $i = 0; $i <= $#multiples; $i++) {
160 0           my $binBases = $histBinBases * $multiples[$i];
161 0           $curHist = $histograms[$i];
162 0 0         last unless defined($curHist);
163              
164 0           my $firstBin = int($start / $binBases);
165 0           my $lastBin = int($end / $binBases);
166 0           for (my $bin = $firstBin; $bin <= $lastBin; $bin++) {
167 0           $curHist->[$bin] += 1;
168             }
169             }
170 0           };
171              
172 0           $self->overlapCallback($self->lazyNCList->minStart,
173             $self->lazyNCList->maxEnd,
174             $processFeat);
175              
176             # find multiple of base hist bin size that's just over $histBinThresh
177 0           my $i;
178 0           for ($i = 1; $i <= $#multiples; $i++) {
179 0 0         last if ($histBinBases * $multiples[$i]) > $histBinThresh;
180             }
181              
182 0           my @histogramMeta;
183             my @histStats;
184 0           for (my $j = $i - 1; $j <= $#multiples; $j += 1) {
185 0           my $curHist = $histograms[$j];
186 0 0         last unless defined($curHist);
187 0           my $histBases = $histBinBases * $multiples[$j];
188              
189 0           my $chunks = $self->chunkArray($curHist, $histChunkSize);
190 0           for (my $k = 0; $k <= $#{$chunks}; $k++) {
  0            
191 0           $jsonStore->put("hist-$histBases-$k" . $jsonStore->ext,
192             $chunks->[$k]);
193             }
194 0           push @histogramMeta,
195             {
196             basesPerBin => $histBases,
197             arrayParams => {
198 0           length => $#{$curHist} + 1,
199             urlTemplate => "hist-$histBases-{Chunk}" . $jsonStore->ext,
200             chunkSize => $histChunkSize
201             }
202             };
203 0 0         push @histStats,
    0          
204             {
205             'basesPerBin' => $histBases,
206             'max' => @$curHist ? List::Util::max( @$curHist ) : undef,
207             'mean' => @$curHist ? ( List::Util::sum( @$curHist ) / @$curHist ) : undef,
208             };
209             }
210              
211 0           return { meta => \@histogramMeta,
212             stats => \@histStats };
213             }
214              
215              
216             sub chunkArray {
217 0     0 0   my ( $self, $bigArray, $chunkSize) = @_;
218              
219 0           my @result;
220 0           for (my $start = 0; $start <= $#{$bigArray}; $start += $chunkSize) {
  0            
221 0           my $lastIndex = $start + $chunkSize;
222 0 0         $lastIndex = $#{$bigArray} if $lastIndex > $#{$bigArray};
  0            
  0            
223              
224 0           push @result, [@{$bigArray}[$start..$lastIndex]];
  0            
225             }
226 0           return \@result;
227             }
228              
229              
230              
231             sub overlapCallback {
232 0     0 1   my ($self, $start, $end, $cb) = @_;
233 0           $self->lazyNCList->overlapCallback($start, $end, $cb);
234             }
235              
236              
237 0     0 0   sub lazyNCList { shift->{lazyNCList} }
238 0     0 0   sub count { shift->{lazyNCList}->count }
239 0     0 0   sub hasIntervals { shift->count > 0 }
240 0     0 0   sub store { shift->{store} }
241              
242              
243             sub descriptor {
244 0     0 1   my ( $self ) = @_;
245             return {
246 0           lazyClass => $self->{lazyClass},
247             nclist => $self->{nclist},
248             classes => $self->{attrs}->descriptor,
249             urlTemplate => $self->{urlTemplate},
250             count => $self->count,
251             minStart => $self->lazyNCList->minStart,
252             maxEnd => $self->lazyNCList->maxEnd
253             };
254             }
255              
256             1;
257              
258             __END__