11import contextlib
2+ import functools
23import multiprocessing
34import os
45import shutil
@@ -198,35 +199,17 @@ def __init__(self, bamFilesList, binLength=50, numberOfSamples=None, numberOfPro
198199 self .bed_and_bin = bed_and_bin
199200 self .genomeChunkSize = genomeChunkSize
200201
201- if extendReads and len (bamFilesList ):
202- from deeptools .getFragmentAndReadSize import get_read_and_fragment_length
203- frag_len_dict , read_len_dict = get_read_and_fragment_length (bamFilesList [0 ],
204- return_lengths = False ,
205- blackListFileName = blackListFileName ,
206- numberOfProcessors = numberOfProcessors ,
207- verbose = verbose )
208- if extendReads is True :
209- # try to guess fragment length if the bam file contains paired end reads
210- if frag_len_dict :
211- self .defaultFragmentLength = int (frag_len_dict ['median' ])
212- else :
213- sys .exit ("*ERROR*: library is not paired-end. Please provide an extension length." )
214- if verbose :
215- print ("Fragment length based on paired en data "
216- "estimated to be {}" .format (frag_len_dict ['median' ]))
217-
218- elif extendReads < read_len_dict ['median' ]:
219- sys .stderr .write ("*WARNING*: read extension is smaller than read length (read length = {}). "
220- "Reads will not be extended.\n " .format (int (read_len_dict ['median' ])))
221- self .defaultFragmentLength = 'read length'
222-
223- elif extendReads > 2000 :
224- sys .exit (f"*ERROR*: read extension must be smaller that 2000. Value give: { extendReads } " )
225- else :
226- self .defaultFragmentLength = int (extendReads )
227-
202+ if extendReads :
203+ self .defaultFragmentLengths = [self .get_default_fragment_length (fname , extendReads ,
204+ blackListFileName = blackListFileName ,
205+ numberOfProcessors = numberOfProcessors ,
206+ verbose = verbose )
207+ for fname in bamFilesList ]
228208 else :
229- self .defaultFragmentLength = 'read length'
209+ self .defaultFragmentLengths = ['read length' ] * len (bamFilesList )
210+
211+ # direct call fallback
212+ self .defaultFragmentLength = self .defaultFragmentLengths [0 ] if self .defaultFragmentLengths else 'read length'
230213
231214 self .numberOfProcessors = numberOfProcessors
232215 self .verbose = verbose
@@ -255,12 +238,8 @@ def __init__(self, bamFilesList, binLength=50, numberOfSamples=None, numberOfPro
255238 if numberOfSamples is None and stepSize is None and bedFile is None :
256239 raise ValueError ("either stepSize, numberOfSamples or bedFile have to be set" )
257240
258- if self .defaultFragmentLength != 'read length' :
259- self .maxPairedFragmentLength = 4 * self .defaultFragmentLength
260- else :
261- self .maxPairedFragmentLength = 1000
262- if self .maxFragmentLength > 0 :
263- self .maxPairedFragmentLength = self .maxFragmentLength
241+ self .maxPairedFragmentLengths = [self .get_max_paired_fragment_length (x ) for x in self .defaultFragmentLengths ]
242+ self .maxPairedFragmentLength = self .get_max_paired_fragment_length (self .defaultFragmentLength )
264243
265244 if len (self .mappedList ) == 0 :
266245 try :
@@ -273,6 +252,50 @@ def __init__(self, bamFilesList, binLength=50, numberOfSamples=None, numberOfPro
273252 self .mappedList = []
274253 self .statsList = []
275254
255+ @staticmethod
256+ def get_default_fragment_length (bamFile , extendReads , blackListFileName = None ,
257+ numberOfProcessors = 1 , verbose = False ):
258+ """
259+ Returns the length to which reads of the given file are extended,
260+ or 'read length' if reads are not to be extended.
261+ If extendReads is True, the median fragment length of the file is used.
262+ """
263+ from deeptools .getFragmentAndReadSize import get_read_and_fragment_length
264+ frag_len_dict , read_len_dict = get_read_and_fragment_length (bamFile ,
265+ return_lengths = False ,
266+ blackListFileName = blackListFileName ,
267+ numberOfProcessors = numberOfProcessors ,
268+ verbose = verbose )
269+ if extendReads is True :
270+ # try to guess fragment length if the bam file contains paired end reads
271+ if not frag_len_dict :
272+ sys .exit (f"*ERROR*: library { bamFile } is not paired-end. Please provide an extension length." )
273+ if verbose :
274+ print (f"Fragment length of { bamFile } based on paired en data "
275+ f"estimated to be { frag_len_dict ['median' ]} " )
276+ return int (frag_len_dict ['median' ])
277+
278+ if extendReads < read_len_dict ['median' ]:
279+ sys .stderr .write (f"*WARNING*: read extension is smaller than read length (read length = { int (read_len_dict ['median' ])} ) "
280+ f"for { bamFile } . Reads will not be extended.\n " )
281+ return 'read length'
282+
283+ if extendReads > 2000 :
284+ sys .exit (f"*ERROR*: read extension must be smaller that 2000. Value give: { extendReads } " )
285+
286+ return int (extendReads )
287+
288+ def get_max_paired_fragment_length (self , defaultFragmentLength ):
289+ """
290+ Returns the maximum fragment length for which read pairs are
291+ considered proper pairs, given the default fragment length of a file.
292+ """
293+ if self .maxFragmentLength > 0 :
294+ return self .maxFragmentLength
295+ if defaultFragmentLength != 'read length' :
296+ return 4 * defaultFragmentLength
297+ return 1000
298+
276299 def get_chunk_length (self , bamFilesHandles , genomeSize , chromSizes , chrLengths ):
277300 # Try to determine an optimal fraction of the genome (chunkSize) that is sent to
278301 # workers for analysis. If too short, too much time is spent loading the files
@@ -499,9 +522,11 @@ def count_reads_in_region(self, chrom, start, end, bed_regions_list=None):
499522 else :
500523 _file_name = ''
501524
502- for bam in bam_handles :
525+ for bam_idx , bam in enumerate ( bam_handles ) :
503526 for trans in transcriptsToConsider :
504- tcov = self .get_coverage_of_region (bam , chrom , trans )
527+ tcov = self .get_coverage_of_region (bam , chrom , trans ,
528+ defaultFragmentLength = self .defaultFragmentLengths [bam_idx ],
529+ maxPairedFragmentLength = self .maxPairedFragmentLengths [bam_idx ])
505530 if bed_regions_list is not None and not self .bed_and_bin :
506531 subnum_reads_per_bin .append (np .sum (tcov ))
507532 else :
@@ -537,11 +562,17 @@ def count_reads_in_region(self, chrom, start, end, bed_regions_list=None):
537562 return subnum_reads_per_bin , _file_name
538563
539564 def get_coverage_of_region (self , bamHandle , chrom , regions ,
540- fragmentFromRead_func = None ):
565+ fragmentFromRead_func = None ,
566+ defaultFragmentLength = None ,
567+ maxPairedFragmentLength = None ):
541568 """
542569 Returns a numpy array that corresponds to the number of reads
543570 that overlap with each tile.
544571
572+ defaultFragmentLength and maxPairedFragmentLength are the values for
573+ the file of bamHandle. If not given, self.defaultFragmentLength and
574+ self.maxPairedFragmentLength are used.
575+
545576 >>> test = Tester()
546577 >>> import pysam
547578 >>> c = CountReadsPerBin([], stepSize=1, extendReads=300)
@@ -572,8 +603,14 @@ def get_coverage_of_region(self, bamHandle, chrom, regions,
572603
573604
574605 """
606+ if defaultFragmentLength is None :
607+ defaultFragmentLength = self .defaultFragmentLength
608+ if maxPairedFragmentLength is None :
609+ maxPairedFragmentLength = self .maxPairedFragmentLength
575610 if not fragmentFromRead_func :
576- fragmentFromRead_func = self .get_fragment_from_read
611+ fragmentFromRead_func = functools .partial (self .get_fragment_from_read ,
612+ defaultFragmentLength = defaultFragmentLength ,
613+ maxPairedFragmentLength = maxPairedFragmentLength )
577614 nbins = len (regions )
578615 if len (regions [0 ]) == 3 :
579616 nbins = 0
@@ -583,10 +620,10 @@ def get_coverage_of_region(self, bamHandle, chrom, regions,
583620 nbins += 1
584621 coverages = np .zeros (nbins , dtype = 'float64' )
585622
586- if self . defaultFragmentLength == 'read length' :
623+ if defaultFragmentLength == 'read length' :
587624 extension = 0
588625 else :
589- extension = self . maxPairedFragmentLength
626+ extension = maxPairedFragmentLength
590627
591628 blackList = None
592629 if self .blackListFileName is not None :
@@ -779,7 +816,7 @@ def is_proper_pair(read, maxPairedFragmentLength):
779816 return True
780817 return False
781818
782- def get_fragment_from_read (self , read ):
819+ def get_fragment_from_read (self , read , defaultFragmentLength = None , maxPairedFragmentLength = None ):
783820 """Get read start and end position of a read.
784821 If given, the reads are extended as follows:
785822 If reads are paired end, each read mate is extended to match
@@ -823,6 +860,14 @@ def get_fragment_from_read(self, read):
823860 ----------
824861 read : pysam read object
825862
863+ defaultFragmentLength : int or 'read length'
864+ The fragment length used to extend reads of the file of `read`.
865+ If None, self.defaultFragmentLength is used.
866+
867+ maxPairedFragmentLength : int
868+ Pairs with a larger fragment length are extended as single reads.
869+ If None, self.maxPairedFragmentLength is used.
870+
826871
827872 Returns
828873 -------
@@ -865,11 +910,16 @@ def get_fragment_from_read(self, read):
865910 # E.g for a cigar of 40M260N22M
866911 # get blocks return two elements for the first 40 matches
867912 # and the for the last 22 matches.
868- if self .defaultFragmentLength == 'read length' :
913+ if defaultFragmentLength is None :
914+ defaultFragmentLength = self .defaultFragmentLength
915+ if maxPairedFragmentLength is None :
916+ maxPairedFragmentLength = self .maxPairedFragmentLength
917+
918+ if defaultFragmentLength == 'read length' :
869919 return read .get_blocks ()
870920
871921 else :
872- if self .is_proper_pair (read , self . maxPairedFragmentLength ):
922+ if self .is_proper_pair (read , maxPairedFragmentLength ):
873923 if read .is_reverse :
874924 fragmentStart = read .next_reference_start
875925 fragmentEnd = read .reference_end
@@ -882,11 +932,11 @@ def get_fragment_from_read(self, read):
882932 # Extend using the default fragment length
883933 else :
884934 if read .is_reverse :
885- fragmentStart = read .reference_end - self . defaultFragmentLength
935+ fragmentStart = read .reference_end - defaultFragmentLength
886936 fragmentEnd = read .reference_end
887937 else :
888938 fragmentStart = read .reference_start
889- fragmentEnd = read .reference_start + self . defaultFragmentLength
939+ fragmentEnd = read .reference_start + defaultFragmentLength
890940
891941 if self .center_read :
892942 fragmentCenter = fragmentEnd - (fragmentEnd - fragmentStart ) / 2
0 commit comments