def associate_extracted_sources(image_id, deRuiter_r, beamwidths_limit=1,
                                new_source_sigma_margin=3):
    """
    Associate extracted sources with sources detected in the running
    catalog.

    See the "developer's reference" section of the docs for a step-by-step
    breakdown of the logic encapsulated here.

    The dimensionless distance between two sources is given by the
    "De Ruiter radius", see Chapters 2 & 3 of Scheers' thesis.
    """

    logger.debug("Using a De Ruiter radius of %s" % (deRuiter_r,))
    ##This is used as a check that everything from the sourcefinder is sensible.
    ##Currently switched off as it's incompatible with sources about the meridian.
#    _delete_bad_blind_extractions(conn, image_id)
    _empty_temprunningcatalog()
    #+------------------------------------------------------+
    #| Here we select all extracted sources that have one or|
    #| more counterparts in the runningcatalog              |
    #| Association pairs are of the sequence runcat-xtrsrc, |
    #| which may be matching one of the following cases:    |
    #| many-to-many, many-to-one, one-to-many, one-to-many  |
    #+------------------------------------------------------+
    mw = _check_meridian_wrap(image_id)
    _insert_temprunningcatalog(image_id, deRuiter_r, beamwidths_limit, mw)
    #+------------------------------------------------------+
    #| Here we process (flag) the many-to-many associations.|
    #+------------------------------------------------------+
    database = tkp.db.Database()

    # Since the _flag_many_to_many_tempruncat uses the temprunningcatalog table
    # as a temporary use space the table will receive many writes and updates.
    # On postgresql for speed up reasons rows are not directly deleted, but
    # marked for deletion and eventually deleted by an auto vacuum process.
    # Because of the nested complexity of this query it may happen the
    # computational complexity of the query explodes, resulting in massive
    # slowdowns. To make sure the temprunningcatalog table doesn't contain dead
    # rows we force a manual vacuum here.
    database.vacuum('temprunningcatalog')

    # _process_many_to_many()
    _flag_many_to_many_tempruncat()
    #+------------------------------------------------------+
    #| After this, the assocs have been reduced to many-to-1|
    #| which are treated identical as 1-to-1, and 1-to-many.|
    #+------------------------------------------------------+
    # _process_many_to_1() => process_1_to_1()
    #+------------------------------------------------------+
    #| Here we process the one-to-many associations.        |
    #+------------------------------------------------------+
    try:
        _insert_1_to_many_runcat()
    except IntegrityError as e:
        logger.error("Error caught around _insert_1_to_many_runcat - "
                 "possible 'IntegrityError'. See Issue #4778. Will now re-raise.")
        raise e

    _flag_1_to_many_inactive_runcat()

    _insert_1_to_many_runcat_flux()
    _delete_1_to_many_inactive_runcat_flux()

    _insert_1_to_many_basepoint_assocxtrsource()
    _insert_1_to_many_replacement_assocxtrsource()
    _delete_1_to_many_inactive_assocxtrsource()

    _insert_1_to_many_assocskyrgn()
    _delete_1_to_many_inactive_assocskyrgn()

    _insert_1_to_many_newsource()
    _delete_1_to_many_inactive_newsource()

    _insert_1_to_many_varmetric()
    _delete_1_to_many_inactive_varmetric()

    _flag_1_to_many_inactive_tempruncat()

    #+-----------------------------------------------------+
    #| Here we process the one-to-one associations         |
    #+-----------------------------------------------------+
    # _process_1_to_1()
    _insert_1_to_1_assoc()
    _update_1_to_1_runcat()
    n_updated_rf = _update_1_to_1_runcat_flux()  # update flux in existing band
    if n_updated_rf:
        logger.debug("Updated 1-to-1 fluxes for %s sources" % n_updated_rf)
    n_new_rf = _insert_1_to_1_runcat_flux()  # insert flux for new band
    if n_new_rf:
        logger.debug("Inserted new fluxes for %s sources" % n_new_rf)
    #+-------------------------------------------------------+
    #| Here we take care of the extracted sources that could |
    #| not be associated with any runningcatalog source      |
    #+-------------------------------------------------------+
    _insert_new_runcat(image_id)
    _insert_new_runcat_flux(image_id)
    _insert_new_runcat_skyrgn_assocs(image_id)
    _insert_new_assocxtrsource(image_id)
    _determine_newsource_previous_limits(image_id, new_source_sigma_margin)

    _empty_temprunningcatalog()
    _update_ff_runcat_extractedsource()
    _delete_inactive_runcat() def insert_extracted_sources(image_id, results, extract_type='blind',
                             ff_runcat_ids=None, ff_monitor_ids=None):
    """
    Insert all detections from sourcefinder into the extractedsource table.

    Besides the source properties from sourcefinder, we calculate additional
    attributes that are increase performance in other tasks.

    The strict sequence from results (the sourcefinder detections) is given below.
    Note the units between sourcefinder and database.
    (0) ra [deg], (1) dec [deg],
    (2) ra_fit_err [deg], (3) decl_fit_err [deg],
    (4) peak_flux [Jy], (5) peak_flux_err [Jy],
    (6) int_flux [Jy], (7) int_flux_err [Jy],
    (8) significance detection level,
    (9) beam major width (arcsec), (10) - minor width (arcsec), (11) - parallactic angle [deg],
    (12) ew_sys_err [arcsec], (13) ns_sys_err [arcsec],
    (14) error_radius [arcsec]
    (15) gaussian fit (bool)
    (16), (17) chisq, reduced_chisq (float)

    ra_fit_err and decl_fit_err are the 1-sigma errors from the gaussian fit,
    in degrees. Note that for a source located towards the poles the ra_fit_err
    increases with absolute declination.
    error_radius is a pessimistic on-sky error estimate in arcsec.
    ew_sys_err and ns_sys_err represent the telescope dependent systematic errors
    and are in arcsec.
    An on-sky error (declination independent, and used in de ruiter calculations)
    is then:
    uncertainty_ew^2 = ew_sys_err^2 + error_radius^2
    uncertainty_ns^2 = ns_sys_err^2 + error_radius^2
    The units of uncertainty_ew and uncertainty_ns are in degrees.
    The error on RA is given by ra_err. For a source with an RA of ra and an error
    of ra_err, its RA lies in the range [ra-ra_err, ra+ra_err].
    ra_err^2 = ra_fit_err^2 + [alpha_inflate(ew_sys_err,decl)]^2
    decl_err^2 = decl_fit_err^2 + ns_sys_err^2.
    The units of ra_err and decl_err are in degrees.
    Here alpha_inflate() is the RA inflation function, it converts an
    angular on-sky distance to a ra distance at given declination.

    Input argument "extract" tells whether the source detections originate from:
    'blind': blind source extraction
    'ff_nd': from forced fits at null detection locations
    'ff_ms': from forced fits at monitoringlist positions

    Input argument ff_runcat is not empty in the case of forced fits from
    null detections. It contains the runningcatalog ids from which the
    source positions were derived for the forced fits. In that case the
    runcat ids will be inserted into the extractedsource table as well,
    to simplify further null-detection processing.
    For blind extractions this list is empty (None).

    For all extracted sources additional parameters are calculated,
    and appended to the sourcefinder data. Appended and converted are:

        - the image id to which the extracted sources belong to
        - the zone in which an extracted source falls is calculated, based
          on its declination. We adopt a zoneheight of 1 degree, so
          the floor of the declination represents the zone.
        - the positional errors are converted from degrees to arcsecs
        - the Cartesian coordinates of the source position
        - ra * cos(radians(decl)), this is very often being used in
          source-distance calculations
    """
    if not len(results):
        logger.debug("No extract_type=%s sources added to extractedsource for"
                    " image %s" % (extract_type, image_id))
        return

    xtrsrc = []
    for i, src in enumerate(results):
        r = list(src)
        # Drop any fits with infinite flux errors
        if math.isinf(r[5]) or math.isinf(r[7]):
            logger.warn("Dropped source fit with infinite flux errors "
                        "at position {} {} in image {}".format(
                r[0], r[1], image_id))
            continue
        # Use 360 degree rather than infinite uncertainty for
        # unconstrained positions.
        r[14] = substitute_inf(r[14], 360.0)
        r[15] = int(r[15])
        # ra_err: sqrt of quadratic sum of fitted and systematic errors.
        r.append(math.sqrt(r[2]**2 + alpha_inflate(r[12]/3600., r[1])**2))
        # decl_err: sqrt of quadratic sum of fitted and systematic errors.
        r.append(math.sqrt(r[3]**2 + (r[13]/3600.)**2))
        # uncertainty_ew: sqrt of quadratic sum of systematic error and error_radius
        # divided by 3600 because uncertainty in degrees and others in arcsec.
        r.append(math.sqrt(r[12]**2 + r[14]**2)/3600.)
        # uncertainty_ns: sqrt of quadratic sum of systematic error and error_radius
        # divided by 3600 because uncertainty in degrees and others in arcsec.
        r.append(math.sqrt(r[13]**2 + r[14]**2)/3600.)
        r.append(image_id) # id of the image
        r.append(int(math.floor(r[1]))) # zone
        r.extend(eq_to_cart(r[0], r[1])) # Cartesian x,y,z
        r.append(r[0] * math.cos(math.radians(r[1]))) # ra * cos(radians(decl))
        if extract_type == 'blind':
            r.append(0)
        elif extract_type == 'ff_nd':
            r.append(1)
        elif extract_type == 'ff_ms':
            r.append(2)
        else:
            raise ValueError("Not a valid extractedsource insert type: '%s'"
                             % extract_type)
        if ff_runcat_ids is not None:
            assert len(results)==len(ff_runcat_ids)
            r.append(ff_runcat_ids[i])
        else:
            r.append(None)

        if ff_monitor_ids is not None:
            assert len(results)==len(ff_monitor_ids)
            r.append(ff_monitor_ids[i])
        else:
            r.append(None)

        xtrsrc.append(r)

    insertion_query = """\
INSERT INTO extractedsource
  (ra
  ,decl
  ,ra_fit_err
  ,decl_fit_err
  ,f_peak
  ,f_peak_err
  ,f_int
  ,f_int_err
  ,det_sigma
  ,semimajor
  ,semiminor
  ,pa
  ,ew_sys_err
  ,ns_sys_err
  ,error_radius
  ,fit_type
  ,chisq
  ,reduced_chisq
  ,ra_err
  ,decl_err
  ,uncertainty_ew
  ,uncertainty_ns
  ,image
  ,zone
  ,x
  ,y
  ,z
  ,racosdecl
  ,extract_type
  ,ff_runcat
  ,ff_monitor
  )
VALUES {placeholder}
"""
    if xtrsrc:
        cols_per_row = len(xtrsrc[0])
        placeholder_per_row = '('+ ','.join(['%s']*cols_per_row) +')'

        placeholder_full = ','.join([placeholder_per_row]*len(xtrsrc))

        query = insertion_query.format(placeholder= placeholder_full)
        cursor = tkp.db.execute(query, tuple(itertools.chain.from_iterable(xtrsrc)),
                                commit=True)
        insert_num = cursor.rowcount
        #if insert_num == 0:
        #    logger.info("No forced-fit sources added to extractedsource for "
        #                "image %s" % (image_id,))
        if extract_type == 'blind':
            logger.debug("Inserted %d sources in extractedsource for image %s" %
                        (insert_num, image_id))
        elif extract_type == 'ff_nd':
            logger.debug("Inserted %d forced-fit null detections in extractedsource"
                        " for image %s" % (insert_num, image_id))
        elif extract_type == 'ff_ms':
            logger.debug("Inserted %d forced-fit for monitoring in extractedsource"
                        " for image %s" % (insert_num, image_id)) def frequency_bands(dataset_id):
    """Return a list of distinct bands present in the dataset."""
    query = """\
    SELECT DISTINCT(band)
      FROM image
     WHERE dataset = %s
    """
    cursor = tkp.db.execute(query, (dataset_id,))
    bands = zip(*cursor.fetchall())[0]
    return bands def runcat_entries(dataset_id):
    """
    Returns:
        (tuple): a list of dictionarys representing rows in runningcatalog,
        for all sources belonging to this dataset

        Column 'id' is returned with the key 'runcat'

        Currently only returns 3 columns:
        [{'runcat,'xtrsrc','datapoints'}]
    """
    return columns_from_table('runningcatalog',
                              keywords=['id', 'xtrsrc', 'datapoints'],
                              alias={'id': 'runcat'},
                              where={'dataset': dataset_id}) def delete_test_database(database):
def example_dbimage_data_dict(**kwargs):
def generate_timespaced_dbimages_data(n_images,
                           timedelta_between_images=datetime.timedelta(days=1),
                           **kwargs):
def example_extractedsource_tuple(ra=123.123, dec=10.5,  # Arbitrarily picked defaults
                                  ra_fit_err=5. / 3600, dec_fit_err=6. / 3600,
                                  peak=15e-3, peak_err=5e-4,
                                  flux=15e-3, flux_err=5e-4,
                                  sigma=15.,
                                  beam_maj=100., beam_min=100., beam_angle=45.,
                                  ew_sys_err=20., ns_sys_err=20.,
                                  error_radius=10.0, fit_type=0,
                                  chisq=5., reduced_chisq=1.5):
def deRuiter_radius(src1, src2):
def lightcurve_metrics(src_list):
    def __init__(self,
                 template_extractedsource,
                 lightcurve,
                 ):
    def value_at_dtime(self, dtime, image_rms):
    def simulate_extraction(self, db_image, extraction_type,
                            rms_attribute='rms_min'):
def insert_image_and_simulated_sources(dataset, image_params, mock_sources,
                                       new_source_sigma_margin,
                                       deruiter_radius=3.7):
def get_newsources_for_dataset(dsid):
def get_sources_filtered_by_final_variability(dataset_id,
                     eta_min,
                     v_min,
                     # minpoints
    ):
        N = i + 1
class MockSource(object): def example_extractedsource_tuple(ra=123.123, dec=10.5,  # Arbitrarily picked defaults
                                  ra_fit_err=5. / 3600, dec_fit_err=6. / 3600,
                                  peak=15e-3, peak_err=5e-4,
                                  flux=15e-3, flux_err=5e-4,
                                  sigma=15.,
                                  beam_maj=100., beam_min=100., beam_angle=45.,
                                  ew_sys_err=20., ns_sys_err=20.,
                                  error_radius=10.0, fit_type=0,
                                  chisq=5., reduced_chisq=1.5):
    """Generates an example 'fake extraction' for unit testing.

    Note that while RA and Dec are arbitrary, they should (usually) be close
    to the RA and Dec of any fake images used, since the association routines
    reject sources outside of designated extraction regions.
    """
    # NOTE: ra_fit_err & dec_fit_err are in degrees,
    # and ew_sys_err, ns_sys_err and error_radius are in arcsec.
    # The ew_uncertainty_ew is then the sqrt of the quadratic sum of the
    # systematic error and the error_radius
    return ExtractedSourceTuple(ra=ra, dec=dec,
                                ra_fit_err=ra_fit_err, dec_fit_err=dec_fit_err,
                                peak=peak, peak_err=peak_err,
                                flux=flux, flux_err=flux_err,
                                sigma=sigma,
                                beam_maj=beam_maj, beam_min=beam_min,
                                beam_angle=beam_angle,
                                ew_sys_err=ew_sys_err, ns_sys_err=ns_sys_err,
                                error_radius=error_radius, fit_type=fit_type,
                                chisq=chisq, reduced_chisq=reduced_chisq
    ) class MockSource(object):

    def __init__(self,
                 template_extractedsource,
                 lightcurve,
                 ):
        """

        Defines a MockSource for generating mock source lists.

        (These can be used to test the database routines.)

        The lightcurve-dict entries define the times of non-zero
        flux (we do not support time-ranges here, discretely defined datapoints are
        sufficiently complex for the current unit-test suite). In this case,
        any undefined datetimes requested will produce a zero-flux measurement.
        A defaultdict may be supplied to simulate a steady-flux source.

        Args:
            template_extractedsource (ExtractedSourceTuple): This defines
                everything **except** the flux and significance of the
                extraction (i.e. position, fit error, beam properties, etc.).
            lightcurve (dict): A dict mapping datetime -> flux value [Jy].
                Any undefined datetimes will produce a zero-flux measurement.
                A defaultdict with constant-valued default may be supplied to
                represent a steady source, e.g.

                    >>>MockSource(base_source, defaultdict(lambda:steady_flux_val))

        """
        self.base_source = template_extractedsource
        self.lightcurve = lightcurve

    def value_at_dtime(self, dtime, image_rms):
        """Returns an `extractedsource` for a given datetime.

        If lightcurve is defined but does not contain the requested datetime,
        then peak, flux, sigma are all set to zero.
        """
        try:
            fluxval = self.lightcurve[dtime]
        except KeyError:
            fluxval = 0
        return self.base_source._replace(
                peak=fluxval,flux=fluxval,sigma=fluxval/image_rms)

    def simulate_extraction(self, db_image, extraction_type,
                            rms_attribute='rms_min'):
        """
        Simulate extraction process, returns extracted source or none.

        Uses the database image properties (extraction region, rms values)
        to determine if this source would be extracted in the given image,
        and return an extraction or None accordingly.

        Args:
            db_image (int): Database Image object.
            extraction_type: Valid values are 'blind', 'ff_nd'. If 'blind'
                then we only return an extracted source if the flux is above
                rms_value * detection_threshold.
            rms_attribute (str): Valid values are 'rms_min', 'rms_max'.
                Determines which rms value we use when deciding if this source
                will be seen in a blind extraction.

        Returns:
            ExtractedSourceTuple or None.
        """

        rms = getattr(db_image, rms_attribute)
        ex = self.value_at_dtime(db_image.taustart_ts, rms)

        #First check if source is in this image's extraction region:
        src_distance_degrees = coords.angsep(
            ex.ra, ex.dec,db_image.centre_ra, db_image.centre_decl) / 3600.0
        if src_distance_degrees > db_image.xtr_radius:
            return None

        if extraction_type == 'ff_nd':
            return ex
        elif extraction_type == 'blind':
            if ex.sigma > db_image.detection_thresh:
                return ex
            else:
                return None
        else:
            raise ValueError("Unrecognised extraction type: {}".format(
                                                            extraction_type)) def insert_image_and_simulated_sources(dataset, image_params, mock_sources,
                                       new_source_sigma_margin,
                                       deruiter_radius=3.7):
    """
    Simulates the standard database image-and-source insertion logic using mock
    sources.

    Args:
        dataset: The dataset object
        image_params (dict): Contains the image properties.
        mock_sources (list of MockSource): The mock sources to simulate.
        new_source_sigma_margin (float): Parameter passed to source-association
            routines.
        deruiter_radius (float): Parameter passed to source-association
            routines.

    Returns:
        3-tuple (image, list of blind extractions, list of forced fits).

    """
    image = tkp.db.Image(data=image_params, dataset=dataset)
    blind_extractions=[]
    for src in mock_sources:
        xtr = src.simulate_extraction(image, extraction_type='blind')
        if xtr is not None:
            blind_extractions.append(xtr)
    insert_extracted_sources(image._id, blind_extractions, 'blind')
    associate_extracted_sources(image._id, deRuiter_r=deruiter_radius,
                                new_source_sigma_margin=new_source_sigma_margin)
    nd_ids_posns = nulldetections.get_nulldetections(image.id)
    nd_posns = [(ra,decl) for ids, ra, decl in nd_ids_posns]
    forced_fits = []
    for posn in nd_posns:
        for src in mock_sources:
            eps = 1e-13
            if (math.fabs(posn[0] - src.base_source.ra)<eps and
                        math.fabs(posn[1] - src.base_source.dec)<eps ):
                forced_fits.append(
                    src.simulate_extraction(image,extraction_type='ff_nd')
                )
    if len(nd_posns) != len(forced_fits):
        raise LookupError("Something went wrong, nulldetection position did "
                          "not match a mock source.")
    #image.insert_extracted_sources(forced_fits, 'ff_nd')
    dbgen.insert_extracted_sources(image.id, forced_fits, 'ff_nd',
                   ff_runcat_ids=[ids for ids, ra, decl in nd_ids_posns])
    nulldetections.associate_nd(image.id)

    return image, blind_extractions, forced_fits def get_newsources_for_dataset(dsid):
    """
    Returns dicts representing all newsources for this dataset.

    Args:
        dsid: Dataset id

    Returns:
        tuple: (list of dicts) Each dict represents one newsource.
            The dict keys are all the columns in the newsources table, plus
            the 'taustart_ts' from the image table, which represents the
            trigger time.
    """
    qry = """\
    SELECT tr.id
          ,tr.previous_limits_image
          ,rc.id as runcat_id
          ,img.taustart_ts
          ,img.band
          ,ax.v_int
          ,ax.eta_int
          , ((ex.f_peak - limits_image.detection_thresh*limits_image.rms_min)
               / limits_image.rms_min) AS low_thresh_sigma
           , ((ex.f_peak - limits_image.detection_thresh*limits_image.rms_max)
               / limits_image.rms_max) AS high_thresh_sigma
      FROM newsource tr
          ,runningcatalog rc
          ,extractedsource ex
          ,image img
          ,assocxtrsource ax
          ,image limits_image
      WHERE rc.dataset = %(dsid)s
        AND tr.runcat = rc.id
        AND tr.trigger_xtrsrc = ex.id
        AND ex.image = img.id
        AND ax.runcat = rc.id
        AND ax.xtrsrc = ex.id
        AND tr.previous_limits_image = limits_image.id
    """
    cursor = Database().cursor
    cursor.execute(qry, {'dsid':dsid})
    newsource_rows_for_dataset = get_db_rows_as_dicts(cursor)
    return newsource_rows_for_dataset def get_sources_filtered_by_final_variability(dataset_id,
                     eta_min,
                     v_min,
                     # minpoints
    ):
    """
    Search the database to find high-variability lightcurves.

    Uses the variability associated with the last datapoint in a lightcurve
    as the key criteria.

    Args:
        dataset_id (int): Dataset to search
        eta_min (float): Minimum value of eta-index to return.
        v_min (float): Minimum value of V-index to return.

    Returns:
        tuple: (list of dicts) Each dict represents a runningcatalog_flux entry
            matching the filter criteria.

    """

    query = """\
SELECT rc.id as runcat_id
      ,image.band
      ,ax.v_int
      ,ax.eta_int
FROM runningcatalog as rc
    JOIN assocxtrsource as ax ON ax.runcat = rc.id
    JOIN extractedsource as ex ON ax.xtrsrc = ex.id
    JOIN image ON ex.image = image.id
    JOIN (
        -- Determine which are the most recent variability values
        -- for each lightcurve.
        SELECT
            a.runcat as runcat_id,
            i.band as band,
            max(i.taustart_ts) as MaxTimestamp
        FROM
            assocxtrsource a
            JOIN extractedsource e ON a.xtrsrc = e.id
            JOIN image i ON e.image = i.id
        GROUP BY
            runcat_id, band
        ) last_timestamps
    ON  rc.id = last_timestamps.runcat_id
    AND image.band = last_timestamps.band
    AND image.taustart_ts = last_timestamps.MaxTimestamp
WHERE rc.dataset = %(dataset_id)s
  AND eta_int >= %(eta_min)s
  AND v_int >= %(v_min)s
"""
    cursor = tkp.db.Database().cursor
    cursor.execute(query, {'dataset_id': dataset_id,
                           'eta_min':eta_min,
                           'v_min':v_min,
                           })
    transients = get_db_rows_as_dicts(cursor)

    return transients def requires_database():
    if database_disabled():
        return unittest.skip("Database functionality disabled in configuration")
    return lambda func: func
import unittest
import tkp.db
from collections import defaultdict
from tkp.db.associations import associate_extracted_sources
from tkp.db.general import insert_extracted_sources, frequency_bands, runcat_entries
from tkp.testutil import db_subs
from tkp.testutil.db_subs import (example_extractedsource_tuple,
                                  MockSource,
                                  insert_image_and_simulated_sources,
                                  get_newsources_for_dataset,
                                  get_sources_filtered_by_final_variability)
from tkp.testutil.decorators import requires_database

# Convenient default values
deRuiter_r = 3.7


class TestSimplestCases(unittest.TestCase):
    """
    Various basic test-cases of the transient-detection logic.

    In these simple cases we just have one source, fixed image properties,
    and identical min/max image RMS values.

    As a result, we can only test for type 1 / 2 transients here, i.e.
    -type 1: single-epoch, bright enough that we would previously have seen it
        wherever in the image it lies, or
    -type 2: multi-epoch, identified by variability, possibly from forced-fits.

    (Type 0 is a possible single-epoch transient, that might just be a steady
    source fluctuating in a high-RMS region, or might be real transient
    if it's seen in the low-RMS region).

    """

    @requires_database()
    def setUp(self):
        self.database = tkp.db.Database()

        self.dataset = tkp.db.DataSet(database=self.database,
                                data={'description':"Trans:"
                                        + self._testMethodName})
        self.n_images = 4
        self.new_source_sigma_margin = 3
        image_rms = 1e-3
        detection_thresh=10

        self.search_params = dict(eta_min=1,
                                  v_min=0.1,
                                  # minpoints=1,
                                  )

        self.barely_detectable_flux = 1.01*image_rms*(detection_thresh)
        self.reliably_detectable_flux = (
            1.01*image_rms*(detection_thresh+self.new_source_sigma_margin))

        # 1mJy image RMS, 10-sigma detection threshold = 10mJy threshold.
        test_specific_img_params = dict(rms_qc = image_rms,
                                rms_min = image_rms,
                                rms_max = image_rms,
                                detection_thresh = detection_thresh)

        self.im_params = db_subs.generate_timespaced_dbimages_data(
            self.n_images,**test_specific_img_params)

    def tearDown(self):
        tkp.db.rollback()

    def test_steady_source(self):
        """
        Sanity check: Ensure we get no newsource table entries for a steady
        source.
        """
        im_params = self.im_params
        steady_src = db_subs.MockSource(

             template_extractedsource=db_subs.example_extractedsource_tuple(