Package pylal :: Module followup_trigger
[hide private]
[frames] | no frames]

Source Code for Module pylal.followup_trigger

   1  # Copyright (C) 2006  Alexander Dietz 
   2  # 
   3  # This program is free software; you can redistribute it and/or modify it 
   4  # under the terms of the GNU General Public License as published by the 
   5  # Free Software Foundation; either version 2 of the License, or (at your 
   6  # option) any later version. 
   7  # 
   8  # This program is distributed in the hope that it will be useful, but 
   9  # WITHOUT ANY WARRANTY; without even the implied warranty of 
  10  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General 
  11  # Public License for more details. 
  12  # 
  13  # You should have received a copy of the GNU General Public License along 
  14  # with this program; if not, write to the Free Software Foundation, Inc., 
  15  # 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA. 
  16  from __future__ import division 
  17   
  18  __prog__ = "followup_trigger.py" 
  19   
  20  import os 
  21  import sys 
  22  import copy 
  23  from math import sqrt, pi 
  24  import subprocess 
  25  import tempfile 
  26   
  27  import pylab 
  28  import numpy 
  29   
  30  from pylal import SnglInspiralUtils 
  31  from pylal import InspiralUtils 
  32  from pylal import SimInspiralUtils 
  33  from pylal import CoincInspiralUtils 
  34  from pylal import SearchSummaryUtils 
  35  from pylal import git_version 
  36  from pylal import grbsummary 
  37  from pylal import viz 
  38  from pylal import tools 
  39  from glue import lal 
  40  from glue import markup 
  41  from glue import pipeline 
  42  from glue import segments 
  43  from glue import segmentsUtils 
  44  from glue import iterutils 
  45  from glue.markup import oneliner as extra 
  46  from glue.ligolw import table 
  47  from glue.ligolw import lsctables 
  48  from glue.ligolw import utils 
  49  from glue.ligolw import ligolw 
  50  from glue.ligolw.utils import ligolw_add 
  51   
  52   
  53  # DB content handler for reading xml input files 
54 -class ContentHandler(ligolw.LIGOLWContentHandler):
55 pass
56 lsctables.use_in(ContentHandler) 57 58 59 ##########################################################
60 -class FollowupTrigger:
61 """ 62 This defines a class for following up a trigger and to create 63 the timeseries of found triggers in each stage of the pipeline. 64 65 Usage: 66 67 # first need to initialize the class 68 followup = followup_trigger.FollowupTrigger(cache, opts) 69 70 # later, one can do a followup of different objects, like an injection, 71 # a coinc. trigger or a single trigger: 72 htmlname1 = followup.from_coinc(coinc) 73 htmlname2 = followup.from_sngl(sngl_inspiral) 74 htmlname3 = followup.from_missed(missed_inj) 75 htmlname4 = followup.from_found(found_inj) 76 htmlname5 = followup.from_new_coinc(new_coinc,[sngls]) 77 htmlname6 = followup.from_new_slide_coinc(new_coinc,[sngls],slideDict,segs) 78 79 # In each case the path to the created html file is returned. 80 # In the first call a CoincInspirals table is expected, a SnglInspiral 81 # table in the second case and a SimInspiral table in the two last. 82 """ 83 84 85 # -----------------------------------------------------
86 - def __init__(self, cache, opts, use_injections = True,do_slides=False):
87 """ 88 Initialize this class and sets up all the cache files. 89 @param cache: The cache of all files 90 @param opts: The 'opts' structure from the main code 91 @param use_injections: Specifying if injections are being used 92 (if no injections are used and this is set to 93 False, it speeds up the whole initalization...) 94 """ 95 pylab.rcParams.update({'text.usetex': False}) 96 97 # Check all the required options 98 if not hasattr(opts, 'followup_exttrig'): 99 # default: this is a regular search, not a exttrig search 100 opts.followup_exttrig = False 101 if not hasattr(opts, 'followup_time_window'): 102 # default: set the time window for the timeseries to 10 seconds 103 opts.followup_time_window = 10.0 104 if not hasattr(opts, 'followup_tag'): 105 # default: don't specify a followup-tag 106 opts.followup_tag = None 107 if not hasattr(opts, 'followup_sned'): 108 # default: do not incorporate the updating of effective distances 109 # with lalapps_sned 110 opts.followup_sned = None 111 112 option_list = ['verbose','followup_exttrig','output_path',\ 113 'followup_time_window','prefix',\ 114 'suffix','figure_resolution','user_tag',\ 115 'followup_tag','followup_sned'] 116 for option in option_list: 117 if not hasattr(opts, option): 118 raise "Error: The following parameter is required in the "\ 119 "opts structure for followup_trigger: ", option 120 121 122 # setting the color definition and the stages of the pipeline 123 self.colors = {'H1':'r','H2':'b','L1':'g','V1':'m','G1':'c'} 124 self.stageLabels = [('MATCHEDFILTER',['INSPIRAL'])] 125 if do_slides: 126 self.stageLabels.append(('COINCIDENCE-SLID',['THINCA_1','THINCA_SLIDES'])) 127 else: 128 self.stageLabels.append(('COINCIDENCE',['THINCA_0','THINCA_ZEROLAG'])) 129 130 self.orderLabels = ['MATCHEDFILTER'] 131 if do_slides: 132 self.orderLabels.extend( [ 'COINCIDENCE-SLID_CAT_1',\ 133 'COINCIDENCE-SLID_CAT_2', 'COINCIDENCE-SLID_CAT_3',\ 134 'COINCIDENCE-SLID_CAT_4', 'COINCIDENCE-SLID_CAT_5'] ) 135 else: 136 self.orderLabels.extend( [ 'COINCIDENCE_CAT_1','COINCIDENCE_CAT_2', \ 137 'COINCIDENCE_CAT_3','COINCIDENCE_CAT_4', \ 138 'COINCIDENCE_CAT_5'] ) 139 140 # set arguments from the options 141 self.opts = opts 142 self.tag = opts.user_tag 143 self.verbose = opts.verbose 144 self.exttrig = opts.followup_exttrig 145 self.output_path = opts.output_path 146 self.time_window = opts.followup_time_window 147 self.sned = opts.followup_sned 148 149 # Set argument "old_document" to true if the option is specified. 150 # Do not crash if "opts.old_document" is not defined in the script 151 # calling this method. 152 if not hasattr(opts, "old_document"): 153 self.old_document = False 154 else: 155 self.old_document = opts.old_document 156 157 # Choose the cache-file 158 if opts.followup_tag is None: 159 if opts.verbose: 160 print "WARNING: All injection files are considered. Is that desired?" 161 print " Or do you want to select an injection run with --followup-tag INJRUN ?" 162 self.cache = cache 163 else: 164 if opts.verbose: 165 print "WARNING: Only a selected injection run is taken into account: ", opts.followup_tag 166 print " Is that the desired behaviour? You might reconsider removing --followup-tag INJRUN " 167 self.cache = cache.sieve(description = opts.followup_tag) 168 169 # Value for the injection window. This value might be taken from 170 # a processParams table (see function get_injection_window) 171 self.get_injection_window() 172 173 # initialize a list of images created 174 self.fname_list = [] 175 176 # counter for the followups 177 self.number = 0 178 179 # setting for each time to followup: 180 self.followup_time = None 181 self.injection_id = None # only needed if time is an injection 182 self.flag_followup = False 183 184 # a dictionary used to create a list of injections to followup 185 # taking into account the 'combined' effective distance 186 # and vetoes as well 187 self.followup_dict = {'inj':lsctables.New(lsctables.SimInspiralTable), 'dist':[], 'type':[]} 188 self.vetoed_injections = lsctables.New(lsctables.SimInspiralTable) 189 190 if self.verbose: 191 print "\nStarting initializing the Followup class..." 192 193 # splitting up the cache for the different stages 194 self.trigger_cache = {} 195 for stageName, stagePatterns in self.stageLabels: 196 sievedFiles = [] 197 for pattern in stagePatterns: 198 sievedFiles.extend(self.cache.sieve(description=pattern)) 199 self.trigger_cache[stageName] = sievedFiles 200 if self.opts.verbose: 201 print "%d files found for stage %s" %\ 202 (len(self.trigger_cache[stageName]), stageName) 203 204 205 # generating a dictionary for injection followups 206 self.injections = dict() 207 if use_injections: 208 if self.verbose: 209 print "Creating the injection cache..." 210 211 self.injection_cache = self.cache.sieve(description = "INJECTION").\ 212 sieve(ifos='HL') 213 214 for file, entry in zip(self.injection_cache.pfnlist(), \ 215 self.injection_cache): 216 injection_id = self.get_injection_id(cache_entry = entry) 217 self.injections[injection_id] = SimInspiralUtils.\ 218 ReadSimInspiralFromFiles( \ 219 [file], verbose=False ) 220 221 # read the veto files 222 self.read_veto_files() 223 224 if self.verbose: 225 print "Initializing the Followup class done..."
226 227 # -----------------------------------------------------
228 - def set_sned(self, executable):
229 """ 230 Sets the sned flag to 'flag' 231 @param flag: the executable used to calculate the sned 232 """ 233 self.sned = executable
234 235 # -----------------------------------------------------
236 - def execute_sned(self, injections):
237 """ 238 Makes an external call to lalapps_sned to recalculate 239 the effective distances 240 @param injections: the list of injections to be converted 241 @return: the recalculated injection 242 """ 243 244 def fix_numrel_columns(sims): 245 """ 246 Due to a mismatch between our LAL tag and the LAL HEAD, against which 247 we compile pylal, there are missing columns that cannot be missing. 248 Just put in nonsense values so that glue.ligolw code won't barf. 249 """ 250 for sim in sims: 251 sim.numrel_data="nan" 252 sim.numrel_mode_max = 0 253 sim.numrel_mode_min = 0
254 255 file1 = '.followup_trigger.output.xml' 256 file2 = '.followup_trigger.input.xml' 257 258 # write out a dummy file, after fixing the columns 259 fix_numrel_columns(injections) 260 grbsummary.write_rows(injections, lsctables.SimInspiralTable, file1) 261 262 # call command for lalapps_sned 263 # FIXME: Hardcoded lower cutoff frequency 264 command = self.sned+' --f-lower 40.0 --inj-file '+file1+\ 265 ' --output '+file2 266 if self.verbose: 267 print "Executing command '%s'" % command 268 subprocess.call(command.split()) 269 270 # read in the 'converted' injection 271 doc = ligolw_add.ligolw_add(ligolw.Document(), [file2]) 272 # return a single SimInspiral table 273 return lsctables.SimInspiralTable.get_table(doc)
274 275 # -----------------------------------------------------
276 - def setTag(self, tag):
277 """ 278 Setting a tag (non-conformal naming because of backwards compatibality) 279 @param tag: the tag to be set 280 """ 281 self.set_tag(tag)
282
283 - def set_tag(self, tag):
284 """ 285 Setting a tag 286 @param tag: well, its the tag! 287 """ 288 self.tag = tag
289 290 291 # -----------------------------------------------------
292 - def get_injection_window(self):
293 """ 294 Extracting the length of the used injection_window from 295 any 'FOUND' file in the cache. 296 """ 297 298 # FIXME: This value should now be provided to minifollowups!! 299 # Ssipe output does come through here, but will not get the correct 300 # value (as SIRE files are not doing actual injection finding). 301 302 # get the process params table from one of the COIRE files 303 found_cache = self.cache.sieve(description = "FOUND") 304 if len(found_cache)==0: 305 self.injection_window = 0.1 306 print "INFO: No FOUND files found, so setting the injection window"\ 307 " to default value." 308 return 309 310 coire_file = found_cache.checkfilesexist()[0].pfnlist()[0] 311 try: 312 doc = SearchSummaryUtils.ReadTablesFromFiles([coire_file],\ 313 [lsctables.ProcessParamsTable]) 314 process_params = lsctables.ProcessParamsTable.get_table(doc) 315 except IOError: 316 sys.stderr.write("ERROR (IOError) while reading process_params table from"\ 317 " file %s. Does this file exist and does it contain"\ 318 " a search_summary table?\n" %(coire_file)) 319 raise 320 except: 321 raise "Error while reading process_params table from file: ", coire_file 322 323 # and retrieve the time window from this file 324 found_flag = False 325 for tab in process_params: 326 if tab.param=='--injection-window': 327 found_flag = True 328 self.injection_window = float(tab.value)/1000.0 329 if not found_flag: 330 sys.stderr.write("WARNING: No entry '--injection-window' found in file %s"\ 331 "Value used is %.1f ms. If incorrect, please change file at %s\n" %\ 332 (coire_file, 1000.0*self.injection_window, __file__)) 333 334 # set the parameter 335 if self.verbose: 336 print "Injection-window set to %.0f ms" % (1000*self.injection_window)
337 338 339 # -----------------------------------------------------
340 - def get_injection_id(self, filename=None, url=None, cache_entry=None):
341 """ 342 Extracting the injection ID from the filename, using 343 the mechanism as used in lalapps_path2cache. You can specify the filename 344 itself, the url or the cache entry. You must not specify more than one input! 345 The injection-ID is calculated in the following way (exttrig only): 346 347 The code expects the INSPIRAL and THINCA files in the following scheme (example): 348 PREFIX-TAGPART_injections32_77-GPS-DURATION.xml 349 The number of the injection run is extracted (INJRUN) as well as the following 350 number (INJNUMBER). The injection ID is then calculated as: 351 INJID = 100000*INJRUN + INJNUMBER 352 so for this example the injectionj ID is 3200077. 353 354 @param filename: filename from which the injection ID is extracted 355 @param url: url from which the injection ID is extracted 356 @param cache_entry: cache entry from which the injection ID is extracted 357 """ 358 359 # Check that only one input is given 360 if cache_entry: 361 if filename and url: 362 raise "Error in function get_injection_id: Only one input should be "\ 363 "specified. Now 'cache_entry' and another variable is specified. Check the code." 364 365 if cache_entry is None: 366 if filename and url: 367 raise "Error in function get_injection_id: Only one input should be "\ 368 "specified. Now 'filename' and 'url' is specified. Check the code." 369 370 if filename: 371 path, filename = os.path.split(filename.strip()) 372 url = "file://localhost%s" % os.path.abspath(os.path.join(path, filename)) 373 374 try: 375 cache_entry = lal.CacheEntry.from_T050017(url) 376 except ValueError, e: 377 raise "Error while extracting injection ID from file ", filename 378 379 # split the expression into different sub-pieces 380 pieces = cache_entry.description.split('_') 381 if self.exttrig: 382 383 # its easy for the exttrig case 384 injection_id = pieces[-2]+'_'+pieces[-1] 385 else: 386 387 # but need to check for the appearance of the CAT suffix else 388 index = 0 389 for ind, piece in enumerate(pieces): 390 if 'CAT' in piece: 391 index = ind 392 injection_id = pieces[index-1] 393 394 return injection_id
395
396 - def check_injection_id(self, cache_entry, tag):
397 """ 398 The above relies on a very specific naming convention, here we check if 399 the injection tag is present in the files' description. 400 """ 401 if tag in cache_entry.description: 402 return True 403 else: 404 return False
405 406 # -----------------------------------------------------
407 - def find_injection_id(self, injection):
408 """ 409 Find the injection-ID corresponding to this particular injection. 410 @param injection: the injection object 411 @return: the injection ID 412 """ 413 414 if self.injections: 415 # injection_id: the injection ID (a number or a string) 416 # group_inj: a list of SimInspiral tables 417 for injection_id, group_inj in self.injections.iteritems(): 418 for inj in group_inj: 419 if injection.geocent_end_time==inj.geocent_end_time and \ 420 injection.geocent_end_time_ns==inj.geocent_end_time_ns: 421 return injection_id 422 423 raise "No injection ID found for the above particular missed Injection " 424 425 else: 426 427 # cache not presearched, so searching for this particular injection 428 if self.verbose: 429 print "INFO: Searching for the injection at time %d.%d" % \ 430 (injection.geocent_end_time, injection.geocent_end_time_ns) 431 injection_cache = self.cache.sieve(description = "INJECTION").\ 432 sieve(ifos='HL') 433 434 for file, entry in zip(injection_cache.pfnlist(), injection_cache): 435 injection_id = self.get_injection_id(cache_entry = entry) 436 sims = SimInspiralUtils.ReadSimInspiralFromFiles( [file], verbose=False ) 437 438 for sim in sims: 439 # searching just by comparing the times... 440 if injection.geocent_end_time==sim.geocent_end_time and \ 441 injection.geocent_end_time_ns==sim.geocent_end_time_ns: 442 if self.verbose: 443 print "... found it: injection_id = ", injection_id 444 return injection_id 445 446 447 return None
448 449 # -----------------------------------------------------
450 - def read_veto_files( self ):
451 """ 452 Reads the veto segments given by veto-files (if any) 453 """ 454 self.vetodict = dict() 455 456 # loop over the IFO names 457 for ifoName in self.colors.keys(): 458 459 self.vetodict[ifoName]=None 460 # create the attribute name and check if it exists 461 attributeName = 'followup_vetofile_'+ifoName.lower() 462 if hasattr( self.opts, attributeName): 463 464 # get the filename 465 filename = getattr( self.opts, attributeName ) 466 if filename: 467 self.vetodict[ifoName] = segmentsUtils.fromsegwizard(open(filename))
468 469 # -----------------------------------------------------
470 - def reset( self ):
471 """ 472 Resets the counting number for the time-series plots generated. 473 """ 474 self.number=0
475 476 # -----------------------------------------------------
477 - def print_inj( self, inj, injID ):
478 """ 479 Print some useful informations to the screen. 480 @param inj: the current missed injection 481 @param injID: the injection ID (used for exttrig only) 482 """ 483 484 if self.exttrig: 485 print "\nInjection details for injection %d with injID %s: " %\ 486 (self.number, injID) 487 else: 488 print "\nInjection details for injection %d:" % (self.number) 489 490 print "m1: %.2f m2:%.2f | end_time: %d.%d | "\ 491 "distance: %.2f eff_dist_h: %.2f eff_dist_l: %.2f" % \ 492 ( inj.mass1, inj.mass2, inj.geocent_end_time, inj.geocent_end_time_ns,\ 493 inj.distance, inj.eff_dist_h, inj.eff_dist_l )
494 495 # ----------------------------------------------------
496 - def save_plot( self, stage ):
497 """ 498 Saves the plots and store them in a seperate fname_list. 499 @param stage: the stage this plot belongs to (e.g. INSPIRAL, THINCA,...) 500 """ 501 fname = 'Images/'+self.opts.prefix + "_"+self.tag+"_map-"+\ 502 stage+"-"+str(self.number) +self.opts.suffix+'.png' 503 fname_thumb = InspiralUtils.\ 504 savefig_pylal( filename = self.output_path+fname,\ 505 doThumb = True, 506 dpi_thumb = self.opts.figure_resolution) 507 508 self.fname_list.append( fname ) 509 return fname
510 511 512 # -----------------------------------------------------
513 - def get_time_trigger(self, trig):
514 """ 515 This is a helper function to return a GPS time as one float number 516 @param trig: a sngl_inspiral table entry 517 """ 518 return float(trig.end_time) + float(trig.end_time_ns) * 1.0e-9
519 520 521 # -----------------------------------------------------
522 - def get_effective_snr(self, trig, fac=50):
523 if trig.chisq>0: 524 return trig.get_effective_snr(fac=fac) 525 else: 526 return 0.0
527 528 # -----------------------------------------------------
529 - def get_new_snr(self, trig, index=6.):
530 return trig.get_new_snr(index=index)
531 532 # -----------------------------------------------------
533 - def get_sim_time(self, sim, ifo = None):
534 """ 535 This is a helper function to return a GPS time as one float number 536 for a certain IFO. If no IFO is specified the injected geocentric 537 time is returned. 538 @param sim: a sim_inspiral table entry 539 @param ifo: the IFO for which we want the sim time 540 """ 541 542 time=0 543 nano=0 544 545 if not ifo: 546 time = sim.geocent_end_time 547 nano = sim.geocent_end_time_ns 548 if ifo: 549 time = getattr(sim, ifo[0].lower()+'_end_time' ) 550 nano = getattr(sim, ifo[0].lower()+'_end_time_ns' ) 551 552 return float(time) + float(nano) * 1.0e-9
553 554 555 # -----------------------------------------------------
556 - def is_veto(self, time_trigger, ifo):
557 """ 558 This function checks if there is a veto at the time 'timeTrigger' 559 for the IFO 'ifo'. 560 @param time_trigger: The time to be investigated 561 @param ifo: The name of the IFO to be investigated 562 """ 563 if self.vetodict[ifo] is None: 564 return False 565 566 return iterutils.any(time_trigger in seg for seg in self.vetodict[ifo])
567 568 # -----------------------------------------------------
569 - def put_text(self, text):
570 """ 571 Puts some text into an otherwise empty plot. 572 @param text: text to put in the empty plot 573 """ 574 575 newText = '' 576 for i in range( int(len(text)/60.0)+1): 577 newText+=text[60*i:60*i+60]+'\n' 578 pylab.figtext(0.15,0.15, newText)
579 580 # -----------------------------------------------------
581 - def create_timeseries(self, trigger_files, stage, number,\ 582 slideDict=None):
583 """ 584 Investigate inspiral triggers and create a time-series 585 of the SNRs around the injected time 586 @param trigger_files: List of files containing the inspiral triggers 587 @param stage: the name of the stage (INSPIRAL_FIRST, THINCA_0_CAT_2) 588 @param number: the consecutive number for this inspiral followup 589 @param slideDict: A dictionary of ifo keyed slide times if using slides 590 """ 591 # create the small and large segments for storing triggers 592 seg_small = segments.segment(self.followup_time - self.injection_window, \ 593 self.followup_time + self.injection_window) 594 seg_large = segments.segment(self.followup_time - self.time_window, \ 595 self.followup_time + self.time_window) 596 if abs(seg_small) > abs(seg_large): 597 err_msg = "Injection window must be smaller than time_window." 598 err_msg = "Got injection window = %f and time window = %g." \ 599 %(self.injection_window,self.time_window) 600 raise ValueError(err_msg) 601 602 # read the file(s) and get the desired sngl_inspiral rows 603 if self.verbose: 604 print "Processing INSPIRAL triggers from files ", trigger_files 605 606 # Memory usage here can balloon, so read in files one-by-one and only keep 607 # triggers within time_window 608 sngls = lsctables.New(lsctables.SnglInspiralTable, \ 609 columns=lsctables.SnglInspiralTable.loadcolumns) 610 611 for file in trigger_files: 612 xmldoc = utils.load_filename(file, verbose=self.verbose, 613 contenthandler=ContentHandler) 614 try: 615 sngl_table = lsctables.SnglInspiralTable.get_table(xmldoc) 616 except ValueError: # Some files have no sngl table. That's okay 617 xmldoc.unlink() # Free memory 618 continue 619 if slideDict: # If time slide, slide the triggers 620 for event in sngl_table: 621 event.set_end( event.get_end() + slideDict[event.ifo] ) 622 # Remove triggers not within time window 623 sngl_table = sngl_table.vetoed(seg_large) 624 625 # Add to full list 626 if sngl_table: 627 sngls.extend(sngl_table) 628 629 xmldoc.unlink() # Free memory 630 631 # create a figure and initialize some lists 632 fig=pylab.figure() 633 foundSet = set() 634 loudest_details = {} 635 no_triggers_found = True 636 637 if len(sngls) == 0: 638 self.put_text( 'No triggers/coincidences found within time window') 639 else: 640 # loop over the IFOs 641 for ifo in self.colors.keys(): 642 # get the singles for this ifo 643 sngls_ifo = sngls.ifocut(ifo) 644 645 # select the triggers within a given time window 646 selected_large = sngls_ifo 647 time_large = [ float(sel.get_end()) - self.followup_time \ 648 for sel in selected_large ] 649 selected_small = sngls_ifo.vetoed(seg_small) 650 time_small = [ float(sel.get_end()) - self.followup_time \ 651 for sel in selected_small ] 652 653 # skip if no triggers from ifo in the large time window 654 if len(time_large) == 0: 655 continue 656 no_triggers_found = False 657 658 # add IFO to this set; the injection is found for this IFO-stage 659 if len(time_small)>0: 660 foundSet.add(ifo) 661 662 # record details of the loudest trigger 663 loudest = selected_small[selected_small.get_column('snr').argmax()] 664 loudest_details[ifo] = {} 665 loudest_details[ifo]["snr"] = loudest.snr 666 loudest_details[ifo]["mchirp"] = loudest.mchirp 667 loudest_details[ifo]["eta"] = loudest.eta 668 loudest_details[ifo]["eff_dist"] = loudest.eff_distance 669 loudest_details[ifo]["rchisq"] = 0 670 loudest_details[ifo]["bank_rchisq"] = 0 671 loudest_details[ifo]["auto_rchisq"] = 0 672 if loudest.chisq_dof: 673 loudest_details[ifo]["rchisq"] = loudest.chisq/(2*loudest.chisq_dof-2) 674 if loudest.bank_chisq_dof: 675 loudest_details[ifo]["bank_rchisq"] = loudest.bank_chisq/loudest.bank_chisq_dof 676 if loudest.cont_chisq_dof: 677 loudest_details[ifo]["auto_rchisq"] = loudest.cont_chisq/loudest.cont_chisq_dof 678 loudest_details[ifo]["timeTrigger"] = float(loudest.get_end()) 679 loudest_details[ifo]["eff_snr"] = self.get_effective_snr(loudest) 680 loudest_details[ifo]["new_snr"] = self.get_new_snr(loudest) 681 loudest_details[ifo]["end_time"] = loudest.end_time+loudest.end_time_ns*1E-9 682 loudest_details[ifo]["trig"] = loudest 683 684 # plot the triggers 685 pylab.plot( time_large, selected_large.get_column('snr'),\ 686 self.colors[ifo]+'o', label="_nolegend_") 687 pylab.plot( time_small, selected_small.get_column('snr'), \ 688 self.colors[ifo]+'s', label=ifo) 689 690 # highlight any veto 691 # FIXME: FOR NOW: COMMENTED OUT... 692 ## self.highlight_veto(self.followup_time, seg_large, ifo, ylims) 693 694 # draw the injection times and other stuff 695 if no_triggers_found: 696 self.put_text( 'No triggers/coincidences found within time window') 697 698 ylims=pylab.axes().get_ylim() 699 pylab.plot([0,0], ylims, 'g--', label="_nolegend_") 700 pylab.plot([-self.injection_window, -self.injection_window], ylims, 'c:',\ 701 label="_nolegend_") 702 pylab.plot([+self.injection_window, +self.injection_window], ylims, 'c:',\ 703 label="_nolegend_") 704 705 # save the plot 706 pylab.grid(True) 707 pylab.legend() 708 709 ylims=pylab.axes().get_ylim() 710 pylab.axis([-self.time_window, +self.time_window, ylims[0], ylims[1]]) 711 pylab.xlabel('time [s]') 712 pylab.ylabel('SNR') 713 pylab.title(stage+'_'+str(self.number)) 714 fname = self.save_plot( stage ) 715 pylab.close(fig) 716 717 result = {'filename':fname, 'foundset':foundSet, 'loudest_details':loudest_details} 718 return result
719 720 721 # -----------------------------------------------------
722 - def select_category(self, trigger_files, category):
723 """ 724 Return a trigger list that contains only files for the choosen category. 725 @param trigger_files : a list of trigger file names 726 @param category: a category number 727 @return: a sub list of filename corresponding to the category requested 728 """ 729 730 # there are two different labels used to denote 731 # the categories. THIS NEEDS TO BE UNIFIED 732 cat1 = 'CAT_'+str(category) 733 cat2 = 'CATEGORY_'+str(category) 734 735 if category==1: 736 # Category 1 files might not be labelled with a 'CAT_1' suffix. 737 # So, for now, all files NOT containing the expression 738 # 'CAT' in the filename are supposed to be CAT_1 files 739 new_list = [file for file in trigger_files \ 740 if 'CAT' not in file or cat1 in file or cat2 in file] 741 742 else: 743 cat = 'CAT_'+str(category) 744 new_list = [file for file in trigger_files if cat1 in file\ 745 or cat2 in file] 746 747 return new_list
748 749 # -----------------------------------------------------
750 - def fill_table(self, page, contents,header=False,no_wrapping=False):
751 """ 752 Fills contents in a html table 753 @param page: the page object describing a html page 754 @contents: the contents of the next table item 755 """ 756 757 page.add('<tr>') 758 for content in contents: 759 if header: 760 tmpString = '<th' 761 else: 762 tmpString = '<td' 763 if no_wrapping: 764 tmpString += ' style="white-space: nowrap;"' 765 tmpString += '>' 766 page.add(tmpString) 767 page.add( str(content) ) 768 if header: 769 page.add('</th>') 770 else: 771 page.add('</td>') 772 page.add('</tr>')
773 774 775 # --------------------------------------------
776 - def create_table_inj(self, inj):
777 """ 778 Creates the first table containing basic properties 779 of the injection which is followed up. 780 @param inj: an injection table 781 """ 782 783 if self.sned: 784 inj_sned = self.execute_sned([inj])[0] 785 786 # FIXME: Add expected SNR 787 # create the web-page and add a table 788 page = markup.page() 789 page.h1("Followup injection #"+str(self.number)) 790 page.add('<table border="2" >') 791 page.add('<caption><b> Injection parameters </b> </caption>') 792 self.fill_table( page, ['<b>parameter','<b>value'] ) 793 self.fill_table( page, ['Number', self.number] ) 794 self.fill_table( page, ['inj ID', self.injection_id] ) 795 self.fill_table( page, ['mass1', '%.2f' % inj.mass1] ) 796 self.fill_table( page, ['mass2', '%.2f' % inj.mass2] ) 797 self.fill_table( page, ['mtotal', '%.2f' % (inj.mass1+inj.mass2)] ) 798 self.fill_table( page, ['mchirp', '%.2f' % (inj.mchirp)] ) 799 self.fill_table( page, ['eta', '%.2f' % (inj.eta)] ) 800 self.fill_table( page, ['spin1z', '%.2f' % (inj.spin1z) ] ) 801 self.fill_table( page, ['spin2z', '%.2f' % (inj.spin2z) ] ) 802 self.fill_table( page, ['end_time', '%010d' % inj.geocent_end_time] ) 803 self.fill_table( page, ['end_time_ns', '%09d' % inj.geocent_end_time_ns] ) 804 self.fill_table( page, ['distance', '%.1f' % inj.distance] ) 805 for ifo_id in ['h','l','v','g']: 806 if self.sned: 807 self.fill_table( page, ['eff_dist_%s' % ifo_id, '%5.1f / %5.1f' % \ 808 (eval("inj.eff_dist_%s" % ifo_id), eval("inj_sned.eff_dist_%s" % ifo_id))] ) 809 else: 810 self.fill_table( page, ['eff_dist_%s' % ifo_id, '%5.1f' % eval("inj.eff_dist_%s" % ifo_id)] ) 811 self.fill_table( page, ['playground','%s' % pipeline.s2play(inj.geocent_end_time)] ) 812 page.add('</table></td>') 813 814 return page
815 816 # --------------------------------------------
817 - def create_table_sngl(self, trig):
818 """ 819 Creates the first table containing basic properties 820 of the trigger which is followed up. 821 @param trig: an sngl_inspiral table 822 """ 823 824 # create the web-page and add a table 825 page = markup.page() 826 page.h1("Followup trigger #"+str(self.number)) 827 page.add('<table border="2" >') 828 self.fill_table(page, ['<b>parameter','<b>value'] ) 829 self.fill_table(page, ['Number', self.number] ) 830 self.fill_table(page, ['inj ID', self.injection_id] ) 831 self.fill_table(page, ['mass1', '%.2f'% trig.mass1] ) 832 self.fill_table(page, ['mass2', '%.2f'% trig.mass2] ) 833 self.fill_table(page, ['mtotal', '%.2f' % (trig.mass1+trig.mass2)] ) 834 self.fill_table(page, ['mchirp', '%.2f' % (trig.mchirp)] ) 835 self.fill_table(page, ['end_time', '%010d' % trig.end_time] ) 836 self.fill_table(page, ['end_time_ns', '%09d' % trig.end_time_ns] ) 837 self.fill_table(page, ['snr', trig.snr]) 838 if trig.chisq_dof: 839 self.fill_table(page, ['rchisq', trig.chisq/(2*trig.chisq_dof-2)]) 840 if trig.bank_chisq_dof: 841 self.fill_table(page, ['bank_rchisq', trig.bank_chisq/trig.bank_chisq_dof]) 842 if trig.cont_chisq_dof: 843 self.fill_table(page, ['auto_rchisq', trig.cont_chisq/trig.cont_chisq_dof]) 844 self.fill_table(page, ['eff_snr (fac=50)', self.get_effective_snr(trig)]) 845 self.fill_table(page, ['new_snr', self.get_new_snr(trig)]) 846 self.fill_table(page, ['eff_distance', '%.1f' % trig.eff_distance] ) 847 page.add('</table></td><br>') 848 page.hr() 849 850 return page
851 852 # --------------------------------------------
853 - def create_table_coinc(self, coinc,snglInspirals=None,page=None, 854 slideDict=None):
855 """ 856 Creates the first table containing basic properties 857 of the coincidence which is followed up. 858 @param coinc: an CoincInspiral table 859 """ 860 861 if slideDict: 862 timeSlide = True 863 else: 864 timeSlide = False 865 866 ## create the web-page and add a table 867 if not page: 868 page = markup.page() 869 page.h1("Followup trigger #"+str(self.number)) 870 page.add('<table border="2">') 871 872 page.add('<caption><b>Coincidence Information</b></caption>') 873 if not snglInspirals: 874 self.fill_table( page, ['Statistic: ', coinc.stat] ) 875 else: 876 self.fill_table( page, ['Combined FAR: ', coinc.combined_far] ) 877 self.fill_table( page, ['Uncombined FAR: ', coinc.false_alarm_rate] ) 878 for i in range(len(snglInspirals)): 879 for j in range(i+1,len(snglInspirals)): 880 sngl1 = snglInspirals[i] 881 sngl2 = snglInspirals[j] 882 ifo1 = sngl1.ifo 883 ifo2 = sngl2.ifo 884 try: 885 ethinca = tools.XLALCalculateEThincaParameter(sngl1,sngl2) 886 except: 887 ethinca = 'Not coincident' 888 Name = 'Ethinca distance between ' + ifo1 + ' and ' + ifo2 889 self.fill_table( page, [Name + ': ', ethinca]) 890 891 page.add('</table><br>') 892 893 page.add('<table border="2" >') 894 page.add('<caption><b>Individual IFO Information</b></caption>') 895 for ifo in ['H1','H2','L1','V1','G1']: 896 trig = None 897 if snglInspirals: 898 for sngl in snglInspirals: 899 if sngl.ifo == ifo: 900 trig = sngl 901 if timeSlide: 902 trig2 = copy.deepcopy(trig) 903 trig2.set_end(trig2.get_end() - slideDict[trig2.ifo]) 904 elif hasattr(coinc,ifo): 905 trig = getattr(coinc,ifo) 906 907 if trig: 908 page.add('<td><table border="2" >') 909 910 self.fill_table( page, ['parameter', ifo], header=True ) 911 self.fill_table( page, ['Number', self.number] ) 912 self.fill_table( page, ['inj ID', self.injection_id] ) 913 self.fill_table( page, ['SNR', '%.3f' % trig.snr] ) 914 self.fill_table( page, ['Effective SNR (fac=50)', '%.3f' % self.get_effective_snr(trig,fac=50.)] ) 915 self.fill_table( page, ['New SNR', '%.3f' % self.get_new_snr(trig,index=6.)] ) 916 if trig.chisq_dof: 917 self.fill_table( page, ['Chisq/dof', '%.3f' % (trig.chisq/(2*trig.chisq_dof-2))] ) 918 if trig.bank_chisq_dof: 919 self.fill_table( page, ['Bank chisq/dof', '%.3f' % (trig.bank_chisq/trig.bank_chisq_dof)] ) 920 if trig.cont_chisq_dof: 921 self.fill_table( page, ['Auto chisq/dof', '%.3f' % (trig.cont_chisq/trig.cont_chisq_dof)] ) 922 self.fill_table( page, ['Rsq duration (s)', '%.4f' % trig.rsqveto_duration] ) 923 self.fill_table( page, ['''Mass1 (M<sub>&#x2A00;</sub>)''', '%.2f' % trig.mass1] ) 924 self.fill_table( page, ['''Mass2 (M<sub>&#x2A00;</sub>)''', '%.2f' % trig.mass2] ) 925 self.fill_table( page, ['''Mtotal (M<sub>&#x2A00;</sub>)''', '%.2f' % (trig.mass1+trig.mass2)] ) 926 self.fill_table( page, ['''Mchirp (M<sub>&#x2A00;</sub>)''', '%.3f' % trig.mchirp] ) 927 self.fill_table( page, ['Template duration (s)', '%.3f' % trig.template_duration ] ) 928 if timeSlide: 929 endTime = trig.end_time + 1E-9*trig.end_time_ns 930 self.fill_table( page, ['Slid GPS end time', '%.4f' % endTime] ) 931 slidEndTime = trig2.end_time + 1E-9*trig2.end_time_ns 932 self.fill_table( page, ['Unslid end time', '%.4f' % slidEndTime] ) 933 else: 934 endTime = trig.end_time + 1E-9*trig.end_time_ns 935 self.fill_table( page, ['GPS end time', '%.3f' % endTime] ) 936 self.fill_table( page, ['Effective distance (Mpc)', '%.1f' % trig.eff_distance] ) 937 page.add('</table></td>') 938 939 page.add('</table><br>') 940 page.hr() 941 942 return page
943 944 # --------------------------------------------
945 - def create_table_time(self, trigger_time):
946 """ 947 Creates the first table containing the time 948 of the followup 949 @param trigger_time: well, just the trigger time 950 """ 951 952 ## create the web-page and add a table 953 page = markup.page() 954 page.h1("Followup time around GPS "+str(trigger_time) ) 955 page.add('<table border="2" >') 956 self.fill_table( page, ['Number', self.number] ) 957 self.fill_table( page, ['Time', trigger_time] ) 958 page.add('</table></td><br>') 959 page.hr() 960 961 return page
962 963 964 # --------------------------------------------
965 - def add_table_followup(self, page, invest_dict):
966 """ 967 Adds the table containing specific information on the loudest 968 trigger found in the followup region for each IFO. 969 @param page: the html page to which to add the table 970 @param invest_dict: dictionary containing the stage results 971 """ 972 973 ## print out the result for this particular injection 974 page.add('<table border="2" >') 975 page.add('<caption><b> Parameters of the loudest (by SNR) recovered single ifo triggers at each stage of the pipeline </b> </caption>') 976 self.fill_table( page, ['step', 'F/M', 'SNR', \ 977 'Mchirp', 'eta', 'eff_dist', \ 978 'rchisq', 'bank_rchisq', 'auto_rchisq', 'eff_snr',\ 979 'new_snr', 'end_time', 'ethinca', 'Veto ON/OFF'], header=True ) 980 981 # loop over the stages and create the table with 982 # the various data in it (when available) 983 for stage in self.orderLabels: 984 if stage in invest_dict: 985 result = invest_dict[stage] 986 987 # Fill in the details of the loudest found coinc. 988 found_ifo = '' 989 loudest_snr = '' 990 loudest_mchirp = '' 991 loudest_eta = '' 992 loudest_eff_dist = '' 993 loudest_rchisq = '' 994 loudest_bank_rchisq = '' 995 loudest_auto_rchisq = '' 996 loudest_effsnr = '' 997 loudest_newsnr = '' 998 loudest_ethinca = ' ' 999 loudest_time = ' ' 1000 veto_onoff = '' 1001 1002 # add all the IFO's for this coincident 1003 result['foundlist'] = list(result['foundset']) 1004 for i in range(len(result['foundlist'])): 1005 ifo = (result['foundlist'])[i] 1006 found_ifo += ifo+' ' 1007 1008 # Parameters of the loudest trigger, taken from the 1009 # 'loudest-details' dictionary, created in 'create_timeseries' 1010 loudest_snr += "%s : %.3f <br>" % \ 1011 (ifo, result['loudest_details'][ifo]['snr']) 1012 loudest_mchirp += "%s : %.3f <br>" % \ 1013 (ifo, result['loudest_details'][ifo]['mchirp']) 1014 loudest_eta += "%s : %.3f <br>" % \ 1015 (ifo, result['loudest_details'][ifo]['eta']) 1016 loudest_eff_dist += "%s : %.3f <br>" % \ 1017 (ifo, result['loudest_details'][ifo]['eff_dist']) 1018 loudest_rchisq += "%s : %.3f <br>" % \ 1019 (ifo, result['loudest_details'][ifo]['rchisq']) 1020 loudest_bank_rchisq += "%s : %.3f <br>" % \ 1021 (ifo, result['loudest_details'][ifo]['bank_rchisq']) 1022 loudest_auto_rchisq += "%s : %.3f <br>" % \ 1023 (ifo, result['loudest_details'][ifo]['auto_rchisq']) 1024 loudest_effsnr += "%s : %.3f <br>" % \ 1025 (ifo, result['loudest_details'][ifo]['eff_snr']) 1026 loudest_newsnr += "%s : %.3f <br>" % \ 1027 (ifo, result['loudest_details'][ifo]['new_snr']) 1028 loudest_time += "%s : %.3f <br>" % \ 1029 (ifo, result['loudest_details'][ifo]['end_time']) 1030 for j in range(i+1,len(result['foundlist'])): 1031 ifo2 = (result['foundlist'])[j] 1032 try: 1033 ethinca = tools.XLALCalculateEThincaParameter( 1034 result['loudest_details'][ifo]['trig'], 1035 result['loudest_details'][ifo2]['trig']) 1036 loudest_ethinca += "%s and %s: %.3f <br>" % \ 1037 (ifo,ifo2,ethinca) 1038 except: 1039 loudest_ethinca += "%s and %s: %s <br>" % \ 1040 (ifo,ifo2,'Not coincident') 1041 1042 # Check whether some of the ifo times is vetoed 1043 time_trigger = float(result['loudest_details'][ifo]['timeTrigger']) 1044 if self.vetodict[ifo]: 1045 veto = self.is_veto(time_trigger, ifo) 1046 veto_txt = 'OFF' 1047 if veto: 1048 veto_txt = 'ON' 1049 veto_onoff+=ifo+': '+veto_txt+'<br>' 1050 else: 1051 veto_onoff+=ifo+': No info<br>' 1052 1053 # Fill the table whether something is found or not 1054 if len(result['foundset'])>0: 1055 self.fill_table( page, [ stage, 'FOUND in <br>'+found_ifo, \ 1056 loudest_snr, \ 1057 loudest_mchirp, \ 1058 loudest_eta, \ 1059 loudest_eff_dist,\ 1060 loudest_rchisq, \ 1061 loudest_bank_rchisq, \ 1062 loudest_auto_rchisq, \ 1063 loudest_effsnr, \ 1064 loudest_newsnr, \ 1065 loudest_time, \ 1066 loudest_ethinca, \ 1067 veto_onoff],no_wrapping=True) 1068 else: 1069 self.fill_table( page, [ stage, '<font color="red">MISSED']) 1070 1071 page.add('</table>') 1072 page.add('</td></tr></table><br><br>') 1073 1074 return page
1075 1076 1077 # -----------------------------------------------------
1078 - def from_coinc(self, coinc, ifo = None, more_infos = False, \ 1079 injection_id = None,slideDict=None):
1080 """ 1081 Creates a followup page from a coincident trigger. 1082 @param coinc: the coincidence to be followed up 1083 @param ifo: specifies the ifo to be used from the coinc. 1084 @param more_infos: to have some additional informations 1085 @param injection_id: Must be specified for exttrig search 1086 to specify what injection to use 1087 """ 1088 1089 if not ifo: 1090 print "WARNING (not bad): No IFO specified, using data from the "\ 1091 "first IFO in the coincidence " 1092 for ifo_name in self.colors.keys(): 1093 if hasattr(coinc, ifo_name): 1094 ifo = ifo_name 1095 break 1096 sngl = getattr(coinc, ifo) 1097 1098 # set the time 1099 self.followup_time = float(sngl.get_end()) 1100 1101 # prepare the page 1102 self.injection_id = injection_id 1103 page = self.create_table_coinc(coinc,slideDict=slideDict) 1104 self.flag_followup = more_infos 1105 1106 # do the followup 1107 return self.followup(page,slideDict=slideDict)
1108 1109 # -----------------------------------------------------
1110 - def from_new_coinc(self, coinc, sngls,\ 1111 more_infos = False, injection_id = None):
1112 """ 1113 Creates a followup page from a coincident trigger. 1114 @param coinc: the coincidence to be followed up 1115 @param ifo: specifies the ifo to be used from the coinc. 1116 @param more_infos: to have some additional informations 1117 @param injection_id: Must be specified for exttrig search 1118 to specify what injection to use 1119 """ 1120 1121 sngl = sngls[0] 1122 1123 # set the time 1124 self.followup_time = float(sngl.get_end()) 1125 1126 # prepare the page 1127 self.injection_id = injection_id 1128 page = self.create_table_coinc(coinc,snglInspirals= sngls) 1129 self.flag_followup = more_infos 1130 1131 # do the followup 1132 return self.followup(page)
1133 1134 # -----------------------------------------------------
1135 - def from_new_slide_coinc(self, coinc, sngls,slideDict,\ 1136 more_infos = False, injection_id = None):
1137 """ 1138 Creates a followup page from a slid coincident trigger. This function 1139 does not yet produce the plots (as I'm not sure how to!) but the 1140 relevant information to do this (the slide dictionary and the segment list) 1141 are provided to this function. 1142 @param coinc: the coincidence to be followed up 1143 @param ifo: specifies the ifo to be used from the coinc. 1144 @param more_infos: to have some additional informations 1145 @param injection_id: Must be specified for exttrig search 1146 to specify what injection to use 1147 """ 1148 1149 sngl = sngls[0] 1150 1151 # set the time 1152 self.followup_time = float(sngl.get_end()) 1153 1154 # prepare the page 1155 self.injection_id = injection_id 1156 page = self.create_table_coinc(coinc,snglInspirals= sngls,\ 1157 slideDict=slideDict) 1158 self.flag_followup = more_infos 1159 1160 return self.followup(page,slideDict=slideDict)
1161 1162 # -----------------------------------------------------
1163 - def from_sngl(self, sngl, ifo = None, more_infos = False, \ 1164 injection_id = None):
1165 """ 1166 Creates a followup page from a single trigger. 1167 @param sngl: the sngl trigger to be followed up 1168 @param ifo: NOT USED 1169 @param more_infos: to have some additional informations 1170 @param injection_id: Must be specified for exttrig search 1171 to specify what injection to use 1172 """ 1173 1174 # prepare the page 1175 self.injection_id = injection_id 1176 page = self.create_table_sngl(sngl) 1177 self.flag_followup = more_infos 1178 1179 # set the time 1180 self.followup_time = float(sngl.get_end()) 1181 1182 # do the followup 1183 return self.followup(page)
1184 1185 # -----------------------------------------------------
1186 - def from_missed(self, missed, ifo = None, more_infos = True, \ 1187 injection_id = None):
1188 """ 1189 Creates a followup page from a missed injection. 1190 @param sngl: the missed injection to be followed up 1191 @param ifo: The ifo whose time is used (geocent if None) 1192 @param more_infos: to have some additional informations 1193 @param injection_id: Must be specified for exttrig search 1194 to specify what injection to use 1195 """ 1196 1197 return self.from_injection(missed, ifo = ifo, more_infos = more_infos,\ 1198 injection_id = injection_id )
1199 1200 # -----------------------------------------------------
1201 - def from_found(self, found, ifo = None, more_infos = False, \ 1202 injection_id = None,coinc = None, sngls = None):
1203 """ 1204 Creates a followup page from a found injection. 1205 @param sngl: the found injection to be followed up 1206 @param ifo: The ifo whose time is used (geocent if None) 1207 @param more_infos: to have some additional informations 1208 @param injection_id: Must be specified for exttrig search 1209 to specify what injection to use 1210 """ 1211 1212 return self.from_injection(found, ifo = ifo, more_infos = more_infos, \ 1213 injection_id = injection_id,coinc=coinc, \ 1214 sngls = sngls )
1215 1216 # -----------------------------------------------------
1217 - def from_injection(self, injection, ifo = None, more_infos = True, \ 1218 injection_id = None,coinc = None, sngls = None):
1219 """ 1220 Creates a followup page from an injection. 1221 @param injection: the injection to be followed up 1222 @param ifo: The ifo whose time is used (geocent if None) 1223 @param more_infos: to have some additional informations 1224 @param injection_id: Must be specified for exttrig search 1225 to specify what injection to use 1226 """ 1227 1228 # Set the injection ID if required 1229 if injection_id: 1230 self.injection_id = injection_id 1231 else: 1232 self.injection_id = self.find_injection_id(injection) 1233 1234 # prepare the page 1235 page = self.create_table_inj(injection) 1236 1237 if coinc and sngls: 1238 page = self.create_table_coinc(coinc,snglInspirals= sngls,page=page) 1239 1240 self.flag_followup = more_infos 1241 1242 # set the time and do the followup 1243 self.followup_time = self.get_sim_time(injection, ifo) 1244 1245 # do the followup 1246 return self.followup(page)
1247 1248 1249 # -----------------------------------------------------
1250 - def from_time(self, trigger_time, ifo = None, more_infos = False, \ 1251 injection_id = None):
1252 """ 1253 Creates a followup page from a given time. 1254 @param trigger_time: the time to be followed up 1255 @param ifo: NOT USED 1256 @param injection_id: Must be specified for exttrig search 1257 to specify what injection to use 1258 """ 1259 1260 self.flag_followup = more_infos 1261 1262 # prepare the page 1263 page = self.create_table_time(trigger_time) 1264 1265 # set the time 1266 self.followup_time = trigger_time 1267 self.injection_id = injection_id 1268 1269 # do the followup 1270 return self.followup(page)
1271 1272 1273 # -----------------------------------------------------
1274 - def followup(self, page,slideDict = None):
1275 """ 1276 Central followup procedure, finding corresponding files, 1277 generating the time-series and creating the output html files 1278 @param page: The head of the html page created with different informations 1279 @param slideDict: A dictionary of ifo keyed slide times if using slides 1280 @return: filename of the created html 1281 """ 1282 1283 # increase internal number: 1284 self.number+=1 1285 page.add('<br>') 1286 1287 # loop over each stage 1288 invest_dict = {} 1289 for stage, cache in self.trigger_cache.iteritems(): 1290 1291 # loop over each file in a stage 1292 trig_cache = lal.Cache() 1293 for c in cache: 1294 1295 # check the time and the injection ID 1296 # Also pick up files +/- 2048s of this trigger to avoid boundary issues 1297 if (self.followup_time in c.segment) or ((self.followup_time-2048) in c.segment) or ((self.followup_time+2048) in c.segment): 1298 if not self.injection_id or \ 1299 (self.injection_id and \ 1300 self.check_injection_id(c, self.injection_id)): 1301 trig_cache.append( c ) 1302 1303 # check if the pfnlist is empty. ` 1304 file_list = trig_cache.pfnlist() 1305 if len(file_list)==0: 1306 print >>sys.stderr, "ERROR: No files found for stage %s in the "\ 1307 "cache for ID %s and time %d; probably mismatch of a "\ 1308 "pattern in the options. " % \ 1309 ( stage, self.injection_id, self.followup_time) 1310 continue 1311 1312 # call the function to create the timeseries 1313 if stage in ('COINCIDENCE','COINCIDENCE-SLID'): 1314 # ... need to loop over the four categories 1315 for cat in [1,2,3,4,5]: 1316 select_list=self.select_category(file_list, cat) 1317 if len(select_list)==0: 1318 print "WARNING (not that bad): "\ 1319 "No THINCA files found for category ", cat 1320 continue 1321 modstage = stage+'_CAT_' + str(cat) 1322 invest_dict[modstage] = self.create_timeseries(select_list,modstage,\ 1323 self.number,slideDict) 1324 else: 1325 invest_dict[stage]=self.create_timeseries(file_list, stage, \ 1326 self.number,slideDict) 1327 1328 1329 ## add some more followup if required 1330 if self.flag_followup: 1331 self.add_table_followup(page, invest_dict) 1332 1333 ## add the pictures to the webpage 1334 for stage in self.orderLabels: 1335 if stage in invest_dict: 1336 result = invest_dict[stage] 1337 1338 fname = result['filename'] 1339 page.a(extra.img(src=[fname], width=400, \ 1340 alt=fname, border="2"), title=fname, href=[fname]) 1341 1342 1343 ## add the version of this code 1344 page.add("<hr>") 1345 page.add("Figure(s) and data produced with " + __prog__ + ", version " \ 1346 + git_version.verbose_msg) 1347 1348 # and write the html file 1349 htmlfilename = self.opts.prefix + "_followup_"+str(self.number) +\ 1350 self.opts.suffix+'.html' 1351 file = open(self.opts.output_path+htmlfilename,'w') 1352 file.write(page(False)) 1353 file.close() 1354 1355 # store html file in fname_list and return filename 1356 self.fname_list.append(htmlfilename) 1357 return htmlfilename
1358 1359 # -----------------------------------------------------
1360 - def find_unvetoed_ifos(self, inj, ifo_list):
1361 """ 1362 Find the list of unvetoed injections for this particular 1363 injection (missed or found) 1364 @param inj: the injection (SimInspiral table) 1365 @param ifo_list: the ifo list 1366 """ 1367 1368 unvetoed_ifos = [] 1369 for ifo in ifo_list: 1370 veto_time = float(getattr(inj, ifo[0].lower()+'_end_time')) 1371 if not self.is_veto(veto_time, ifo): 1372 unvetoed_ifos.append(ifo) 1373 1374 return unvetoed_ifos
1375 1376 # -----------------------------------------------------
1377 - def get_relevant_distance(self, inj, ifo_list):
1378 """ 1379 Find the relevant distance for this injection, 1380 given the list of IFO's 1381 """ 1382 1383 # take the second largest effective distance 1384 list_dist = [] 1385 for ifo in ifo_list: 1386 list_dist.append(getattr(inj, 'eff_dist_'+ifo[0].lower())) 1387 list_dist.sort() 1388 1389 return list_dist[1]
1390 1391 # -----------------------------------------------------
1392 - def populate_followup_list(self, inj, ifo_list):
1393 """ 1394 Find the relevant effective distance for this injection 1395 including any possible vetoes. 1396 """ 1397 1398 # retreive list of unvetoed IFOs 1399 unvetoed_ifos = self.find_unvetoed_ifos(inj, ifo_list) 1400 1401 if len(unvetoed_ifos)>=2: 1402 # if there are 2 or more unvetoed IFO's left 1403 # a coincidence is possible. Need to followup this case 1404 1405 # find the type (for plotting issues) 1406 type = 'ks' 1407 if len(unvetoed_ifos) == len(ifo_list): 1408 # no veto for any ifo 1409 type = 'ro' 1410 1411 # get the relevant distance 1412 rel_dist = self.get_relevant_distance(inj, unvetoed_ifos) 1413 1414 # append to the list of potential injections to be followed up 1415 self.followup_dict['inj'].append(inj) 1416 self.followup_dict['dist'].append(rel_dist) 1417 self.followup_dict['type'].append(type) 1418 1419 else: 1420 self.vetoed_injections.append(inj)
1421 1422 # -----------------------------------------------------
1423 - def followup_missed_injections(self, opts, missed_injections, \ 1424 found_injections, \ 1425 xvalue = 'mtotal', xlog = 'linear', \ 1426 ylog = 'log'):
1427 """ 1428 This function will do the complete followup on each relevant 1429 missed injection. 1430 """ 1431 1432 1433 # FIXME: put this dict into a basic class 1434 foundSymbol = {'H1H2':'x', 'H1L1':'^', 'H2L1':'+', 'H1V1':'v', \ 1435 'H2V1':'<','L1V1':'>', 'H1H2L1':'s', 'H1H2V1':'D',\ 1436 'H2L1V1':'d', 'H1L1V1':'1',\ 1437 'H1H2L1V1':'p', 'H1':'x', 'H2':'x', 'L1':'x', 'V1':'x'} 1438 1439 # populate the injections to be followed up 1440 ifo_list = get_ifo_list(opts.ifo_times) 1441 for inj in missed_injections: 1442 self.populate_followup_list(inj, ifo_list) 1443 1444 # select the first N injections to followup 1445 if opts.followup_number: 1446 index = numpy.argsort(self.followup_dict['dist']) 1447 number_to_followup = min(len(index), opts.followup_number) 1448 else: 1449 raise NotImplementedError,"NotImplemented. You only can specify the "\ 1450 "number of injections to be followed up. " 1451 1452 # Handle the found injections. Distinguish them by the 1453 # IFO's they were found with 1454 found_dist = {} 1455 found_masses = {} 1456 for type in found_injections.keys(): 1457 temp_dist = None 1458 1459 # create a list of IFO's from the one-word-list 1460 sub_ifo_list = get_ifo_list(type) 1461 1462 # get the distance and the masses 1463 found_dist[type] = [self.get_relevant_distance(inj, sub_ifo_list)\ 1464 for inj in found_injections[type]] 1465 1466 found_masses[type], legend = get_data_from_table( \ 1467 found_injections[type], xvalue) 1468 1469 ## create the main image 1470 pylab.clf() 1471 1472 # plot the found injections 1473 for type in found_injections.keys(): 1474 1475 # create a list of IFO's from the one-word-list 1476 sub_ifo_list = get_ifo_list(type) 1477 1478 # Color the markers blue iff this inj is found in all 1479 # possible detectors 1480 col = 'm' 1481 if len(sub_ifo_list) == len(ifo_list): 1482 col = 'b' 1483 1484 # FIXME: foundSymbol not defined 1485 pylab.plot( found_masses[type], found_dist[type], col+foundSymbol[type], \ 1486 markerfacecolor='None',markeredgecolor=col, \ 1487 label = type, markersize=10, markeredgewidth=1) 1488 1489 # don't forget to plot the real vetoed ones 1490 dist = [self.get_relevant_distance(inj, ifo_list)\ 1491 for inj in self.vetoed_injections] 1492 masses = viz.readcol(self.vetoed_injections,"mass1")+\ 1493 viz.readcol(self.vetoed_injections,"mass2") 1494 pylab.plot( masses, dist, 'ks', \ 1495 markerfacecolor='None',label='vetoed',\ 1496 markeredgecolor='k',markersize=10, markeredgewidth=1) 1497 1498 # plot the missed ones 1499 missed_masses, legend = get_data_from_table( self.followup_dict['inj'], xvalue) 1500 1501 pylab.plot( missed_masses, self.followup_dict['dist'], 'ro', \ 1502 markerfacecolor='None',label='(prop) missed',\ 1503 markeredgecolor='r',markersize=10, markeredgewidth=1) 1504 1505 1506 legy = 'Second smallest eff. distance [Mpc]' 1507 title_text = legend+' vs '+legy 1508 pylab.title( opts.title + ' '+title_text+' in '+opts.ifo_times+\ 1509 ' times', size='x-large') 1510 pylab.xlabel(legend, size='x-large') 1511 pylab.ylabel(legy, size='x-large') 1512 pylab.grid(True) 1513 pylab.legend() 1514 1515 # create a mapDist 1516 mapDict ={'object':'', \ 1517 'text':'Click on a filled circle to go to the followup page',\ 1518 'xCoords':[], 'yCoords':[], 'links': []} 1519 1520 # coordinates of the actual picture in the image 1521 # FIXME: These numbers might change for different solutions, 1522 # and image types. 1523 boundFigX = [100.0,720.0] 1524 boundFigY = [540.0, 60.0] 1525 1526 # choose lin/log for the axes 1527 pylab.gca().set_xscale(xlog) 1528 pylab.gca().set_yscale(ylog) 1529 1530 # limits on the axes for the image 1531 axes=pylab.gcf().axes[0] 1532 rangeFigX = axes.get_xlim() 1533 rangeFigY = numpy.log10( axes.get_ylim() ) 1534 1535 # calculating scaling factors 1536 slopeX = (boundFigX[1]-boundFigX[0])/(rangeFigX[1]-rangeFigX[0]) 1537 interX = boundFigX[1] - slopeX*rangeFigX[1] 1538 slopeY = (boundFigY[1]-boundFigY[0])/(rangeFigY[1]-rangeFigY[0]) 1539 interY = boundFigY[1] - slopeY*rangeFigY[1] 1540 1541 1542 # loop over all missed injections 1543 for ind in index[:number_to_followup]: 1544 1545 # get the relevant data for this injection to be followed up 1546 inj = self.followup_dict['inj'][ind] 1547 type = self.followup_dict['type'][ind] 1548 eff_dist = self.followup_dict['dist'][ind] 1549 eff_dist_log = numpy.log10(eff_dist) 1550 total_mass = inj.mass1+inj.mass2 1551 1552 1553 # get the coordinates of this missed injection 1554 px = int(interX+slopeX*total_mass) 1555 py = int(interY+slopeY*eff_dist_log) 1556 1557 # highlight the clickable missed ones; depending if it is 1558 # a real missed one or a possible missed one (with vetoes) 1559 pylab.plot( [total_mass], [eff_dist], type, markerfacecolor = type[0], \ 1560 markersize=10) 1561 1562 # do the followup and create the followup page 1563 followuphtml = self.from_missed(inj) 1564 1565 # add the point and the link to the mapDict 1566 mapDict['xCoords'].append(px) 1567 mapDict['yCoords'].append(py) 1568 mapDict['links'].append(followuphtml) 1569 1570 return mapDict
1571 1572 #######################################################################
1573 -def get_data_from_table(table, col_name, ifo = None):
1574 """ 1575 Retrieves data from a table, including non-table entries 1576 such as 'mtotal' and 'time' 1577 @param table: the table containing the data 1578 @param col_name: the name of the column 1579 @param ifo: the ifo for which the column is returned 1580 """ 1581 1582 if col_name=='time': 1583 data = [t for t in viz.timeindays(viz.readcol(table,"end_time", ifo))] 1584 legend = "End time (in days)" 1585 elif col_name == 'mtotal': 1586 data = viz.readcol(table,"mass1")+viz.readcol( table,"mass2") 1587 legend = "Total mass" 1588 else: 1589 data = viz.readcol(table, col_name) 1590 legend = xname 1591 1592 return data, legend
1593 1594 #######################################################################
1595 -def getData( table, xname, yname, ifo ):
1596 """ 1597 Retrieves data from a table, including non-table entries 1598 such as 'mtotal' and 'time' 1599 @param table: the table with the data 1600 @param xname: the x-value 1601 @param yname: the y-value 1602 @param ifo : the ifo for which the time or distance is returned 1603 """ 1604 1605 if xname=='time': 1606 xp= [ t-opts.time_offset \ 1607 for t in viz.timeindays(viz.readcol( table,"end_time", ifo)) ] 1608 legx = "End time (in days)" 1609 elif xname == 'mtotal': 1610 xp = viz.readcol( table,"mass1")+viz.readcol( table,"mass2") 1611 legx = "Total mass" 1612 else: 1613 xp = viz.readcol( table,xname) 1614 legx = xname 1615 1616 1617 if yname == 'eff_dist': 1618 yp = viz.readcol( table,"eff_dist", ifo ) 1619 legy = "Effective distance" 1620 else: 1621 yp = viz.readcol( table,yname) 1622 legy = yname 1623 1624 return xp, yp, legx, legy
1625 1626 ####################################################
1627 -def get_ifo_list(ifo_times):
1628 ifo_list = [] 1629 for i in range(len(ifo_times)/2): 1630 ifo_list.append(ifo_times[2*i:2*i+2]) 1631 return ifo_list
1632