#!/usr/bin/env python
#
# Copyright (C) 2013 Chad Hanna, Kipp Cannon
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
# Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

## @file gstlal_inspiral_plot_background
# A program to plot the likelihood distributions in noise of a gstlal inspiral analysis
#
# ### Command line interface
#
#	+ `--database` [filename]: Retrieve search results from this database (optional).  Can be given multiple times.
#	+ `--database-cache` [filename]: Retrieve search results from all databases in this LAL cache (optional).  See lalapps_path2cache.
#	+ `--max-snr` [value] (float): Plot SNR PDFs up to this value of SNR (default = 200).
#	+ `--max-log-lambda` [value] (float): Plot ranking statistic CDFs, etc., up to this value of the natural logarithm of the likelihood ratio (default = 100).
#	+ `--min-log-lambda` [value] (float): Plot ranking statistic CDFs, etc., down to this value of the natural logarithm of the likelihood ratio (default = -10).
#	+ `--output-dir` [path]: Write output to this directory (default = ".").
#	+ `--tmp-space` [path]: Path to a directory suitable for use as a work area while manipulating the database file.  The database file will be worked on in this directory, and then moved to the final location when complete.  This option is intended to improve performance when running in a networked environment, where there might be a local disk with higher bandwidth than is available to the filesystem on which the final output will reside.
#	+ `--user-tag` [tag]: Set the adjustable component of the description fields in the output filenames (default = "ALL").
#	+ `--verbose`: Be verbose.

import bisect
import math
import matplotlib
matplotlib.rcParams.update({
	"font.size": 8.0,
	"axes.titlesize": 10.0,
	"axes.labelsize": 10.0,
	"xtick.labelsize": 8.0,
	"ytick.labelsize": 8.0,
	"legend.fontsize": 8.0,
	"figure.dpi": 600,
	"savefig.dpi": 600,
	"text.usetex": True
})
from matplotlib import figure
from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
import numpy
from optparse import OptionParser
import sqlite3
import sys
import warnings


from glue import iterutils
from glue.lal import CacheEntry
from glue.ligolw import dbtables
from glue.ligolw import lsctables
from glue.ligolw import utils as ligolw_utils
from pylal import ligolw_thinca


from gstlal import far
from gstlal import inspiral_pipe

golden_ratio = (1. + math.sqrt(5.)) / 2.


def parse_command_line():
	parser = OptionParser()
	parser.add_option("-d", "--database", metavar = "filename", action = "append", help = "Retrieve search results from this database (optional).  Can be given multiple times.")
	parser.add_option("-c", "--database-cache", metavar = "filename", help = "Retrieve search results from all databases in this LAL cache (optional).  See lalapps_path2cache.")
	parser.add_option("--max-snr", metavar = "SNR", default = 200., type = "float", help = "Plot SNR PDFs up to this value of SNR (default = 200).")
	parser.add_option("--max-log-lambda", metavar = "value", default = 100., type = "float", help = "Plot ranking statistic CCDFs, etc., up to this value of the natural logarithm of the likelihood ratio (default = 100).")
	parser.add_option("--min-log-lambda", metavar = "value", default = -5., type = "float", help = "Plot ranking statistic CCDFs, etc., down to this value of the natural logarithm of the likelihood ratio (default = -10).")
	parser.add_option("--output-dir", metavar = "output-dir", default = ".", help = "Write output to this directory (default = \".\").")
	parser.add_option("-t", "--tmp-space", metavar = "path", help = "Path to a directory suitable for use as a work area while manipulating the database file.  The database file will be worked on in this directory, and then moved to the final location when complete.  This option is intended to improve performance when running in a networked environment, where there might be a local disk with higher bandwidth than is available to the filesystem on which the final output will reside.")
	parser.add_option("--user-tag", metavar = "user-tag", default = "ALL", help = "Set the adjustable component of the description fields in the output filenames (default = \"ALL\").")
	parser.add_option("-v", "--verbose", action = "store_true", help = "Be verbose.")
	options, filenames = parser.parse_args()

	return options, filenames


def load_distributions(filenames, verbose = False):
	coinc_params_distributions, ranking_data, seglists = None, None, None
	for n, filename in enumerate(filenames, 1):
		if verbose:
			print >>sys.stderr, "%d/%d:" % (n, len(filenames)),
		this_coinc_params_distributions, this_ranking_data, this_seglists = far.parse_likelihood_control_doc(ligolw_utils.load_filename(filenames[0], contenthandler = far.ThincaCoincParamsDistributions.LIGOLWContentHandler, verbose = verbose))
		if this_coinc_params_distributions is None and this_ranking_data is None:
			raise ValueError("%s contains no parameter distribution data" % filename)
		if coinc_params_distributions is None:
			coinc_params_distributions = this_coinc_params_distributions
		else:
			coinc_params_distributions += this_coinc_params_distributions
		if ranking_data is None:
			ranking_data = this_ranking_data
		else:
			ranking_data += this_ranking_data
		if seglists is None:
			seglists = this_seglists
		else:
			seglists |= this_seglists
	if coinc_params_distributions is None and ranking_data is None:
		raise ValueError("no parameter distribution data loaded")
	# FIXME:  hack to fake some segments so we can generate file names
	# if the input files didn't provide segment information
	try:
		seglists.extent_all()
	except ValueError:
		seglists[None] = lsctables.segments.segmentlist([lsctables.segments.segment(0, 0)])
	return coinc_params_distributions, ranking_data, seglists


def load_search_results(filenames, tmp_path = None, verbose = False):
	if not filenames:
		return None, None

	background_ln_likelihood_ratios = {}
	zerolag_ln_likelihood_ratios = {}

	for n, filename in enumerate(filenames, 1):
		if verbose:
			print >>sys.stderr, "%d/%d: %s" % (n, len(filenames), filename)
		working_filename = dbtables.get_connection_filename(filename, tmp_path = tmp_path, verbose = verbose)
		connection = sqlite3.connect(working_filename)

		xmldoc = dbtables.get_xml(connection)
		definer_id = lsctables.CoincDefTable.get_table(xmldoc).get_coinc_def_id(ligolw_thinca.InspiralCoincDef.search, ligolw_thinca.InspiralCoincDef.search_coinc_type, create_new = False)

		for participating_instruments, ln_likelihood_ratio, is_background in connection.cursor().execute("""
SELECT
	coinc_inspiral.ifos,
	coinc_event.likelihood,
	EXISTS (
		SELECT
			*
		FROM
			time_slide
		WHERE
			time_slide.time_slide_id == coinc_event.time_slide_id
			AND time_slide.offset != 0
	)
FROM
	coinc_event
	JOIN coinc_inspiral ON (
		coinc_inspiral.coinc_event_id == coinc_event.coinc_event_id
	)
WHERE
	coinc_event.coinc_def_id == ?
		""", (definer_id,)):
			participating_instruments = frozenset(lsctables.instrument_set_from_ifos(participating_instruments))
			if is_background:
				background_ln_likelihood_ratios.setdefault(participating_instruments, []).append(ln_likelihood_ratio)
			else:
				zerolag_ln_likelihood_ratios.setdefault(participating_instruments, []).append(ln_likelihood_ratio)

		connection.close()
		dbtables.discard_connection_filename(filename, working_filename, verbose = verbose)

	return background_ln_likelihood_ratios, zerolag_ln_likelihood_ratios


def colour_from_instruments(instruments, colours = {
	"G1": numpy.array((0.0, 1.0, 1.0)),
	"H1": numpy.array((1.0, 0.0, 0.0)),
	"H2": numpy.array((0.0, 0.0, 1.0)),
	"L1": numpy.array((0.0, 0.8, 0.0)),
	"V1": numpy.array((1.0, 0.0, 1.0)),
}):
	# mix colours additively
	colour = sum(map(colours.__getitem__, instruments))
	# desaturate
	colour += len(instruments) - 1
	# normalize
	return colour / colour.max()


def plot_snr_chi_pdf(instrument, binnedarray, snr_max, tag, denom_binnedarray = None):
	fig = figure.Figure()
	FigureCanvas(fig)
	fig.set_size_inches((4.5, 3.5))
	axes = fig.gca()
	# the last bin can have a centre at infinity, and its value is
	# always 0 anyway so there's no point in trying to include it
	x = binnedarray.bins[0].centres()[:-1]
	y = binnedarray.bins[1].centres()[:-1]
	z = binnedarray.array[:-1,:-1]
	if denom_binnedarray is not None:
		assert (denom_binnedarray.bins[0].centres()[:-1] == x).all()
		assert (denom_binnedarray.bins[1].centres()[:-1] == y).all()
		z /= denom_binnedarray.array[:-1,:-1]
	if numpy.isnan(z).any():
		warnings.warn("%s PDF contains NaNs" % key)
		z = numpy.ma.masked_where(numpy.isnan(z), z)
	if not z.any():
		warnings.warn("%s PDF is 0, skipping" % key)
		return None

	# the range of the plots
	xlo, xhi = far.ThincaCoincParamsDistributions.snr_min, snr_max
	ylo, yhi = .0001, 1.

	x = x[binnedarray.bins[xlo:xhi, ylo:yhi][0]]
	y = y[binnedarray.bins[xlo:xhi, ylo:yhi][1]]
	z = z[binnedarray.bins[xlo:xhi, ylo:yhi]]

	# matplotlib's colour bar seems to rely on being able to store the
	# ratio of the lowest and highest value in a double so the range
	# cannot be more than about 300 orders of magnitude.  experiments
	# show it starts to go wrong before that but 250 orders of
	# magnitude seems to be OK
	numpy.clip(z, z.max() * 1e-250, float("+inf"), out = z)

	mesh = axes.pcolormesh(x, y, z.T, norm = matplotlib.colors.LogNorm(), cmap = "afmhot", shading = "gouraud")
	axes.contour(x, y, z.T, norm = matplotlib.colors.LogNorm(), colors = "k", linewidths = .5)
	axes.loglog()
	axes.grid(which = "both")
	#axes.set_xlim((xlo, xhi))
	#axes.set_ylim((ylo, yhi))
	fig.colorbar(mesh, ax = axes)
	axes.set_xlabel(r"$\mathrm{SNR}$")
	axes.set_ylabel(r"$\chi^{2} / \mathrm{SNR}^{2}$")
	if tag.lower() in ("signal",):
		assert denom_binnedarray is None
		axes.set_title(r"%s %s $P(\chi^{2} / \mathrm{SNR}^{2} | \mathrm{SNR})$" % (instrument, tag))
	elif tag.lower() in ("noise", "candidates"):
		assert denom_binnedarray is None
		axes.set_title(r"%s %s $P(\mathrm{SNR}, \chi^{2} / \mathrm{SNR}^{2})$" % (instrument, tag))
	elif tag.lower() in ("lr",):
		assert denom_binnedarray is not None
		axes.set_title(r"%s $P(\chi^{2} / \mathrm{SNR}^{2} | \mathrm{SNR}) / P(\mathrm{SNR}, \chi^{2} / \mathrm{SNR}^{2})$" % instrument)
	else:
		raise ValueError(tag)
	return fig

def plot_likelihood_ratio_pdf(instruments, pdf, (xlo, xhi), tag, zerolag_pdf = None):
	fig = figure.Figure()
	FigureCanvas(fig)
	fig.set_size_inches((4., 4. / golden_ratio))
	axes = fig.gca()
	axes.set_position((.15, .14, .84, .76))
	axes.semilogy(pdf.bins[0].centres(), pdf.array, color = "k")
	if zerolag_pdf is not None:
		axes.semilogy(zerolag_pdf.bins[0].centres(), zerolag_pdf.array, color = "k", linestyle = "--")
	axes.grid(which = "both")
	if instruments is None:
		axes.set_title(r"%s Log Likelihood Ratio PDF" % tag)
	else:
		axes.set_title(r"%s %s Log Likelihood Ratio PDF" % (", ".join(sorted(instruments)), tag))
	axes.set_xlabel(r"$\ln \Lambda$")
	axes.set_ylabel(r"$P(\ln \Lambda | \mathrm{%s})$" % tag.lower())
	yhi = pdf[xlo:xhi,].max()
	ylo = pdf[xlo:xhi,].min()
	if zerolag_pdf is not None:
		yhi = max(yhi, zerolag_pdf[xlo:xhi,].max())
		ylo = min(ylo, zerolag_pdf[xlo:xhi,].min())
	ylo = max(yhi * 1e-40, ylo)
	axes.set_ylim((10**math.floor(math.log10(ylo) - .5), 10**math.ceil(math.log10(yhi) + .5)))
	axes.set_xlim((xlo, xhi))
	return fig

def plot_likelihood_ratio_ccdf(instruments, ccdf, zerolag_ln_likelihood_ratios, (xlo, xhi), tag):
	fig = figure.Figure()
	FigureCanvas(fig)
	fig.set_size_inches((4., 4. / golden_ratio))
	axes = fig.gca()
	axes.set_position((.15, .13, .82, .77))
	x = numpy.linspace(xlo, xhi, 10000)
	y = ccdf(x)
	axes.semilogy(x, y, color = "k")
	yhi = y.max()
	ylo = max(yhi * 1e-40, y.min())
	if zerolag_ln_likelihood_ratios is not None:
		x = numpy.array(sorted(zerolag_ln_likelihood_ratios), dtype = "double")
		y = numpy.arange(len(zerolag_ln_likelihood_ratios), 0, -1, dtype = "double")
		y *= (1. - 1. / math.e) / y[0]
		axes.semilogy(x, y, color = "k", linestyle = "--")
		yhi = max(yhi, y.max())
		ylo = min(ylo, y.min())
		ylo = max(ylo, yhi * (y.min() / yhi)**1.5)
	axes.set_ylim((10**math.floor(math.log10(ylo) - .5), 10**math.ceil(math.log10(yhi) + .5)))
	axes.set_xlim((xlo, xhi))
	axes.grid(which = "both")
	if instruments is None:
		axes.set_title(r"%s Log Likelihood Ratio CCDF" % tag)
	else:
		axes.set_title(r"%s %s Log Likelihood Ratio CCDF" % (", ".join(sorted(instruments)), tag))
	axes.set_xlabel(r"$\ln \Lambda$")
	axes.set_ylabel(r"$P(\geq \ln \Lambda | \mathrm{%s})$" % tag.lower())
	return fig

def plot_rates(coinc_params_distributions, ranking_data):
	fig = figure.Figure()
	FigureCanvas(fig)
	fig.set_size_inches((6., 6.))
	axes0 = fig.add_subplot(2, 2, 1)
	axes1 = fig.add_subplot(2, 2, 2)
	axes2 = fig.add_subplot(2, 2, 3)
	axes3 = fig.add_subplot(2, 2, 4)

	# singles counts
	labels = []
	sizes = []
	colours = []
	for instrument, category in sorted(coinc_params_distributions.instrument_categories.items()):
		count = coinc_params_distributions.background_rates["instruments"][category,]
		if not count:
			continue
		labels.append("%s\n(%d)" % (instrument, count))
		sizes.append(count)
		colours.append(colour_from_instruments((instrument,)))
	axes0.pie(sizes, labels = labels, colors = colours, autopct = "%.3g%%", pctdistance = 0.4, labeldistance = 0.8)
	axes0.set_title("Observed Background Event Counts")

	# projected background counts
	labels = []
	sizes = []
	colours = []
	for instruments in sorted(sorted(instruments) for instruments in coinc_params_distributions.count_above_threshold if instruments is not None):
		count = coinc_params_distributions.background_rates["instruments"][coinc_params_distributions.instrument_categories.category(instruments),]
		if len(instruments) < 2 or not count:
			continue
		labels.append("%s\n(%d)" % (", ".join(instruments), count))
		sizes.append(count)
		colours.append(colour_from_instruments(instruments))
	axes1.pie(sizes, labels = labels, colors = colours, autopct = "%.3g%%", pctdistance = 0.4, labeldistance = 0.8)
	axes1.set_title("Projected Background Coincidence Counts")

	# recovered signal distribution
	if ranking_data is not None:
		labels = []
		sizes = []
		colours = []
		for instruments, fraction in sorted(coinc_params_distributions.Pinstrument_signal.items(), key = lambda (instruments, fraction): sorted(instruments)):
			if len(instruments) < 2 or not fraction:
				continue
			labels.append(", ".join(sorted(instruments)))
			sizes.append(fraction)
			colours.append(colour_from_instruments(instruments))
		axes2.pie(sizes, labels = labels, colors = colours, autopct = "%.3g%%", pctdistance = 0.4, labeldistance = 0.8)
		axes2.set_title(r"Projected Recovered Signal Distribution")

	# observed counts
	labels = []
	sizes = []
	colours = []
	for instruments, count in sorted((sorted(instruments), count) for instruments, count in coinc_params_distributions.count_above_threshold.items() if instruments is not None):
		if len(instruments) < 2 or not count:
			continue
		labels.append("%s\n(%d)" % (", ".join(instruments), count))
		sizes.append(count)
		colours.append(colour_from_instruments(instruments))
	axes3.pie(sizes, labels = labels, colors = colours, autopct = "%.3g%%", pctdistance = 0.4, labeldistance = 0.8)
	axes3.set_title("Observed Coincidence Counts")

	return fig


#
# command line
#


options, filenames = parse_command_line()


#
# load input
#


coinc_params_distributions, ranking_data, seglists = load_distributions(filenames, verbose = options.verbose)
if coinc_params_distributions is not None:
	coinc_params_distributions.finish(verbose = options.verbose)
if ranking_data is not None:
	ranking_data.finish(verbose = options.verbose)

if coinc_params_distributions is not None and ranking_data is not None:
	fapfar = far.FAPFAR(ranking_data, coinc_params_distributions.count_above_threshold, threshold = far.RankingData.ln_likelihood_ratio_threshold, livetime = far.get_live_time(seglists))
else:
	fapfar = None

if ranking_data is not None:
	signal_ccdf = far.FAPFAR(ranking_data, dict.fromkeys(ranking_data.signal_likelihood_pdfs, 1.), threshold = far.RankingData.ln_likelihood_ratio_threshold).ccdf_interpolator
else:
	signal_ccdf = None


if options.database_cache is not None:
	if options.database is None:
		options.database = []
	options.database += [CacheEntry(line).path for line in open(options.database_cache)]
background_ln_likelihood_ratios, zerolag_ln_likelihood_ratios = load_search_results(options.database, tmp_path = options.tmp_space, verbose = options.verbose)


#
# plots
#


fig = plot_rates(coinc_params_distributions, ranking_data)
plotname = inspiral_pipe.T050017_filename("H1L1V1", "GSTLAL_INSPIRAL_PLOT_BACKGROUND_%s_rates" % options.user_tag, int(seglists.extent_all()[0]), int(seglists.extent_all()[1]), ".png", path = options.output_dir)
if options.verbose:
	print >>sys.stderr, "writing %s" % plotname
fig.savefig(plotname)


for (instruments, horizon_distances), (ignored, binnedarray, ignored) in sorted(coinc_params_distributions.snr_joint_pdf_cache.items(), key = lambda ((a, horizon_distances), b): sorted(horizon_distances)):
	if len(instruments) > 2:
		# FIXME:  figure out how to plot 3D PDFs
		continue
	instruments = sorted(instruments)
	horizon_distances = dict(horizon_distances)
	fig = figure.Figure()
	FigureCanvas(fig)
	fig.set_size_inches((5, 4))
	axes = fig.gca()
	x = binnedarray.bins[0].centres()
	y = binnedarray.bins[1].centres()
	z = binnedarray.array
	if numpy.isnan(z).any():
		warnings.warn("%s SNR PDF for %s contains NaNs" % (", ".join(instruments), ", ".join("%s=%g" % instdist for instdist in sorted(horizon_distances.items()))))
		z = numpy.ma.masked_where(numpy.isnan(z), z)

	# the range of the plots
	xlo, xhi = far.ThincaCoincParamsDistributions.snr_min, options.max_snr

	x = x[binnedarray.bins[xlo:xhi, xlo:xhi][0]]
	y = y[binnedarray.bins[xlo:xhi, xlo:xhi][1]]
	z = z[binnedarray.bins[xlo:xhi, xlo:xhi]]

	# don't try to plot blank PDFs (it upsets older matplotlibs)
	if z.max() == 0.:
		continue

	# these plots only require about 20 orders of magnitude of dynamic
	# range
	numpy.clip(z, z.max() * 1e-20, float("+inf"), out = z)

	# one last check for craziness to make error messages more
	# meaningful
	assert not numpy.isnan(z).any()
	assert not (z <= 0.).any()

	mesh = axes.pcolormesh(x, y, z.T, norm = matplotlib.colors.LogNorm(), cmap = "afmhot", shading = "gouraud")
	axes.contour(x, y, z.T, norm = matplotlib.colors.LogNorm(), colors = "k", linewidths = .5)
	axes.loglog()
	axes.grid(which = "both")
	#axes.set_xlim((xlo, xhi))
	#axes.set_ylim((xlo, xhi))
	fig.colorbar(mesh, ax = axes)
	# co-ordinates are in alphabetical order
	axes.set_xlabel(r"$\mathrm{SNR}_{\mathrm{%s}}$" % instruments[0])
	axes.set_ylabel(r"$\mathrm{SNR}_{\mathrm{%s}}$" % instruments[1])
	axes.set_title(r"$P(%s)$" % ", ".join("\mathrm{SNR}_{\mathrm{%s}}" % instrument for instrument in instruments))
	plotname = inspiral_pipe.T050017_filename(instruments, "GSTLAL_INSPIRAL_PLOT_BACKGROUND_%s_snr_pdf_%s" % (options.user_tag, "_".join(iterutils.flatten(sorted(horizon_distances)))), int(seglists.extent_all()[0]), int(seglists.extent_all()[1]), ".png", path = options.output_dir)
	if options.verbose:
		print >>sys.stderr, "writing %s" % plotname
	fig.savefig(plotname)

for key, binnedarray in coinc_params_distributions.background_pdf.items() if coinc_params_distributions is not None else ():
	if "_snr_chi" not in key:
		continue
	instrument = key.split("_")[0]
	fig = plot_snr_chi_pdf(instrument, binnedarray, options.max_snr, "Noise")
	if fig is None:
		continue
	plotname = inspiral_pipe.T050017_filename(instruments, "GSTLAL_INSPIRAL_PLOT_BACKGROUND_%s_noise_snrchi2_pdf" % options.user_tag, int(seglists.extent_all()[0]), int(seglists.extent_all()[1]), ".png", path = options.output_dir)
	if options.verbose:
		print >>sys.stderr, "writing %s" % plotname
	fig.savefig(plotname)


for key, binnedarray in coinc_params_distributions.injection_pdf.items() if coinc_params_distributions is not None else ():
	if "_snr_chi" not in key:
		continue
	instrument = key.split("_")[0]
	fig = plot_snr_chi_pdf(instrument, binnedarray, options.max_snr, "Signal")
	if fig is None:
		continue
	plotname = inspiral_pipe.T050017_filename(instruments, "GSTLAL_INSPIRAL_PLOT_BACKGROUND_%s_signal_snrchi2_pdf" % options.user_tag, int(seglists.extent_all()[0]), int(seglists.extent_all()[1]), ".png", path = options.output_dir)
	if options.verbose:
		print >>sys.stderr, "writing %s" % plotname
	fig.savefig(plotname)

for key, binnedarray in coinc_params_distributions.injection_pdf.items() if coinc_params_distributions is not None else ():
	if "_snr_chi" not in key:
		continue
	instrument = key.split("_")[0]
	fig = plot_snr_chi_pdf(instrument, binnedarray, options.max_snr, "LR", denom_binnedarray = coinc_params_distributions.background_pdf[key])
	if fig is None:
		continue
	plotname = inspiral_pipe.T050017_filename(instruments, "GSTLAL_INSPIRAL_PLOT_BACKGROUND_%s_lr_snrchi2" % options.user_tag, int(seglists.extent_all()[0]), int(seglists.extent_all()[1]), ".png", path = options.output_dir)
	if options.verbose:
		print >>sys.stderr, "writing %s" % plotname
	fig.savefig(plotname)

for key, binnedarray in coinc_params_distributions.zero_lag_pdf.items() if coinc_params_distributions is not None else ():
	if "_snr_chi" not in key:
		continue
	instrument = key.split("_")[0]
	fig = plot_snr_chi_pdf(instrument, binnedarray, options.max_snr, "Candidates")
	if fig is None:
		continue
	plotname = inspiral_pipe.T050017_filename(instruments, "GSTLAL_INSPIRAL_PLOT_BACKGROUND_%s_candidate_snrchi2_pdf" % options.user_tag, int(seglists.extent_all()[0]), int(seglists.extent_all()[1]), ".png", path = options.output_dir)
	if options.verbose:
		print >>sys.stderr, "writing %s" % plotname
	fig.savefig(plotname)

for instruments, binnedarray in ranking_data.background_likelihood_pdfs.items() if ranking_data is not None else ():
	fig = plot_likelihood_ratio_pdf(instruments, binnedarray, (options.min_log_lambda, options.max_log_lambda), "Noise")
	plotname = inspiral_pipe.T050017_filename(instruments or "NONE", "GSTLAL_INSPIRAL_PLOT_BACKGROUND_%s_noise_likelihood_ratio_pdf" % options.user_tag, int(seglists.extent_all()[0]), int(seglists.extent_all()[1]), ".png", path = options.output_dir)
	if options.verbose:
		print >>sys.stderr, "writing %s" % plotname
	fig.savefig(plotname)

	if zerolag_ln_likelihood_ratios is None:
		ranking_stats = None
	elif instruments is None:
		ranking_stats = sum(zerolag_ln_likelihood_ratios.values(), [])
	elif instruments in zerolag_ln_likelihood_ratios:
		ranking_stats = zerolag_ln_likelihood_ratios[instruments]
	else:
		ranking_stats = None
	fig = plot_likelihood_ratio_ccdf(instruments, fapfar.ccdf_interpolator[instruments], ranking_stats, (options.min_log_lambda, options.max_log_lambda), "Noise")
	plotname = inspiral_pipe.T050017_filename(instruments or "NONE", "GSTLAL_INSPIRAL_PLOT_BACKGROUND_%s_noise_likelihood_ratio_ccdf" % options.user_tag, int(seglists.extent_all()[0]), int(seglists.extent_all()[1]), ".png", path = options.output_dir)
	if options.verbose:
		print >>sys.stderr, "writing %s" % plotname
	fig.savefig(plotname)

for instruments, binnedarray in ranking_data.signal_likelihood_pdfs.items() if ranking_data is not None else ():
	fig = plot_likelihood_ratio_pdf(instruments, binnedarray, (options.min_log_lambda, options.max_log_lambda), "Signal")
	plotname = inspiral_pipe.T050017_filename(instruments or "NONE", "GSTLAL_INSPIRAL_PLOT_BACKGROUND_%s_signal_likelihood_ratio_pdf" % options.user_tag, int(seglists.extent_all()[0]), int(seglists.extent_all()[1]), ".png", path = options.output_dir)
	if options.verbose:
		print >>sys.stderr, "writing %s" % plotname
	fig.savefig(plotname)

	fig = plot_likelihood_ratio_ccdf(instruments, signal_ccdf[instruments], None, (options.min_log_lambda, options.max_log_lambda), "Signal")
	plotname = inspiral_pipe.T050017_filename(instruments or "NONE", "GSTLAL_INSPIRAL_PLOT_BACKGROUND_%s_signal_likelihood_ratio_ccdf" % options.user_tag, int(seglists.extent_all()[0]), int(seglists.extent_all()[1]), ".png", path = options.output_dir)
	if options.verbose:
		print >>sys.stderr, "writing %s" % plotname
	fig.savefig(plotname)


#
# done
#


sys.exit(0)


total_count = {'H1':0., 'L1':0., 'V1':0.}

for f in files:
	Far = far.LocalRankingData.from_filenames([f], verbose = options.verbose)
	dists, segs = Far.distributions, Far.livetime_seg
	counts = dists.background_rates
	inj = dists.injection_rates
	likely = copy.deepcopy(inj)

	for i, ifo in enumerate(['H1','L1', 'V1']):	
		likely[ifo+"_snr_chi"].array /= counts[ifo+"_snr_chi"].array
		total_count[ifo] += counts[ifo+"_snr_chi"].array.sum()
		for name, obj in (("background", counts), ("injections", inj), ("likelihood", likely)):
			fig = pylab.figure(figsize=(6,5), facecolor = 'g')
			fig.patch.set_alpha(0.0)
			data = obj[ifo+"_snr_chi"].array
			snr = obj[ifo+"_snr_chi"].bins[0].centres()[1:-1]
			chi = obj[ifo+"_snr_chi"].bins[1].centres()[1:-1]
			chi[0] = 0 # not inf
			ax = pylab.subplot(111)
			pylab.pcolormesh(snr, chi, numpy.log10(data.T +1)[1:-1,1:-1])
			if "Log" in str(obj[ifo+"_snr_chi"].bins[0]):
				ax.set_xscale('log')
			if "Log" in str(obj[ifo+"_snr_chi"].bins[1]):
				ax.set_yscale('log')
			pylab.colorbar()
			pylab.xlabel('SNR')
			pylab.ylabel('reduced chi^2 / SNR^2')
			pylab.ylim([chi[1], chi[-1]])
			pylab.xlim([snr[1],snr[-1]])
			pylab.title('%s: %s log base 10 (number + 1)' % (ifo, name))
			pylab.grid(color=(0.1,0.4,0.5), linewidth=2)
			pylab.savefig(inspiral_pipe.T050017_filename(ifo, "GSTLAL_INSPIRAL_PLOT_BACKGROUND_%s_%s_%s" % (options.user_tag, name, os.path.split(f)[1]), int(seglists.extent_all()[0]), int(seglists.extent_all()[1]), ".png", path = options.output_dir))


if options.marginalized_file:
	marg, procid = far.RankingData.from_xml(ligolw_utils.load_filename(options.marginalized_file, contenthandler = far.ThincaCoincParamsDistributions.LIGOLWContentHandler, verbose = options.verbose))
	marg.compute_joint_cdfs()

	for k in marg.likelihood_pdfs:
		N = sum(total_count[ifo] for ifo in k)
		#FIXME hardcoded num_slides to 2, don't do that!
		m = marg.trials_table[k].count * marg.trials_table[k].count_below_thresh / marg.trials_table[k].thresh / float(abs(marg.livetime_seg)) * marg.trials_table.num_nonzero_count() / 2
		pylab.figure()
		larray = numpy.logspace(max(numpy.log10(marg.minrank[k]), -3), 0.99 * numpy.log10(marg.maxrank[k]), 1000)
		FAP = 1.0 - marg.cdf_interpolator[k](larray)
		FAPM = [far.fap_after_trials(p, m) for p in  FAP]
		pylab.semilogy(numpy.log(larray), FAPM, 'k')
		trials_high_error = FAPM + FAPM / numpy.sqrt(N*FAP)
		trials_low_error = FAPM - FAPM / numpy.sqrt(N*FAP)
		trials_low_error[trials_low_error < 1e-8] = 1e-8
		pylab.fill_between(numpy.log(larray), trials_low_error, trials_high_error, alpha=0.25, color='k')	
		pylab.grid()
		pylab.title("%s FAP vs Likelihood" % "".join(sorted(list(k))))
		pylab.xlabel('ln(Likelihood)')
		pylab.ylabel('False Alarm Probability')
		pylab.ylim([1e-8, 1])

		pylab.savefig(inspiral_pipe.T050017_filename(k.keys(), "GSTLAL_INSPIRAL_PLOT_BACKGROUND_%s_%s" % (options.user_tag, "".join(sorted(list(k)))), int(seglists.extent_all()[0]), int(seglists.extent_all()[1]), ".png", path = options.output_dir))

