@@ -32,7 +32,7 @@ def __init__(self, app_loc: str):
3232 self .file_type = self .config .get ('fileType' , 'FASTQ' )
3333 if not os .path .exists (self .coverage_file ):
3434 with open (self .coverage_file , 'w' ) as f :
35- f .write ("timestamp,reference,avg_depth,breadth ,read_count\n " )
35+ f .write ("timestamp,reference,fold_coverage ,read_count\n " )
3636
3737 def on_moved (self , event ):
3838 self .on_any_event (event )
@@ -150,7 +150,7 @@ def merge_bam(self, new_bam: str):
150150 logger .error (f"Error sorting/indexing merged BAM: { e } " )
151151
152152 def calculate_and_record_coverage (self ):
153- """Calculate and record average depth, breadth of coverage, and read count per reference."""
153+ """Calculate and record fold coverage (average depth) and read count per reference."""
154154 try :
155155 bam = pysam .AlignmentFile (self .merged_bam , "rb" , check_sq = False )
156156 if not bam .has_index ():
@@ -159,22 +159,23 @@ def calculate_and_record_coverage(self):
159159 coverage_data = {}
160160 for ref in bam .references :
161161 ref_length = bam .lengths [bam .references .index (ref )]
162- pileup = bam .pileup (ref )
163- depths = [p .nsegments for p in pileup if p .nsegments > 0 ]
164- covered_positions = len (depths )
165- avg_depth = sum (depths ) / len (depths ) if depths else 0
166- breadth = (covered_positions / ref_length ) * 100 if ref_length > 0 else 0
162+ # Get coverage depth across all positions for this reference
163+ coverage = bam .count_coverage (ref )
164+ # Sum depths across all bases (A, C, G, T) at each position
165+ total_depth = sum (sum (cov ) for cov in coverage )
166+ # Calculate fold coverage as total depth divided by reference length
167+ fold_coverage = total_depth / ref_length if ref_length > 0 else 0
167168 read_count = bam .count (ref ) # Count reads mapping to this reference
168- coverage_data [ref ] = {"avg_depth " : avg_depth , "breadth" : breadth , "read_count" : read_count }
169- # Check if breadth exceeds the threshold and trigger alert if necessary
170- print ( f"Reference: { ref } , Avg Depth: { avg_depth :.2f } , Breadth: { breadth :.2f } %, Read Count: { read_count } " )
171- self .check_breadth_alert (ref , breadth )
169+ coverage_data [ref ] = {"fold_coverage " : fold_coverage , "read_count" : read_count }
170+ print ( f"Reference: { ref } , Fold Coverage: { fold_coverage :.2f } x, Read Count: { read_count } " )
171+ # Update alert check to use fold coverage if needed
172+ self .check_fold_coverage_alert (ref , fold_coverage )
172173 bam .close ()
173174
174175 timestamp = time .strftime ("%Y-%m-%d %H:%M:%S" , time .gmtime ())
175176 with open (self .coverage_file , 'a' ) as f :
176177 for ref , cov in coverage_data .items ():
177- f .write (f"{ timestamp } ,{ ref } ,{ cov ['avg_depth' ] } , { cov [ 'breadth ' ]} ,{ cov ['read_count' ]} \n " )
178+ f .write (f"{ timestamp } ,{ ref } ,{ cov ['fold_coverage ' ]} ,{ cov ['read_count' ]} \n " )
178179 logger .debug (f"Coverage and read counts recorded at { timestamp } " )
179180
180181 # Emit coverage update via Socket.IO
@@ -186,49 +187,22 @@ def calculate_and_record_coverage(self):
186187 except Exception as e :
187188 logger .error (f"Error calculating coverage: { e } " )
188189
189- def check_breadth_alert (self , ref : str , breadth : float ):
190- """Check if breadth exceeds threshold and send alerts."""
191- print (f"Checking breadth alert for { ref } with breadth { breadth :.2f} %" )
192- alertinfo_cfg_file = os .path .join (self .app_loc , 'alertinfo.cfg' )
193- try :
194- with open (alertinfo_cfg_file , 'r' ) as f :
195- alertinfo_cfg_data = json .load (f )
196- queries = alertinfo_cfg_data .get ("queries" , [])
197- device = alertinfo_cfg_data .get ("device" , "" )
198- print (alertinfo_cfg_data )
199- for query in queries :
200- print (f"Checking query: { query } " )
201- if ref == query .get ("header" , "" ):
202- print (f"Matched query: { query } " )
203- threshold = float (query .get ("threshold" , 0 ))
204- current_breadth = query .get ("current_breadth" , 0 )
205- # Check alert condition before updating current_breadth
206- if breadth >= threshold and current_breadth < threshold :
207- alert_str = f"Alert: { query ['name' ]} breadth coverage reached { breadth :.2f} % (threshold: { threshold } %)"
208- logger .critical (alert_str )
209- if device :
210- LinuxNotification .send_notification (device , alert_str )
211- # Send alerts if configured
212- if "alertNotifConfig" in alertinfo_cfg_data :
213- print ("Sending email" )
214- email_config = alertinfo_cfg_data ['alertNotifConfig' ]
215- subject = "nanoCAS Alert"
216- body = alert_str
217- send_email (subject , body , email_config )
218- # Send SMS via Twilio if configured in .env
219- if os .getenv ('TWILIO_ACCOUNT_SID' ):
220- print ("Sending SMS" )
221- send_sms (alert_str )
222- else :
223- print ("Twilio not configured; skipping SMS" )
224- logger .debug ("Twilio not configured in .env; SMS skipped" )
225- # Update current_breadth after checking
226- if breadth > current_breadth :
227- query ["current_breadth" ] = breadth
228- with open (alertinfo_cfg_file , 'w' ) as f :
229- json .dump (alertinfo_cfg_data , f , indent = 4 )
230- else :
231- print (f"No match for query: { query } " )
232- logger .debug (f"No match for query: { query } " )
233- except Exception as e :
234- logger .error (f"Error checking breadth alert: { e } " )
190+ def check_fold_coverage_alert (self , ref : str , fold_coverage : float ):
191+ """Check if fold coverage exceeds threshold and send alerts."""
192+ with open (os .path .join (self .app_loc , 'alertinfo.cfg' ), 'r' ) as f :
193+ alertinfo_cfg_data = json .load (f )
194+ queries = alertinfo_cfg_data .get ("queries" , [])
195+ device = alertinfo_cfg_data .get ("device" , "" )
196+ for query in queries :
197+ if ref == query .get ("header" , "" ):
198+ threshold = float (query .get ("threshold" , 0 ))
199+ if fold_coverage >= threshold :
200+ alert_str = f"Alert: { query ['name' ]} fold coverage reached { fold_coverage :.2f} x (threshold: { threshold } x)"
201+ logger .critical (alert_str )
202+ if device :
203+ LinuxNotification .send_notification (device , alert_str )
204+ if "alertNotifConfig" in alertinfo_cfg_data :
205+ email_config = alertinfo_cfg_data ['alertNotifConfig' ]
206+ send_email ("nanoCAS Alert" , alert_str , email_config )
207+ if os .getenv ('TWILIO_ACCOUNT_SID' ):
208+ send_sms (alert_str )
0 commit comments