diff --git a/gstlal-inspiral/python/stats/inspiral_extrinsics.py b/gstlal-inspiral/python/stats/inspiral_extrinsics.py
index e8b77f61df8d54cb6e437a1cb576c9f4d11640f9..1a0768a0f4ac1e1ede71f289777f137f41c323c6 100644
--- a/gstlal-inspiral/python/stats/inspiral_extrinsics.py
+++ b/gstlal-inspiral/python/stats/inspiral_extrinsics.py
@@ -66,18 +66,98 @@ from gstlal import paths as gstlal_config_paths
 
 
 __all__ = [
-	"P_instruments_given_signal",
-	"SNRPDF",
-	"NumeratorSNRCHIPDF",
-	"chunker",
-	"normsq_along_one",
-	"margprob",
+	"InspiralExtrinsics",
 	"TimePhaseSNR",
 	"p_of_instruments_given_horizons",
-	"InspiralExtrinsics"
+	"margprob",
+	"chunker",
 ]
 
 
+__doc__ = """
+
+The goal of this module is to implement the probability of getting a given set
+of extrinsics parameters for each detector (snr, horizon distance, end time and
+phase) assuming that the event is a gravitational wave signal, *s*, coming from
+an isotropic distribution uniform in location, orientation and the volume of
+space.  The implementation of this in the calling code can be found in
+:py:mod:`stats.inspiral_lr`.
+
+The probabilities are factored in the following way:
+
+.. math::
+
+	P(\\vec{\\rho}, \\vec{t}, \\vec{\phi}, \\vec{O} | \\vec{D_H}, s)
+	= 
+	\underbrace{P(\\vec{\\rho}, \\vec{t}, \\vec{\phi} | \\vec{O}, \\vec{D_H}, s)}_{\mathrm{1:\,TimePhaseSNR()}} 
+	\\times 
+	\underbrace{P(\\vec{O} | \\vec{D_H}, s)}_{\mathrm{2:\,p\_of\_instruments\_given\_horizons()}}
+
+where:
+
+* :math:`\\vec{\\rho}` denotes the vector of SNRs with one component from each detector
+* :math:`\\vec{t}`     denotes the vector of end time with one component from each detector
+* :math:`\\vec{\phi}`  denotes the vector of measured phases with one component from each detector
+* :math:`\\vec{O}`    denotes the vector of observing IFOs with one component from each detector
+* :math:`\\vec{D_H}`   denotes the vector of horizon distances with one component from each detector
+* :math:`s`            denotes the signal hypothesis
+
+See the following for details:
+
+* :py:class:`InspiralExtrinsics` -- helper class to implement the full probability expression
+* :py:class:`TimePhaseSNR` -- implementation of term 1 above
+* :py:class:`p_of_instruments_given_horizons` -- implementation of term 2 above
+
+and :any:`gstlal_inspiral_plot_extrinsic_params` for some visualizations of
+these PDFs.
+
+
+Sanity Checks
+-------------
+
+The code here is new for O3.  We compared the result to the O2 code on 100,000
+seconds of data searching for binary neutron stars in H and L.  The injection
+set was identical.
+
+.. |O2_O3_O2_LR_range| image:: ../images/O2_O3_O2_LR_range.png
+   :width: 400px
+
+.. |O2_O3_O3_LR_range| image:: ../images/O2_O3_O3_LR_range.png
+   :width: 400px
+
+.. |O2_O3_O2_cnt_vs_LR| image:: ../images/O2_O3_O2_cnt_vs_LR.png
+   :width: 400px
+
+.. |O2_O3_O3_cnt_vs_LR| image:: ../images/O2_O3_O3_cnt_vs_LR.png
+   :width: 400px
+
++-------------------+-------------------------+-------------------------+
+|                   | O2 Code                 | O3 Code                 |
++===================+=========================+=========================+
+| **Found/Missed**  | 939 / 2270              | 951 / 2258              |
++-------------------+-------------------------+-------------------------+
+| **Range**         | |O2_O3_O2_LR_range|     | |O2_O3_O3_LR_range|     |
++-------------------+-------------------------+-------------------------+
+| **Count vs LR**   | |O2_O3_O2_cnt_vs_LR|    | |O2_O3_O3_cnt_vs_LR|    |
++-------------------+-------------------------+-------------------------+
+
+
+Review Status
+-------------
+
++-------------------------------------------------+------------------------------------------+------------+
+| Names                                           | Hash                                     | Date       |
++=================================================+==========================================+============+
+| --                                              | --                                       | --         |
++-------------------------------------------------+------------------------------------------+------------+
+
+
+Documentation of classes and functions
+--------------------------------------
+
+"""
+
+
 #
 # =============================================================================
 #
@@ -86,7 +166,7 @@ __all__ = [
 # =============================================================================
 #
 
-
+# FIXME: This code is no longer used.
 def P_instruments_given_signal(horizon_distances, n_samples = 500000, min_instruments = 2, min_distance = 0.):
 	"""
 	Example:
@@ -258,6 +338,7 @@ def P_instruments_given_signal(horizon_distances, n_samples = 500000, min_instru
 #
 
 
+# FIXME: This code is no longer used.
 class SNRPDF(object):
 	#
 	# cache of pre-computed P(instruments | horizon distances, signal)
@@ -712,6 +793,7 @@ class SNRPDF(object):
 #
 
 
+# FIXME: This code is no longer used.
 class NumeratorSNRCHIPDF(rate.BinnedLnPDF):
 	"""
 	Reports ln P(chi^2/rho^2 | rho, signal)
@@ -873,33 +955,55 @@ class TimePhaseSNR(object):
 
 	.. math::
 
-		P(D_{\mathrm{eff}\,H}, D_{\mathrm{eff}\,L}, D_{\mathrm{eff}\,V}, t_H, t_L, t_V, \phi_H, \phi_L, \phi_V | s, t_g = 0, \phi_c = 0 \, \mathrm{s}, D = 1 \, \mathrm{Mpc})
+		P(\\vec{\\rho}, \\vec{t}, \\vec{\phi} | \\vec{O}, \\vec{D_H}, s)
 
-	We reduce the dimensionality by computing things relative to the first
-	instrument in alphabetical order, e.g.,
+	Instead of evaluating the above probability we will parameterize it by
+	pulling out an overall term that scales as the network SNR to the negative
+	fourth power.
 
 	.. math::
 
-		\\begin{align*}
-		P(D_{\mathrm{eff}\,H} / D_{\mathrm{eff}\,L}, D_{\mathrm{eff}\,H} / D_{\mathrm{eff}\,V}, t_H - t_L, t_H - t_V, \phi_H - \phi_L, \phi_H - \phi_V | s, D = 1 \, \mathrm{Mpc}) &= \\
-		P(\\vec\lambda|s,  D = 1 \, \mathrm{Mpc}) &
-		\end{align*}
+		P(\\vec{\\rho}, \\vec{t}, \\vec{\phi} | \\vec{O}, \\vec{D_H}, s)
+		\\approx
+		P(\\vec{\\rho_{\mathrm{1\,Mpc}}}, \\vec{t}, \\vec{\phi} | \\vec{O}, \\vec{D_H}, s) \\times |\\vec{\\rho}|^{-4}
+
+
+	We reduce the dimensionality by computing things relative to the first
+	instrument in alphabetical order and use effective distance instead of SNR,
+	e.g.,
+
+	.. math::
+			P(\\vec{D_{\mathrm{eff}}} / D_{\mathrm{eff}\,0}, \\vec{t} - t_0, \\vec{\phi} - \phi_0 | \\vec{O}, \\vec{D_H}, s) 
+		\\times
+			|\\vec{\\rho}|^{-4} 
+		\equiv
+			P(\\vec\lambda|\\vec{O}, \\vec{D_H}, s)
+		\\times
+			|\\vec{\\rho}|^{-4}
 
 	where now the absolute geocenter end time or coalescence phase does not
-	matter.  We then assert that physical signals have a uniform distribution in
-	earth based coordinates, :math:`\mathrm{RA}, \cos(\mathrm{DEC}),
-	\cos(\iota), \psi`.  We lay down a uniform densly sampled grid in these
-	coordinates and assert that any signal should ``exactly" land
-	on one of the grid points. We transform that regularly spaced grid
-	into a grid of (irregularly spaced) points in :math:`\\vec{\lambda}`,
-	which we denote as :math:`\\vec{\lambda_{\mathrm{m}i}}` for the *i*\ th model lambda vector.
-	We consider that the **only** mechanism to push a signal away from one
-	of these exact *i*\ th grid points is Gaussian noise.  Furthermore we assume the
+	matter since our new variables are relative to the first instrument in
+	alphabetical order (denoted with 0 subscript).  Since the first component of
+	each of the new vectors is one by construction we don't track it and have
+	reduced the dimensionality by three.
+
+	In order to evaluate this distribution, we then assert that physical signals have
+	a uniform distribution in earth based coordinates, :math:`\mathrm{RA},
+	\cos(\mathrm{DEC}), \cos(\iota), \psi`.  We lay down a uniform densly sampled
+	grid in these coordinates and assert that any signal should ``exactly" land on
+	one of the grid points. We transform that regularly spaced grid into a grid of
+	(irregularly spaced) points in :math:`\\vec{\lambda}`, which we denote as
+	:math:`\\vec{\lambda_{\mathrm{m}i}}` for the *i*\ th model lambda vector.  We
+	consider that the **only** mechanism to push a signal away from one of these
+	exact *i*\ th grid points is Gaussian noise.  Furthermore we assume the
 	probability distribution is of the form:
 
 	.. math::
 
-		P(\\vec{\lambda}, \\vec{\lambda_{mi}}) = \\frac{1}{\sqrt{(2\pi)^k |\pmb{\Sigma}|}} \exp{ \left[ -\\frac{1}{2} \\vec{\Delta\lambda}^T \, \pmb{\Sigma}^{-1} \, \\vec{\Delta\lambda} \\right] }
+		P(\\vec{\lambda}, \\vec{\lambda_{mi}})
+		= 
+			\\frac{1}{\sqrt{(2\pi)^k |\pmb{\Sigma}|}} 
+			\exp{ \left[ -\\frac{1}{2} \\vec{\Delta\lambda}^T \, \pmb{\Sigma}^{-1} \, \\vec{\Delta\lambda} \\right] }
 
 	where :math:`\\vec{\Delta\lambda_i} = \\vec{\lambda} -
 	\\vec{\lambda_{\mathrm{m}i}}` and :math:`\pmb{\Sigma}` is diagonal.
@@ -907,13 +1011,15 @@ class TimePhaseSNR(object):
 
 	.. math::
 
-		P(\\vec{\lambda}, \\vec{\lambda_{mi}}) \propto \exp{ \left[ -\\frac{1}{2} \\vec{\Delta\lambda_i}^2 \\right] }
+		P(\\vec{\lambda}, \\vec{\lambda_{mi}})
+		\propto
+			\exp{ \left[ -\\frac{1}{2} \\vec{\Delta\lambda_i}^2 \\right] }
 
 	Then we assert that:
 
 	.. math::
 
-		P(\\vec\lambda|s,  D = 1 \, \mathrm{Mpc}) \propto \sum_i P(\\vec{\lambda}, \\vec{\lambda_{mi}})
+		P(\\vec\lambda|\\vec{O}, \\vec{D_H}, s) \propto \sum_i P(\\vec{\lambda}, \\vec{\lambda_{mi}})
 
 	Computing this probability on the fly is tough since the grid might
 	have millions of points.  Therefore we make another simplifying
@@ -935,6 +1041,7 @@ class TimePhaseSNR(object):
 	The geometric interpretation is shown in the following figure:
 
 	.. image:: ../images/TimePhaseSNR01.png
+	   :width: 400px
 
 	In order for this endeavor to be successful, we still need a fast way
 	to find the nearest neighbor. We use scipy KDTree to accomplish this.