diff --git a/branson.wsgi b/branson.wsgi index ba5774aa97912966641dc00c29fc4eaf1aa4b493..2329d66fcbc1b2fad8fd15edfadb8cc1e65697fd 100644 --- a/branson.wsgi +++ b/branson.wsgi @@ -22,6 +22,8 @@ sys.path.insert(1,'/home/branson/sandbox/lib/python2.6/site-packages') import logging logging.basicConfig() +os.environ['MPLCONFIGDIR']='/home/branson/logs/' + #logging.basicConfig(level=logging.DEBUG, # format='%(asctime)s %(levelname)s %(message)s', # filename='/tmp/myapp.log', diff --git a/django.wsgi b/django.wsgi index 164ebb1f4cc6fdf8cb7a0d49078e9475d27dc92f..31d11ce1dc62f3a27a2a52d8dc8ab176e14c8cf5 100644 --- a/django.wsgi +++ b/django.wsgi @@ -13,6 +13,8 @@ sys.path.append('/home/gracedb/graceproj') import logging logging.basicConfig() +os.environ['MPLCONFIGDIR']='/tmp/' + #logging.basicConfig(level=logging.DEBUG, # format='%(asctime)s %(levelname)s %(message)s', # filename='/tmp/myapp.log', diff --git a/gracedb/forms.py b/gracedb/forms.py index 2e5497ae33ec54178079e8d9d6fc6757c0a67fcb..f89210aef13365a7f9899caccd06aa10a97d4122 100644 --- a/gracedb/forms.py +++ b/gracedb/forms.py @@ -22,7 +22,11 @@ class GraceQueryField(forms.CharField): from django.db.models import Q queryString = forms.CharField.clean(self, queryString) try: - return Event.objects.filter(parseQuery(queryString)).distinct() + if self.select_subclasses: + return Event.objects.filter(parseQuery(queryString)).select_subclasses() + else: + return Event.objects.filter(parseQuery(queryString)).distinct() + except ParseException, e: err = "Error: " + escape(e.pstr[:e.loc]) + errorMarker + escape(e.pstr[e.loc:]) raise forms.ValidationError(mark_safe(err)) @@ -34,9 +38,19 @@ class GraceQueryField(forms.CharField): # What could this be and how can we handle it better? XXX raise forms.ValidationError(str(e)+str(type(e))) + def __init__(self, *args, **kwargs): + self.select_subclasses = False + if 'select_subclasses' in kwargs.keys(): + self.select_subclasses = kwargs.pop('select_subclasses') + super(GraceQueryField, self).__init__(*args, **kwargs) + class SimpleSearchForm(forms.Form): - query = GraceQueryField(required=False, widget=forms.TextInput(attrs={'size':60})) + query = GraceQueryField(required=False, widget=forms.TextInput(attrs={'size':60})) +# XXX There must be a better way of doing this. +class SimpleSearchFormWithSubclasses(forms.Form): + query = GraceQueryField(required=False, widget=forms.TextInput(attrs={'size':60}), + select_subclasses=True) class CreateEventForm(forms.Form): groupChoices = [("","")]+[(g.name, g.name) for g in Group.objects.all()] diff --git a/gracedb/reports.py b/gracedb/reports.py index b01577193fc98fc7f5017404912f3aa985446831..d4eaefe24b735a3f4d9641b3981dfc8e2e4bace6 100644 --- a/gracedb/reports.py +++ b/gracedb/reports.py @@ -1,5 +1,6 @@ from django.http import HttpResponse +from django.http import HttpResponseForbidden from django.template import RequestContext from django.shortcuts import render_to_response from django.conf import settings @@ -7,7 +8,26 @@ from django.conf import settings from gracedb.models import Event from django.db.models import Q -import os, datetime, json, time +import os, json + +from django.core.urlresolvers import reverse + +from models import CoincInspiralEvent ,SingleInspiral +from forms import SimpleSearchFormWithSubclasses +from query import parseQuery + + +from django.db.models import Max, Min +import matplotlib +matplotlib.use('Agg') +import numpy +import matplotlib.pyplot as plot +import StringIO +import base64 +import sys +import time +from datetime import datetime, timedelta +from utils import posixToGpsTime def histo(request): @@ -46,8 +66,8 @@ def histo(request): def rate_data(request): # XXX there is a better way -- should be using group_by or something. # WAAY too many queries (~300) going on here. - now = datetime.datetime.now() - day = datetime.timedelta(1) + now = datetime.now() + day = timedelta(1) ts_min = now - 60 * day ts_max = now @@ -84,3 +104,193 @@ def rate_data(request): return series + +# XXX This should be configurable / moddable or something +MAX_QUERY_RESULTS = 1000 + +# The following two util routines are for gstlalcbc_report. This is messy. +def cluster(events): + # FIXME N^2 clustering, but event list should always be small anyway... + def quieter(e1, events = events, win = 5): + for e2 in events: + if e2.far == e1.far: + # XXX I need subclass attributes here. + if (e2.gpstime < e1.gpstime + win) and (e2.gpstime > e1.gpstime - win) and (e2.snr > e1.snr): + return True + else: + if (e2.gpstime < e1.gpstime + win) and (e2.gpstime > e1.gpstime - win) and (e2.far < e1.far): + return True + return False + return [e for e in events if not quieter(e)] + +def to_png_image(out = sys.stdout): + f = StringIO.StringIO() + plot.savefig(f, format="png") + return base64.b64encode(f.getvalue()) + +def gstlalcbc_report(request, format=""): + if not request.user or not request.user.is_authenticated(): + return HttpResponseForbidden("Forbidden") + + if request.method == "GET": + if "query" not in request.GET: + # Use default query. LowMass events from the past week. + t_high = datetime.now() + dt = timedelta(days=7) + t_low = t_high - dt + t_high = posixToGpsTime(time.mktime(t_high.timetuple())) + t_low = posixToGpsTime(time.mktime(t_low.timetuple())) + query = 'CBC LowMass %d .. %d' % (t_low, t_high) + rawquery = query + form = SimpleSearchFormWithSubclasses({'query': query}) + else: + form = SimpleSearchFormWithSubclasses(request.GET) + rawquery = request.GET['query'] + else: + form = SimpleSearchFormWithSubclasses(request.POST) + rawquery = request.POST['query'] + if form.is_valid(): + objects = form.cleaned_data['query'] + + # Check for foreign objects. + for obj in objects: + if not isinstance(obj, CoincInspiralEvent): + errormsg = 'Your query returned items that are not CoincInspiral Events. ' + errormsg += 'Please try again.' + form = SimpleSearchFormWithSubclasses() + return render_to_response('gracedb/gstlalcbc_report.html', + { 'form':form, 'message':errormsg}, + context_instance=RequestContext(request)) + + # Check that we have a well-defined GPS time range. + # Find the gpstime limits of the query by diving into the query object. + # XXX Down the rabbit hole! + qthing = parseQuery(rawquery) + gpsrange = None + for child in qthing.children: + if 'gpstime' in str(child): + for subchild in child.children: + if isinstance(subchild, tuple): + gpsrange = subchild[1] + if not gpsrange: + # Bounce back to the user with an error message + errormsg = 'Your query does not have a gpstime range. Please try again.' + form = SimpleSearchFormWithSubclasses() + return render_to_response('gracedb/gstlalcbc_report.html', + { 'form':form, 'message':errormsg}, + context_instance=RequestContext(request)) + lt = int(gpsrange[1]) - int(gpsrange[0]) + + # Check that there aren't too many objects. + # XXX Hardcoded limit + if objects.count() > 2000: + errormsg = 'Your query returned too many events. Please try again.' + return render_to_response('gracedb/gstlalcbc_report.html', + { 'form':form, 'message':errormsg}, + context_instance=RequestContext(request)) + + # Zero events will break the min/max over masses below. + if objects.count() < 1: + errormsg = 'Your query returned no events. Please try again.' + return render_to_response('gracedb/gstlalcbc_report.html', + { 'form':form, 'message':errormsg}, + context_instance=RequestContext(request)) + + clustered_events = cluster(objects) + clustered_events = sorted(clustered_events, None, key=lambda x: x.far) + + # Make IFAR plot. + ifars = numpy.array(sorted([1.0 / e.far for e in clustered_events])[::-1]) + N = numpy.linspace(1, len(ifars), len(ifars)) + + eN = numpy.linspace(1, 1000 * len(ifars), 1000 * len(ifars)) / 1000. + expected_ifars = lt / eN + + up = eN + eN**.5 + down = eN - eN**.5 + down[down < 0.9] = 0.9 + + plot.figure(figsize=(6,5)) + plot.loglog(ifars[::-1], N[::-1]) + plot.fill_between(expected_ifars[::-1], down[::-1], up[::-1], alpha=0.1) + plot.loglog(expected_ifars[::-1], eN[::-1]) + plot.ylim([0.9, len(ifars)]) + plot.xlabel('IFAR (s)') + plot.ylabel('N') + plot.grid() + ifar_plot = to_png_image() + + # Set the color map for loudest event table. Depends on lt. + #FAR_color_map = [ { 'max_far' : 1.e-12, 'color' : 'gold', 'desc' : '< 1/10000 yrs'}, + # { 'max_far' : 3.e-10, 'color' : 'silver', 'desc' : '< 1/100 yrs'}, + # { 'max_far' : 3.e-8, 'color' : '#A67D3D', 'desc' : '< 1/yr'}, + # { 'max_far' : 1.0/lt, 'color' : '#B2C248', 'desc' : 'louder than expected'}, ] + + # XXX Okay, this sucks. There is no switch/case structure in the django template + # language. So I couldn't think of any way to do this without checking ranges. + # And the range is zero to infinity. So here I go... + FAR_color_map = [ { 'min_far' : 0.0, 'max_far' : 1.e-12, 'color' : 'gold', 'desc' : '< 1/10000 yrs'}, + { 'min_far' : 1.e-12, 'max_far' : 3.e-10, 'color' : 'silver', 'desc' : '< 1/100 yrs'}, + { 'min_far' : 3.e-10, 'max_far' : 3.e-8, 'color' : '#A67D3D', 'desc' : '< 1/yr'}, + { 'min_far' : 3.e-8, 'max_far' : 1.0/lt, 'color' : '#B2C248', 'desc' : 'louder than expected'}, + { 'min_far' : 1.0/lt, 'max_far' : float('inf'), 'color' : 'white', 'desc' : ''}, ] + + # Make mass distribution plots + # First, find the min and max on the set of objects. + mchirp_limits = objects.aggregate(Max('coincinspiralevent__mchirp'), + Min('coincinspiralevent__mchirp')) + mass_limits = objects.aggregate(Max('coincinspiralevent__mass'), + Min('coincinspiralevent__mass')) + + mchirp = numpy.array([e.mchirp for e in clustered_events]) + plot.figure(figsize=(6,4)) + lower = int(mchirp_limits['coincinspiralevent__mchirp__min']) + upper = int(mchirp_limits['coincinspiralevent__mchirp__max']) + 1 + # XXX How to decide the number of bins? Hardcoding. + N_bins = 20 + delta = max(float(upper-lower)/N_bins,0.2) + plot.hist(mchirp, bins=numpy.arange(lower,upper,delta)) + plot.xlabel('Chirp Mass') + plot.ylabel('count') + plot.grid() + mchirp_dist = to_png_image() + + mass = numpy.array([e.mass for e in clustered_events]) + plot.figure(figsize=(6,4)) + lower = int(mass_limits['coincinspiralevent__mass__min']) + upper = int(mass_limits['coincinspiralevent__mass__max']) + 1 + # XXX Hardcoding number of bins again. + N_bins = 20 + delta = max(float(upper-lower)/N_bins,0.2) + plot.hist(mass, bins=numpy.arange(lower,upper,delta)) + plot.xlabel('Total Mass') + plot.ylabel('count') + plot.grid() + mass_dist = to_png_image() + + if objects.count() == 1: + title = "Query returned %s event." % objects.count() + else: + title = "Query returned %s events." % objects.count() + + context = { + 'title': title, + 'form': form, + 'formAction': reverse(gstlalcbc_report), + 'count' : objects.count(), + 'rawquery' : rawquery, + 'FAR_color_map' : FAR_color_map, + 'mass_dist' : mass_dist, + 'mchirp_dist' : mchirp_dist, + 'ifar_plot' : ifar_plot, + 'clustered_events' : clustered_events, + } + return render_to_response('gracedb/gstlalcbc_report.html', context, + context_instance=RequestContext(request)) + + return render_to_response('gracedb/gstlalcbc_report.html', + { 'form' : form, + }, + context_instance=RequestContext(request)) + + diff --git a/gracedb/templatetags/timeutil.py b/gracedb/templatetags/timeutil.py index 0290c58cfbe209d0b70819dd8ed69375db4edaed..0e3a6180bf1c9218ba2444dd2867274f804f9d08 100644 --- a/gracedb/templatetags/timeutil.py +++ b/gracedb/templatetags/timeutil.py @@ -12,6 +12,7 @@ from utils import posixToGpsTime, gpsToUtc import pytz import time import datetime +import logging # DATETIME_SETTINGS is guaranteed to be set. GRACE_DATETIME_FORMAT is not. FORMAT = getattr(settings, 'GRACE_DATETIME_FORMAT', settings.DATETIME_FORMAT) @@ -157,3 +158,19 @@ def timeSelections(t): rv['utc'] = dateformat.format(dt.astimezone(pytz.utc), format) return rv + +# XXX Branson added this. God will punish me. +@register.filter +def end_time(event,digits=4): + try: + #return float(event.end_time) + float(event.end_time_ns)/1.e9 + # The approach above did not work. For some reason, the value loses precision + # in the django template, so that the final digits get truncated and padded + # with zeros. Why? The current gpstime is only 10 digits. So I'm going to have to + # make this into a string. Totally sick, I know. + decimal_part = round(float(event.end_time_ns)/1.e9,digits) + # ugh. must pad with zeros to the right. + decimal_part = str(decimal_part)[1:].ljust(digits+1,'0') + return str(event.end_time) + decimal_part + except Exception, e: + return None diff --git a/gracedb/translator.py b/gracedb/translator.py index 9000531de1dbdf842d1bb6c9519416c3927ba743..78b8a30a18332ea7d941d9f580f0af0fe8a5c8e3 100644 --- a/gracedb/translator.py +++ b/gracedb/translator.py @@ -360,6 +360,23 @@ def handle_uploaded_data(event, datafilename, comment="Log File Created" ) log.save() + # Log the link to the CED. + if data.data['ced_link']: + comment = '<a href="%s">Coherent Event Display (CED)</a>' % data.data['ced_link'] + log = EventLog(event=event, + issuer=event.submitter, + comment=comment) + log.save() + + + # Log the link to the skymap. + if data.data['fits_skymap_link']: + comment = '<a href="%s">FITS skymap</a>' % data.data['fits_skymap_link'] + log = EventLog(event=event, + issuer=event.submitter, + comment=comment) + log.save() + elif event.analysisType == 'GRB': # Get the event time from the VOEvent file error = None @@ -493,6 +510,9 @@ class CwbData(Translator): # # The 2nd number following the "24*6" line is FAR. # + # https://... (link to CED) + # https://... (link to fits skymap) + rawdata = {} # Get Key/Value info @@ -549,6 +569,19 @@ class CwbData(Translator): data['ligo_axis_ra'] = data['rawdata'].get('phi',[None,None,None])[2] data['ligo_axis_dec'] = data['rawdata'].get('theta',[None,None,None])[2] + # Check for the links at the end. + ced_link = None + fits_skymap_link = None + for line in datafile: + if line.startswith("https"): + if line.find(".fits") > 0: + fits_skymap_link = line + else: + ced_link = line + + data['ced_link'] = ced_link + data['fits_skymap_link'] = fits_skymap_link + if needToClose: datafile.close() diff --git a/gracedb/views.py b/gracedb/views.py index 2ec10a1e77423163df743440760856128bc32574..8266ccd8efde923b6831face3700e8ea4f0bc3dd 100644 --- a/gracedb/views.py +++ b/gracedb/views.py @@ -18,8 +18,10 @@ from models import MultiBurstEvent from models import GrbEvent from models import SingleInspiral from forms import CreateEventForm, EventSearchForm, SimpleSearchForm +from forms import SimpleSearchFormWithSubclasses from alert import issueAlert, issueAlertForLabel, issueAlertForUpdate from translator import handle_uploaded_data +from query import parseQuery from django.contrib.auth.models import User @@ -698,7 +700,6 @@ def assembleLigoLw(objects): ligolw_add.merge_compatible_tables(xmldoc) return xmldoc - def search(request, format=""): if not request.user or not request.user.is_authenticated(): return HttpResponseForbidden("Forbidden") diff --git a/ligoauth/migrations/0008_add_grbexttrig_user_and_dn.py b/ligoauth/migrations/0008_add_grbexttrig_user_and_dn.py new file mode 100644 index 0000000000000000000000000000000000000000..72b18031b182d668c2205211824752b80a1cec08 --- /dev/null +++ b/ligoauth/migrations/0008_add_grbexttrig_user_and_dn.py @@ -0,0 +1,125 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import DataMigration +from django.db import models + + + +users = [ + { + 'username' : 'grb.exttrig', + 'first_name' : '', + 'last_name' : 'GRB ExtTrig', + 'email' : 'talukder@uoregon.edu', + 'dns' : [ + "/DC=org/DC=ligo/O=LIGO/OU=Services/CN=grb.exttrig/ldas-pcdev1.ligo.caltech.edu", + ] + }, +] + + +class Migration(DataMigration): + + def forwards(self, orm): + LocalUser = orm['ligoauth.LocalUser'] + X509Cert = orm['ligoauth.X509Cert'] + + # Local Users + for entry in users: + user, created = LocalUser.objects.get_or_create(username=entry['username']) + if created: + user.first_name = entry['first_name'] + user.last_name = entry['last_name'] + user.email = entry['email'] + user.is_active = True + user.is_staff = False + user.is_superuser = False + user.save() + current_dns = set([cert.subject for cert in user.x509cert_set.all()]) + new_dns = set(entry['dns']) + + missing_dns = new_dns - current_dns + redundant_dns = current_dns - new_dns + + for dn in missing_dns: + cert, created = X509Cert.objects.get_or_create(subject=dn) + if created: + cert.save() + cert.users.add(user) + + for dn in redundant_dns: + cert = X509Cert.objects.get(subject=dn) + cert.users.remove(user) + + def backwards(self, orm): + LocalUser = orm['ligoauth.LocalUser'] + X509Cert = orm['ligoauth.X509Cert'] + for entry in users: + for dn in entry['dns']: + cert = X509Cert.objects.get(subject=dn) + cert.delete() + + user = LocalUser.objects.get(username=entry['username']) + user.delete() + + + + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'ligoauth.ligoldapuser': { + 'Meta': {'object_name': 'LigoLdapUser', '_ormbases': ['auth.User']}, + 'ldap_dn': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}), + 'user_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'ligoauth.localuser': { + 'Meta': {'object_name': 'LocalUser', '_ormbases': ['auth.User']}, + 'user_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'ligoauth.x509cert': { + 'Meta': {'object_name': 'X509Cert'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'subject': ('django.db.models.fields.CharField', [], {'max_length': '200'}), + 'users': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.User']", 'symmetrical': 'False'}) + } + } + + complete_apps = ['ligoauth'] + symmetrical = True diff --git a/static/css/style.css b/static/css/style.css index 7580845d0c1fbd3aa756a7a9fb0584cc68dbb163..d95fe1c6faa2c28d62ff5ec270ee429fc4fb3e44 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -12,8 +12,14 @@ table.event {border-bottom:1px solid gray;} /* Branson added */ table.event {width:100%} +table.analysis_specific_lm th {padding:3px;border:none;text-align:center;vertical-align:bottom;} +table.analysis_specific_lm {border-bottom:1px solid gray;} + table.analysis_specific {width:100%} +table.gstlalcbc {border:0;border-spacing:0px;width:100%;} +table.gstlalcbc th {padding:3px;border:none;vertical-align:bottom;} + .content-area { margin-top: 10px; margin-bottom: 30px; diff --git a/templates/gracedb/event_detail_LM.html b/templates/gracedb/event_detail_LM.html index a02d436d216fc96e49520d2482d6590e4a4a984d..11d81da57d377ee221b5bce8d74f3db29af900cb 100644 --- a/templates/gracedb/event_detail_LM.html +++ b/templates/gracedb/event_detail_LM.html @@ -1,40 +1,45 @@ {% extends "gracedb/event_detail.html" %} - +{% load timeutil %} {% load scientific %} {# Analysis-specific attributes for an LM event#} {% block analysis_specific %} -<h3>Analysis-Specific Attributes</h3> -<table class="analysis_specific"> <tbody> - <tr> - <td> <table class="event"> <tbody> - <tr> <th> ifos </th> <td> {{object.ifos}} </td> </tr> - <tr> <th> end_time </th> <td> {{object.end_time}} </td> </tr> - <tr> <th> end_time_ns </th> <td> {{object.end_time_ns}} </td> </tr> - </tbody></table> - </td> - <td> <table class="event"> <tbody> - <tr> <th> mass </th> <td> {{object.mass|floatformat:"-4"}} </td> </tr> - <tr> <th> mchirp </th> <td> {{object.mchirp|floatformat:"-4"}} </td> </tr> - <tr> <th> minimum_duration </th> <td> {{object.minimum_duration|scientific}} </td> </tr> - </tbody></table> - </td> - <td> <table class="event"> <tbody> - <tr> <th> snr </th> <td> {{object.snr|floatformat:"-4"}} </td> </tr> - <tr> <th> false_alarm_rate </th> <td> {{object.false_alarm_rate|floatformat:"-4"}} </td> </tr> - <tr> <th> combined_far </th> <td> {{object.combined_far|scientific}} </td> </tr> - </tbody></table> - </td> +{# Test whether the object has the end_time property. Older (non CoincInspiral) events don't. #} +{% if object.end_time %} + +<div id="container" style="display:table;width:100%"> +<div style="display:table-row;width:100%"> - </tr> -</tbody> </table> +<div style="display:table-cell;float:left;width:35%;"> +<h3>Coinc Tables</h3> +<!-- <table class="analysis_specific"> <tbody> --> +<!-- 5 rows here --> +<table style="height:320px"> + <!-- <tr> <th> ifos </th> <td> {{object.ifos}} </td> </tr> --> + <tr> <th> End Time </th> <td> {{object|end_time:4}} </td> </tr> + <!--<tr> <th> end_time_ns </th> <td> {{object.end_time_ns}} </td> </tr> --> + <tr> <th> Total Mass </th> <td> {{object.mass|floatformat:"-4"}} </td> </tr> + <tr> <th> Chirp Mass </th> <td> {{object.mchirp|floatformat:"-4"}} </td> </tr> + <!--<tr> <th> minimum_duration </th> <td> {{object.minimum_duration|scientific}} </td> </tr>--> + <tr> <th> SNR </th> <td> {{object.snr|floatformat:"-4"}} </td> </tr> + <tr> <th> False Alarm Probability </th> <td> {{object.false_alarm_rate|scientific }} </td> </tr> + <!--<tr> <th> combined_far </th> <td> {{object.combined_far|scientific}} </td> </tr>--> +</table> +<!-- </tbody> </table> --> +</div> {% if single_inspiral_events %} <!-- Single Inspiral Data --> -<div id="single_inspiral_tables" +<!-- 13 rows here. --> +<div style="display:table-cell;float:right;width:65%"> +<h3>Single Inspiral Tables</h3> + +<!--<div id="single_inspiral_tables"> --> +<!-- <div id="single_inspiral_tables" data-dojo-type="dijit/TitlePane" - data-dojo-props="title: 'Single Inspiral Tables', open: false"> + data-dojo-props="title: 'Single Inspiral Tables', open: false"> --> + <!--<table class="analysis_specific_lm"> --> <table> <tr> <th>IFO</th> @@ -42,12 +47,12 @@ <th>{{ e.ifo }}</th> {% endfor %} </tr> - <tr> + <!-- <tr> <th>Search</th> {% for e in single_inspiral_events %} <td>{{ e.search }}</td> {% endfor %} - </tr> + </tr> --> <tr> <th>Channel</th> {% for e in single_inspiral_events %} @@ -60,30 +65,30 @@ <td>{{ e.end_time_full }}</td> {% endfor %} </tr> - <tr> + <!-- <tr> <th>Impulse Time</th> {% for e in single_inspiral_events %} <td>{{ e.impulse_time_full }}</td> {% endfor %} - </tr> + </tr> --> <tr> <th>Template Duration</th> {% for e in single_inspiral_events %} <td>{{ e.template_duration }}</td> {% endfor %} </tr> - <tr> + <!-- <tr> <th>Event Duration</th> {% for e in single_inspiral_events %} <td>{{ e.event_duration }}</td> {% endfor %} - </tr> - <tr> + </tr> --> + <!-- <tr> <th>Amplitude</th> {% for e in single_inspiral_events %} <td>{{ e.amplitude }}</td> {% endfor %} - </tr> + </tr> --> <tr> <th>Effective Distance</th> {% for e in single_inspiral_events %} @@ -108,132 +113,132 @@ <td>{{ e.mass2 }}</td> {% endfor %} </tr> - <tr> + <!-- <tr> <th>MChirp</th> {% for e in single_inspiral_events %} <td>{{ e.mchirp }}</td> {% endfor %} - </tr> - <tr> + </tr> --> + <!-- <tr> <th>MTotal</th> {% for e in single_inspiral_events %} <td>{{ e.mtotal }}</td> {% endfor %} - </tr> + </tr> --> <tr> <th>η</th> {% for e in single_inspiral_events %} <td>{{ e.eta }}</td> {% endfor %} </tr> - <tr> + <!-- <tr> <th>κ</th> {% for e in single_inspiral_events %} <td>{{ e.kappa }}</td> {% endfor %} - </tr> - <tr> + </tr> --> + <!-- <tr> <th>χ</th> {% for e in single_inspiral_events %} <td>{{ e.chi }}</td> {% endfor %} - </tr> - <tr> + </tr> --> + <!-- <tr> <th>τ<sub>0</sub></th> {% for e in single_inspiral_events %} <td>{{ e.tau0 }}</td> {% endfor %} - </tr> - <tr> + </tr> --> + <!-- <tr> <th>τ<sub>2</sub></th> {% for e in single_inspiral_events %} <td>{{ e.tau2 }}</td> {% endfor %} - </tr> - <tr> + </tr> --> + <!-- <tr> <th>τ<sub>3</sub></th> {% for e in single_inspiral_events %} <td>{{ e.tau3 }}</td> {% endfor %} - </tr> - <tr> + </tr> --> + <!-- <tr> <th>τ<sub>4</sub></th> {% for e in single_inspiral_events %} <td>{{ e.tau4 }}</td> {% endfor %} - </tr> - <tr> + </tr> --> + <!-- <tr> <th>τ<sub>5</sub></th> {% for e in single_inspiral_events %} <td>{{ e.tau5 }}</td> {% endfor %} - </tr> - <tr> + </tr> --> + <!-- <tr> <th>τ<sub>Total</sub></th> {% for e in single_inspiral_events %} <td>{{ e.ttotal }}</td> {% endfor %} - </tr> - <tr> + </tr> --> + <!-- <tr> <th>Ψ<sub>0</sub></th> {% for e in single_inspiral_events %} <td>{{ e.psi0 }}</td> {% endfor %} - </tr> - <tr> + </tr> --> + <!-- <tr> <th>Ψ<sub>3</sub></th> {% for e in single_inspiral_events %} <td>{{ e.psi3 }}</td> {% endfor %} - </tr> - <tr> + </tr> --> + <!-- <tr> <th>α</th> {% for e in single_inspiral_events %} <td>{{ e.alpha }}</td> {% endfor %} - </tr> - <tr> + </tr> --> + <!-- <tr> <th>α<sub>1</sub></th> {% for e in single_inspiral_events %} <td>{{ e.alpha1 }}</td> {% endfor %} - </tr> - <tr> + </tr> --> + <!-- <tr> <th>α<sub>2</sub></th> {% for e in single_inspiral_events %} <td>{{ e.alpha2 }}</td> {% endfor %} - </tr> - <tr> + </tr> --> + <!-- <tr> <th>α<sub>3</sub></th> {% for e in single_inspiral_events %} <td>{{ e.alpha3 }}</td> {% endfor %} - </tr> - <tr> + </tr> --> + <!-- <tr> <th>α<sub>4</sub></th> {% for e in single_inspiral_events %} <td>{{ e.alpha4 }}</td> {% endfor %} - </tr> - <tr> + </tr> --> + <!-- <tr> <th>α<sub>5</sub></th> {% for e in single_inspiral_events %} <td>{{ e.alpha5 }}</td> {% endfor %} - </tr> - <tr> + </tr> --> + <!-- <tr> <th>α<sub>6</sub></th> {% for e in single_inspiral_events %} <td>{{ e.alpha6 }}</td> {% endfor %} - </tr> - <tr> + </tr> --> + <!-- <tr> <th>β</th> {% for e in single_inspiral_events %} <td>{{ e.beta }}</td> {% endfor %} - </tr> + </tr> --> <tr> <th>F Final</th> {% for e in single_inspiral_events %} @@ -258,104 +263,110 @@ <td>{{ e.chisq_dof }}</td> {% endfor %} </tr> - <tr> + <!-- <tr> <th>Bank χ<sup>2</sup></th> {% for e in single_inspiral_events %} <td>{{ e.bank_chisq }}</td> {% endfor %} - </tr> - <tr> + </tr> --> + <!-- <tr> <th>Bank χ<sup>2</sup> DOF</th> {% for e in single_inspiral_events %} <td>{{ e.bank_chisq_dof }}</td> {% endfor %} - </tr> - <tr> + </tr> --> + <!-- <tr> <th>Cont χ<sup>2</sup></th> {% for e in single_inspiral_events %} <td>{{ e.cont_chisq }}</td> {% endfor %} - </tr> - <tr> + </tr> --> + <!-- <tr> <th>Cont χ<sup>2</sup> DOF</th> {% for e in single_inspiral_events %} <td>{{ e.cont_chisq_dof }}</td> {% endfor %} - </tr> - <tr> + </tr> --> + <!-- <tr> <th>Σ<sup>2</sup></th> {% for e in single_inspiral_events %} <td>{{ e.sigmasq }}</td> {% endfor %} - </tr> - <tr> + </tr> --> + <!-- <tr> <th>RSQ Veto Duration</th> {% for e in single_inspiral_events %} <td>{{ e.rsqveto_duration }}</td> {% endfor %} - </tr> - <tr> + </tr> --> + <!-- <tr> <th>Γ<sub>0</sub></th> {% for e in single_inspiral_events %} <td>{{ e.gamma0 }}</td> {% endfor %} - </tr> - <tr> + </tr> --> + <!-- <tr> <th>Γ<sub>1</sub></th> {% for e in single_inspiral_events %} <td>{{ e.gamma1 }}</td> {% endfor %} - </tr> - <tr> + </tr> --> + <!-- <tr> <th>Γ<sub>2</sub></th> {% for e in single_inspiral_events %} <td>{{ e.gamma2 }}</td> {% endfor %} - </tr> - <tr> + </tr> --> + <!-- <tr> <th>Γ<sub>3</sub></th> {% for e in single_inspiral_events %} <td>{{ e.gamma3 }}</td> {% endfor %} - </tr> - <tr> + </tr> --> + <!-- <tr> <th>Γ<sub>4</sub></th> {% for e in single_inspiral_events %} <td>{{ e.gamma4 }}</td> {% endfor %} - </tr> - <tr> + </tr> --> + <!-- <tr> <th>Γ<sub>5</sub></th> {% for e in single_inspiral_events %} <td>{{ e.gamma5 }}</td> {% endfor %} - </tr> - <tr> + </tr> --> + <!-- <tr> <th>Γ<sub>6</sub></th> {% for e in single_inspiral_events %} <td>{{ e.gamma6 }}</td> {% endfor %} - </tr> - <tr> + </tr> --> + <!-- <tr> <th>Γ<sub>7</sub></th> {% for e in single_inspiral_events %} <td>{{ e.gamma7 }}</td> {% endfor %} - </tr> - <tr> + </tr> --> + <!-- <tr> <th>Γ<sub>8</sub></th> {% for e in single_inspiral_events %} <td>{{ e.gamma8 }}</td> {% endfor %} - </tr> - <tr> + </tr> --> + <!-- <tr> <th>Γ<sub>9</sub></th> {% for e in single_inspiral_events %} <td>{{ e.gamma9 }}</td> {% endfor %} - </tr> + </tr> --> </table> +</div> + </div> +</div> <!-- container --> + {% endif %} +{% endif %} <!-- object has end_time property. --> + {% endblock %} diff --git a/templates/gracedb/gstlalcbc_report.html b/templates/gracedb/gstlalcbc_report.html new file mode 100644 index 0000000000000000000000000000000000000000..709199cfe3fd30642e3d7a34d702e0f790f80c5f --- /dev/null +++ b/templates/gracedb/gstlalcbc_report.html @@ -0,0 +1,114 @@ +{% extends "base.html" %} +{% load scientific %} +{% load timeutil %} + +{% block bodyattrs %} +onload="document.search_form.query.focus();" +{% endblock %} + +{% block title %}gstlalcbc Report{% endblock %} +{% block heading %}{% endblock %} #} +{% block pageid %}gstlalcbc Report{% endblock %} + +{% block jscript %} + <script type="text/javascript"> + <!-- + function toggle_visibility(id) { + var e = document.getElementById(id); + if(e.style.display == 'block') + e.style.display = 'none'; + else + e.style.display = 'block'; + } + //--> + </script> +{% endblock %} + +{% block content %} + +<div class="content-area"> + + {% if message %} + <p><font color="red"> {{ message }} </font> </p> + {% endif %} + + <form method="POST" name="search_form"> + <table> + {{ form.as_table }} + <tr><td/><td><a onClick="toggle_visibility('hints');">Hints on querying</a></td> + </tr> + <tr><td/><td>{% include "gracedb/query_help_frag.html" %}</td></tr> + </table> + </form> + + {{ title }} +</div> + +{% if count > 0 %} +<div class="content-area"> + <h3> IFAR Plot </h3> + <div> + <img src="data:image/png;base64,{{ ifar_plot|safe }}"></img> + </div> +</div> + +<div class="content-area"> + <h3> Mass Distributions </h3> + <div> + <img src="data:image/png;base64,{{ mchirp_dist|safe }}"></img> + </div> + + <div> + <img src="data:image/png;base64,{{ mass_dist|safe }}"></img> + </div> +</div> + +<div class="content-area"> + <h3> Loudest event table </h3> + + <!-- legend table --> + <br/> + <br/> + <table style="border-spacing:0px;"> + {% for d in FAR_color_map %} + <tr> <td style="background-color:{{ d.color }}">{{ d.desc }}</td> </tr> + {% endfor %} + </table> + <br/> + <br/> + + <table class="gstlalcbc"> + <!-- need to select background color based on far. Ucky? --> + <tr> + <th>Rank</th> + <th>GraceID</th> + <th>gpstime</th> + <th>FAR</th> + <th>IFOs</th> + <th>Total Mass</th> + <th>Chirp Mass</th> + <th>SNR</th> + </tr> + {% for obj in clustered_events %} + {% for d in FAR_color_map %} + {%if obj.far <= d.max_far and obj.far > d.min_far %} + <tr style="background-color:{{d.color}}"> + {% endif %} + {% endfor %} + <td>{{ forloop.counter }} </td> + <td><a href="{% url view obj.graceid %}">{{ obj.graceid }}</a></td> + <td> {{ obj|end_time }} </td> + <td> {{ obj.far|scientific }} </td> + <td> {{ obj.instruments }} </td> + <td> {{ obj.mass|floatformat:4 }} </td> + <td> {{ obj.mchirp|floatformat:4 }} </td> + <td> {{ obj.snr|floatformat:4 }} </td> + </tr> + {% endfor %} + </table> +</div> + +{% endif %} + +{% endblock %} + diff --git a/templates/gracedb/histogram.html b/templates/gracedb/histogram.html index 173a3d3bedc4690e290ee7fc9d79cc9cca56dbfa..42b8c9613a2f34409be1e800e91251fe9d46ea0b 100644 --- a/templates/gracedb/histogram.html +++ b/templates/gracedb/histogram.html @@ -82,6 +82,10 @@ function toggle(id) { {% block content %} <br/> +<a href="{% url gstlalcbc_report %}"><h3>Dynamic CBC Report</h3></a> +<br/> +<br/> + <a name="latency" href="javascript:toggle('latency');"><h3>Latency</h3></a> <div id="latency" style="display:none;"> diff --git a/urls.py b/urls.py index a2d1085659ac87df9354cc7b2a563437e0b31857..b319de8822c928b8c7d79b7984ec2245dc2530e4 100644 --- a/urls.py +++ b/urls.py @@ -30,6 +30,7 @@ urlpatterns = patterns('', url (r'^feeds/$', feedview, name="feeds"), url (r'^reports/$', 'gracedb.reports.histo', name="reports"), + url (r'^reports/gstlalcbc_report/(?P<format>(json|flex))?$', 'gracedb.reports.gstlalcbc_report', name="gstlalcbc_report"), (r'^reports/(?P<path>.+)$', 'django.views.static.serve', {'document_root': settings.LATENCY_REPORT_DEST_DIR}),