1 """
2 Utilities for the inspiral plotting functions
3 """
4
5 from glue import lal
6 from glue import segments
7 import socket, os
8 import sys
9 import copy
10 import math
11
12 from glue.ligolw import ligolw
13 from glue.ligolw import table
14 from glue.ligolw import lsctables
15 from glue.ligolw import utils
16 from pylal import SnglInspiralUtils
17 from pylal import CoincInspiralUtils
18 from pylal import git_version
19 from glue import markup
20 from glue.markup import oneliner as extra_oneliner
21
22
23 colors = {'G1':'k','H1':'r','H2':'b','L1':'g','V1':'m'}
24 symbols = {'G1':'Y','H1':'x','H2':'o','L1':'+','V1':'1'}
25
26
28 """
29 Given an ifo set, returns an html color code for plotting.
30 """
31
32 if not ( isinstance(ifo_set, set) or isinstance(ifo_set, frozenset) ):
33 raise ValueError("ifo_set must be of type set or frozenset. "
34 "Use lsctables.instrument_set_from_ifos to do this.")
35
36 if ifo_set == set(['H1', 'H2', 'L1', 'V1']):
37 return '#F88017'
38 elif ifo_set == set(['H1', 'H2', 'L1']):
39 return '#00FFFF'
40 elif ifo_set == set(['H1', 'L1', 'V1']):
41 return '#7D1B7E'
42 elif ifo_set == set(['H2', 'L1', 'V1']):
43 return '#153E7E'
44 elif ifo_set == set(['H1', 'L1']):
45 return '#00FF00'
46 elif ifo_set == set(['H1', 'V1']):
47 return '#6698FF'
48 elif ifo_set == set(['H2', 'L1']):
49 return '#FF0000'
50 elif ifo_set == set(['H2', 'V1']):
51 return '#FF00FF'
52 elif ifo_set == set(['L1', 'V1']):
53 return '#254117'
54 else:
55 return 'k'
56
57
58
59 -class InspiralPage(object):
60 """
61 This is a class to contain all the bits of a inspiral page
62 showing the results of a piece of code.
63 """
64
65
66
67 - def __init__(self, options, ifo_times=None, ifo_tag=None, user_tag=None,
68 gps_start_time=None, gps_end_time=None):
69 """
70 Initializes this class with the options.
71 @params options: option object from the calling function
72 """
73 self.opts = options
74
75
76 self.version = git_version.verbose_msg.replace('\n','<br>')
77 self.name = os.path.basename(sys.argv[0])
78 self.arguments = sys.argv[1:]
79
80
81 self.initialize()
82
83
84 if ifo_times: self.ifo_times = ifo_times
85 if ifo_tag: self.ifo_tag = ifo_tag
86 if user_tag: self.user_tag = user_tag
87 if gps_start_time is not None: self.gps_start_time = gps_start_time
88 if gps_end_time is not None: self.gps_end_time = gps_end_time
89
90
91 self.create_affixes()
92
93
94 self.fname_list = []
95 self.tag_list = []
96 self.html_footer = ""
97
98
99
100 - def initialize(self):
101 """
102 Extract information from the option structure.
103 Does NOT alter the information in the option structure
104 """
105
106 if hasattr(self.opts, 'ifo_times'):
107 self.ifo_times = self.opts.ifo_times
108 else:
109 self.ifo_times = None
110
111 if hasattr(self.opts, 'ifo_tag'):
112 self.ifo_tag = self.opts.ifo_tag
113 else:
114 self.ifo_tag = None
115
116 if hasattr(self.opts, 'user_tag'):
117 self.user_tag = self.opts.user_tag
118 else:
119 self.user_tag = None
120
121 if hasattr(self.opts, 'gps_start_time'):
122 self.time_string = str(int(self.opts.gps_start_time))+"-" + \
123 str(int(math.ceil(self.opts.gps_end_time))-int(self.opts.gps_start_time))
124 else:
125 self.time_string = "unspecified-gpstime"
126
127 if hasattr(self.opts,'output_path'):
128 self.output_path = self.opts.output_path
129 if not self.output_path.endswith('/'):
130 self.output_path += '/'
131 else:
132 self.output_path = './'
133
134
135
136
137 - def create_affixes(self):
138 """
139 Create the affixes (prefix/suffix) for the image naming
140 """
141 self.prefix = self.name
142
143 if self.ifo_times:
144 self.prefix = self.ifo_times + "-" + self.prefix
145 if self.ifo_tag:
146 self.prefix = self.prefix + "_" + self.ifo_tag
147 if self.user_tag:
148 self.prefix = self.prefix + "_" + self.user_tag
149
150 self.suffix = "-"+self.time_string
151
152
153
154 - def add_plot(self, plot_fig, fig_description, output_dir = None):
155 """
156 Add a plot to the page.
157 @param plot_fig: handle of the figure
158 @param fig_description: descriptive figure text for the filename
159 @param output_dir: alternate output directory [optional]
160 """
161
162 fname = "Images/" + self.prefix + "_"+ fig_description + self.suffix
163
164 if output_dir:
165 fname = output_dir + '/' + fname
166 else:
167 fname = self.output_path + fname
168
169 filename, filename_thumb = self.savefig(fname, plot_fig)
170
171 self.fname_list.append(filename)
172 self.tag_list.append(filename)
173
174
175 - def savefig(self, filename_base, fig, doThumb=True, dpi = None, dpi_thumb=50):
176 """
177 Function to create the image file.
178 @param filename_base: basename of the filename (without the .png ending)
179 @param fig: handle to the figure to save
180 @param doThumb: save the thumbnail or not (doThumb=True by default)
181 @param dpi: resolution of the figure
182 @param dpi_thumb: resolution of the thumbnail (dpi=50 by default)
183 """
184
185 savefig_kwargs = {}
186 if dpi is not None:
187 savefig_kwargs["dpi"] = dpi
188
189
190 filename = filename_base + '.png'
191 fig.savefig(filename, **savefig_kwargs)
192
193 if doThumb:
194 filename_thumb = filename_base + '_thumb.png'
195 fig.savefig(filename_thumb, dpi=dpi_thumb)
196 else:
197 filename_thumb = None
198
199 return filename, filename_thumb
200
201
202 - def write_page(self, infix = None, doThumb = True,
203 map_list = [], coinc_summ_table = None ):
204 """
205 Create the pages if output is enabled
206 """
207 if self.opts.enable_output:
208 html_filename = self.create_htmlname(infix)
209 self.write_html_output(html_filename, doThumb = doThumb,
210 map_list = map_list, coinc_summ_table = coinc_summ_table,
211 comment=self.html_footer or None)
212 self.write_cache_output(html_filename)
213 return html_filename
214
215
216 - def write(self, text):
217 """
218 Write some text to the standard output AND
219 to the page.
220 """
221 print text
222 self.html_footer+=text+'<br>'
223
224
225 - def create_htmlname(self, infix):
226 """
227 Create the html filename
228 """
229
230
231 if infix:
232 html_filename = self.prefix + '_' + infix + self.suffix
233 else:
234 html_filename = self.prefix + self.suffix
235
236 html_filename += ".html"
237
238 if self.output_path:
239 html_filename = self.output_path + html_filename
240
241 return html_filename
242
243
244 - def write_html_output(self, html_filename, doThumb=True, map_list=[],
245 comment=None, coinc_summ_table=None ):
246 """
247 @param doThumb: Uses the thumbnail file as the sourcs for the images
248 @param map_list: A list of dictionaries to create the image maps
249 @param comment: A comment that can be added to the page
250 @param coinc_summ_table: A CoincSummTable that can be added to the page
251 """
252
253
254 page = markup.page()
255 try:
256 page.init(title=__title__)
257 except:
258 page.init()
259
260 page.h1(self.name + " results")
261
262 page.p(self.prefix + self.suffix)
263 page.hr()
264
265
266 html_file = file(html_filename, "w")
267
268
269 for tag,filename in zip(self.tag_list, self.fname_list):
270
271
272 fname = "Images/" + os.path.basename(filename)
273
274
275 if doThumb:
276 fname_thumb = fname[:-4] + "_thumb.png"
277 else:
278 fname_thumb = fname
279
280
281 page.a(extra_oneliner.img(src=[fname_thumb], width=400,
282 alt=tag, border="2"), title=tag, href=[ fname])
283
284 page.add("<hr/>")
285
286
287 m=0
288 for map_dict in map_list:
289 m+=1
290 page.add( map_dict['text']+'<br>' )
291 page.add( '<IMG src="%s" width=800px '
292 'usemap="#map%d">' % ( map_dict['object'], m) )
293 page.add( '<MAP name="map%d"> <P>' % m )
294 n=0
295 for px, py, link in zip( map_dict['xCoords'],
296 map_dict['yCoords'],
297 map_dict['links'] ):
298 n+=1
299 page.add( '<area href="%s" shape="circle" '
300 'coords="%d, %d, 5"> Point%d</a>' %
301 ( link, px, py, n) )
302 page.add('</P></MAP></OBJECT><br>')
303 page.add("<hr/>")
304
305
306 if comment:
307 page.add("<div> "+comment+"</div>")
308 page.hr()
309
310 if coinc_summ_table:
311 page.add(coinc_summ_table)
312 page.hr()
313
314 text = self.write_process_params()
315 page.add(text)
316 html_file.write(page(False))
317 html_file.close()
318
319
320
321 - def write_cache_output(self, html_filename):
322 """
323 Write the output cache file of the plotting functions.
324 @param: html_filename: the name of the html file
325 """
326
327 output_cache_name = self.prefix + self.suffix +'.cache'
328 if self.output_path:
329 output_cache_name = self.output_path + output_cache_name
330
331
332 cachefile = open(output_cache_name, 'w')
333 cachefile.write(os.path.basename(html_filename) + '\n')
334
335
336 for filename in self.fname_list:
337 if filename.endswith('.png'):
338 fname = "Images/"+os.path.basename(filename)
339 elif filename.endswith('.html'):
340 fname = os.path.basename(str(filename))
341
342
343 cachefile.write(fname + '\n')
344
345
346 cachefile.close()
347
348
350 """
351 Returns the version and the full command run
352 """
353 text = "Figure(s) produced with '" + self.name + "' with version: <br>" \
354 + self.version \
355 + '<br>\n<p style="width:80%; color:blue">' + self.name
356 for arg in self.arguments:
357 text += " " + arg
358 text += '</p>'
359
360 return text
361
362 -def savefig_pylal(filename=None, filename_thumb=None, doThumb=True, dpi=None,
363 dpi_thumb=50, fig=None):
364 """
365 @param filename: filename in which to save the figure
366 @param filename_thumb: filename into which save a thumbnail of the figure
367 @param doThumb: save the thumbnail or not (doThumb=True by default)
368 @param dpi: resolution of the figure
369 @param dpi_thumb: resolution of the thumbnail (dpi=50 by default)
370 @param fig: the particular figure you wish to save (current figure by
371 default)
372 @return filename_thumb if a thumbnail was created (computed from filename
373 by default)
374
375 """
376 import pylab
377
378
379 if fig is None:
380 fig = pylab.gcf()
381 if dpi is None:
382 dpi = pylab.rcParams["savefig.dpi"]
383 if doThumb and (filename_thumb is None):
384 if filename is None:
385 raise ValueError("must provide filename_thumb or filename if doThumb "
386 "is True")
387 index = filename.rindex('.')
388 filename_thumb = filename[0:index] + '_thumb' + filename[index:]
389
390
391 if filename is not None:
392 fig.savefig(filename, dpi=dpi)
393
394
395 if doThumb:
396 fig.savefig(filename_thumb, dpi=dpi_thumb)
397
398 return filename_thumb
399
400
402 """
403
404 """
405 text = "---Error in "+opts.name+"in plotting functions "+thisplot
406 if "chi" in thisplot:
407 text += "\n---possible reasons related to chi-square (are you reading first stage triggers ?)"
408 print >> sys.stderr, text
409
410
412 """
413
414 """
415 if opts.verbose:
416 print text
417 return text+'<br>\n'
418
440
452
453 -def write_coinc_summ_table(tableList = [], commentList = [], stat=None, statTag=None, number=None, format=None,followup = None, followupOpts = None):
454 """
455 picks out loudest coincident triggers from given CoincInspiralUtils Tables
456 and returns info about the coincidences in a html or wiki table
457
458 @param tableList: a list of CoincInspiralUtils.coincInspiralTables
459 @param commentList: comments about each table (e.g., file name)
460 @param stat: any CoincInspiralUtils.coincStatistic
461 @param statTag: string specifying what stat used
462 @param number: number of triggers to list
463 @param format: desired output format; can be either 'html' or 'wiki'
464 """
465
466 if format == 'html':
467 tx = '<table border = "1">'
468 xt = '</table>'
469 thx = '<tr><td colspan=14>'
470 rx = '<tr><td>'
471 xr = '</td></tr>'
472 xccx = '</td><td>'
473 elif format == 'wiki':
474 tx = ''
475 xt = ''
476 thx = '||<-14>'
477 rx = '||'
478 xr = '||\n'
479 xccx = '||'
480 else:
481 raise ValueError, 'unrecognized format; must be either html or wiki'
482
483
484 if statTag is None: statTag = stat.name
485
486 CoincSummTable = ''
487
488
489 for coincTable, coincComment in zip(tableList,commentList):
490 if stat.name == 'far':
491 coincTable.sort(descending=False)
492 else:
493 coincTable.sort()
494 rank = 1
495
496 CoincSummTable = CoincSummTable + tx + thx + coincComment + xr
497 CoincSummTable = CoincSummTable + \
498 rx + ' Rank ' + xccx + ' followup ' + xccx + 'Coinc IFOs' + xccx + \
499 statTag + xccx + 'False Alarm Probability' + xccx + ' end_time ' + \
500 xccx + ' end_time_ns ' + xccx + ' mass1 ' + xccx + ' mass2 ' + xccx + ' mchirp ' + \
501 xccx + ' eta ' + xccx + ' snr ' + xccx + ' chisq ' + xccx + ' effective_snr ' + xr
502 for coinc in coincTable:
503 if format == 'html':
504 CoincSummTable = CoincSummTable + '<tr><td rowspan=' + str(coinc.numifos) + '>' + str(rank) + '</td>'
505 elif format == 'wiki':
506 CoincSummTable = CoincSummTable + rx + '<|' + str(coinc.numifos) + '>' + str(rank) + xccx
507 followupLink = 'None'
508 if followup:
509 followup.from_coinc( coinc, coinc.get_ifos()[1][0] )
510 coinc.get_ifos()[1][0]
511 followupFile = followupOpts.prefix \
512 + '_followup_' + str(followup.number) + followupOpts.suffix \
513 + '.html'
514 followupLink = '<a href="./' + followupFile +'"> here </a>'
515 if format == 'html':
516 CoincSummTable = CoincSummTable + '<td rowspan=' + str(coinc.numifos) + '>' + followupLink + xccx
517 elif format == 'wiki':
518 CoincSummTable = CoincSummTable + rx + '<|' + str(coinc.numifos) + '>' + followupLink + xccx
519
520 for trig in coinc:
521 CoincSummTable = CoincSummTable + trig.ifo + xccx + str(coinc.stat) + xccx + str(coinc.fap) + xccx + str(trig.end_time) + \
522 xccx + str(trig.end_time_ns) + xccx + str(trig.mass1) + xccx + str(trig.mass2) + xccx + str(trig.mchirp) + \
523 xccx + str(trig.eta) + xccx + str(trig.snr) + xccx + str(trig.chisq) + xccx + str(trig.get_effective_snr()) + xr + \
524 rx
525 CoincSummTable = CoincSummTable + xr
526 rank = rank + 1
527 if rank > number: break
528 CoincSummTable = CoincSummTable + xt
529
530 return CoincSummTable
531
532 -def write_html_output(opts, args, fnameList, tagLists,
533 doThumb=True, mapList = [],
534 comment=None, CoincSummTable=None,
535 html_tag = '', add_box_flag=False):
536 """
537 @param opts: The options from the calling code
538 @param args: The args from the calling code
539 @param fnameList: A list of the filenames
540 @param tagLists: A list for the tags, getting added to the links
541 @param doThumb: Uses the _thumb file as the sourcs for the images
542 @param mapList: A list of dictionaries to create the image maps
543 @html_tag: tag to add to html filename
544 @add_box_flag: Adds _OPEN_BOX to the html file name if any
545 of the files in filelist have "_OPEN_BOX" in their name. Otherwise,
546 will add "_CLOSED_BOX" to the file name. These flags go between
547 opts.prefix and opts.suffix
548 """
549
550 prefix = opts.prefix
551
552 if html_tag != '':
553 prefix += '_' + html_tag
554
555 if add_box_flag:
556 box_flag = ''
557 if any(fname for fname in fnameList if 'OPEN_BOX' in fname):
558 box_flag ='_OPEN_BOX'
559 else:
560 box_flag = '_CLOSED_BOX'
561
562 prefix += box_flag
563
564
565
566 page, extra = init_markup_page(opts)
567 page.h1(opts.name + " results")
568
569 page.p(prefix + opts.suffix)
570 page.hr()
571
572
573 html_filename = prefix + opts.suffix +".html"
574 if opts.output_path:
575 html_filename = opts.output_path + html_filename
576 html_file = file(html_filename, "w")
577
578
579 for tag,filename in zip(tagLists,fnameList):
580
581
582 fname = "Images/" + os.path.basename(filename)
583
584
585 if doThumb:
586 fname_thumb = fname[:-4] + "_thumb.png"
587 else:
588 fname_thumb =fname
589
590
591 page.a(extra.img(src=[fname_thumb], width=400,
592 alt=tag, border="2"), title=tag, href=[ fname])
593
594 page.add("<hr/>")
595
596
597 if len(mapList)>0:
598 m=0
599 for mapDict in mapList:
600 m+=1
601 page.add( mapDict['text']+'<br>' )
602 page.add( '<IMG src="%s" width=800px '
603 'usemap="#map%d">' % ( mapDict['object'], m) )
604 page.add( '<MAP name="map%d"> <P>' % m )
605 n=0
606 for px, py, link in zip( mapDict['xCoords'],
607 mapDict['yCoords'],
608 mapDict['links'] ):
609 n+=1
610 page.add( '<area href="%s" shape="circle" '
611 'coords="%d, %d, 5"> Point%d</a>' %
612 ( link, px, py, n) )
613 page.add('</P></MAP></OBJECT><br>')
614 page.add("<hr/>")
615
616 if opts.enable_output:
617 if comment is not None:
618 page.add("<div> "+comment+"</div>")
619 page.hr()
620 if CoincSummTable is not None:
621 page.add(CoincSummTable)
622 page.hr()
623 text = writeProcessParams(opts.name, opts.version, args)
624 page.add(text)
625 html_file.write(page(False))
626 html_file.close()
627
628 return html_filename
629
630
632 """
633 write the output cache file of theplotting functions
634 """
635
636 output_cache_name = '.'.join([html_filename.rstrip('.html'), 'cache'])
637 this = open(output_cache_name, 'w')
638 if opts.enable_output:
639 this.write(os.path.basename(html_filename) + '\n')
640 for filename in fnameList:
641 if str(filename).endswith('.png'):
642 fname = "Images/"+os.path.basename(filename)
643 elif str(filename).endswith('.html'):
644 fname = os.path.basename(str(filename))
645 this.write(fname + '\n')
646 this.close()
647
648
650 """
651 Convert input parameters from the process params that the code was called
652 with into a formatted string that can be saved within an other document
653 (e.g., HTML)
654
655 @param name: name of the executable/script
656 @param version:version of the executable/script
657 @param command: command line arguments from a pylal script
658 @return text
659 """
660 text = "Figure(s) produced with '" + name + "' with version: <br>" \
661 + version \
662 + '<br>\n<p style="width:80%; color:blue">'+ name
663 for arg in command:
664 text += " " + arg
665 text+='</p>'
666
667 return text
668
670 """
671 Add the given file to the lal.Cache
672
673 @param fname:
674 @param cache:
675 """
676 file_name = fname.split('.')[0].split('-')
677 cache.append(lal.CacheEntry( file_name[0], file_name[1],
678 segments.segment(int(file_name[2]),
679 int(file_name[2]) + int(file_name[3])),
680 'file://' + socket.gethostbyaddr(socket.gethostname())[0] + \
681 os.getcwd() + '/' + fname))
682
684 """
685 Generate a lal.Cache for the list of files
686
687 @param fileList : a list of files
688 @return cache
689 """
690 cache = lal.Cache()
691 for file in fileList:
692 AddFileToCache(file, cache)
693 return(cache)
694
695
696 -class SummValueContentHandler(ligolw.PartialLIGOLWContentHandler):
697 """
698 Content handler that only reads in the SummValue table
699 """
700 - def __init__(self, xmldoc):
701 ligolw.PartialLIGOLWContentHandler.__init__(self, xmldoc, lambda name, attrs: lsctables.IsTableProperties(lsctables.SummValueTable, name, attrs))
702
703 try:
704 lsctables.use_in(SummValueContentHandler)
705 except AttributeError:
706
707
708 pass
709
710
712 """
713 Create suffix and prefix that will be used to name the output files.
714 'version' is outdated and not used anymore.
715
716 @param opts : the user arguments (user_tag, gps_end_time and
717 gps_start_time are used).
718 @param name: name of the calling function/executable
719 @return prefix
720 @return suffix
721 """
722
723
724
725 prefix = name
726 try:
727 if opts.ifo_times:
728 prefix = opts.ifo_times +"-"+ prefix
729 except:
730 print >> sys.stderr, "--ifo-times option not implemented in the "+name +" executable. skipping..."
731 pass
732 try:
733 if opts.ifo_tag:
734 prefix = prefix + "_" + opts.ifo_tag
735 except:
736 print >> sys.stderr, "--ifo-tag option not implemented in the "+name +" executable. skipping..."
737 pass
738 try:
739 if opts.user_tag:
740 prefix = prefix + "_" + opts.user_tag
741 except:
742 print >> sys.stderr, "--user-tag option not implemented in the "+name +" executable. skipping..."
743 pass
744
745
746 try:
747 if opts.gps_start_time is not None and opts.gps_end_time is not None:
748 suffix = "-"+str(int(opts.gps_start_time))+"-"+str(int(math.ceil(opts.gps_end_time))-int(opts.gps_start_time))
749 else:
750 suffix = "-unspecified-gpstime"
751 except:
752 suffix = "-unspecified-gpstime"
753 print >> sys.stderr, "--gps-start-time and/or --gps-end-time option not implemented in the " + \
754 name + " executable. skipping..."
755 pass
756
757 opts.prefix = prefix
758 opts.suffix = suffix
759 opts.name = name
760 opts.version = git_version.verbose_msg.replace("\n","<br>")
761
762
763 if opts.output_path is not None:
764 if not opts.output_path.endswith("/"):
765 opts.output_path += "/"
766
767
768 if not os.path.exists(opts.output_path):
769 os.mkdir(opts.output_path)
770
771 if not os.path.exists(opts.output_path+"Images"):
772 os.mkdir(opts.output_path+"Images")
773
774 else:
775 if not os.path.exists("Images"):
776 os.mkdir("Images")
777
778 return opts
779
780
782 """
783 Load the markup module, and initialise the HTML document if the opts
784 argument contains enable_ouput option.
785
786 @param opts : the user arguments
787 @return page
788 @return extra
789 """
790
791 if opts.enable_output:
792 try:
793 from glue import markup
794 from glue.markup import oneliner as extra_oneliner
795 except:
796 raise ImportError("Require markup.py to generate the html page")
797
798 page = markup.page()
799 try:
800 page.init(title=__title__)
801 except:
802 page.init()
803
804 return page, extra_oneliner
805
806
808 """
809 read in the SummValueTables from a list of files and return the
810 horizon distance versus total mass
811
812 @param fList: list of input files
813 @param verbose: boolean (default False)
814 """
815
816 output = {}
817 massOutput = {}
818 count = 0
819 if len(fList) == 0:
820 return output
821
822
823 for thisFile in fList:
824 if verbose:
825 print str(count+1)+"/"+str(len(fList))+" " + thisFile
826 count = count+1
827 massNum = 0
828
829 doc = utils.load_filename(thisFile, contenthandler = contenthandler)
830 try:
831 summ_value_table = lsctables.SummValueTable.get_table(doc)
832 except ValueError:
833 print "ValueError in readHorizonDistanceFromSummValueTable while reading summvalue table from file ", thisFile
834 return output,massOutput
835
836
837 if summ_value_table is None:
838 return output,massOutput
839
840
841 for row in summ_value_table:
842
843 if row.name == 'inspiral_effective_distance':
844
845 if (row.comment == '1.40_1.40_8.00') or (row.comment == '1.4_1.4_8'):
846 if not output.has_key(row.ifo):
847 output[row.ifo] = lsctables.New(lsctables.SummValueTable)
848 output[row.ifo].append(row)
849
850 else:
851 if not massOutput.has_key(row.ifo):
852 massOutput[row.ifo] = [lsctables.New(lsctables.SummValueTable)]
853 if len(massOutput[row.ifo]) < massNum + 1:
854 massOutput[row.ifo].append(lsctables.New(lsctables.SummValueTable))
855 massOutput[row.ifo][massNum].append(row)
856 massNum += 1
857 return output,massOutput
858