Пример #1
0
def build_matched_dataset(repo,
                          dataIds,
                          matchRadius=None,
                          safeSnr=50.,
                          useJointCal=False,
                          skipTEx=False):
    blob = Blob('MatchedMultiVisitDataset')

    if not matchRadius:
        matchRadius = afwGeom.Angle(1, afwGeom.arcseconds)

    # Extract single filter
    blob['filterName'] = Datum(quantity=set([dId['filter']
                                             for dId in dataIds]).pop(),
                               description='Filter name')

    # Record important configuration
    blob['useJointCal'] = Datum(
        quantity=useJointCal,
        description='Whether jointcal/meas_mosaic calibrations were used')

    # Match catalogs across visits
    blob._catalog, blob._matchedCatalog = \
        _loadAndMatchCatalogs(repo, dataIds, matchRadius,
                                   useJointCal=useJointCal, skipTEx=False)

    blob.magKey = blob._matchedCatalog.schema.find("base_PsfFlux_mag").key
    # Reduce catalogs into summary statistics.
    # These are the serialiable attributes of this class.
    _reduceStars(blob, blob._matchedCatalog, safeSnr)
    return blob
Пример #2
0
def _compute(blob, bright, mag, magErr, magRms, medianRef, matchRef):
    nMatch = np.sum(bright)
    blob['photScatter'] = Datum(
        quantity=np.median(magRms[bright]),
        label='RMS',
        description='RMS photometric scatter for good stars')
    print('Photometric scatter (median) - SNR > {0:.1f} : {1:.1f}'.format(
        blob['brightSnrMin'].quantity,
        blob['photScatter'].quantity.to(u.mmag)))

    fit_params = fitPhotErrModel(mag[bright], magErr[bright])
    blob['sigmaSys'] = Datum(quantity=fit_params['sigmaSys'],
                             label='sigma(sys)',
                             description='Systematic error floor')
    blob['gamma'] = Datum(
        quantity=fit_params['gamma'],
        label='gamma',
        description='Proxy for sky brightness and read noise')
    blob['m5'] = Datum(quantity=fit_params['m5'],
                       label='m5',
                       description='5-sigma depth')

    if blob['photScatter'].quantity > medianRef:
        msg = 'Median photometric scatter {0:.3f} is larger than ' \
              'reference : {1:.3f}'
        print(msg.format(blob['photScatter'].quantity.value, medianRef))
    if nMatch < matchRef:
        msg = 'Number of matched sources {0:d} is too small ' \
              '(should be > {0:d})'
        print(msg.format(nMatch, matchRef))
Пример #3
0
def _compute(blob, bright, snr, dist, medianRef, matchRef):
    nMatch = len(bright)
    median_dist = np.median(dist)
    msg = 'Median value of the astrometric scatter - all magnitudes: ' \
          '{0:.3f}'
    print(msg.format(median_dist))

    astromScatter = np.median(dist[bright])
    msg = 'Astrometric scatter (median) - snr > {0:.1f} : {1:.1f}'
    print(msg.format(blob['brightSnrMin'].quantity, astromScatter))

    fit_params = fitAstromErrModel(snr[bright], dist[bright])

    if astromScatter > medianRef:
        msg = 'Median astrometric scatter {0:.1f} is larger than ' \
              'reference : {1:.1f}'
        print(msg.format(astromScatter, medianRef))
    if nMatch < matchRef:
        msg = 'Number of matched sources {0:d} is too small ' \
              '(should be > {1:d})'
        print(msg.format(nMatch, matchRef))

    blob['C'] = Datum(quantity=fit_params['C'], description='Scaling factor')
    blob['theta'] = Datum(quantity=fit_params['theta'],
                          label='theta',
                          description='Seeing')
    blob['sigmaSys'] = Datum(quantity=fit_params['sigmaSys'],
                             label='sigma(sys)',
                             description='Systematic error floor')
    blob['astromRms'] = Datum(
        quantity=astromScatter,
        label='RMS',
        description='Astrometric scatter (RMS) for good stars')
Пример #4
0
    def test_quantity_update(self):
        """Verify that when a quantity is updated the unit attributes
        are updated.
        """
        d = Datum(5 * u.mag)
        self.assertEqual(d.quantity.value, 5.)
        self.assertEqual(d.unit_str, 'mag')

        d.quantity = 100. * u.mmag
        self.assertEqual(d.quantity.value, 100.)
        self.assertEqual(d.unit_str, 'mmag')
Пример #5
0
def measurePhotRepeat(metric, filterName, *args, **kwargs):
    """Measurement of a photometric repeatability metric across a set of
    observations.

    Parameters
    ----------
    metric : `lsst.verify.Metric`
        A Metric to construct a Measurement for.
    filterName : `str`
        Name of filter used for all observations.
    *args
        Additional arguments to pass to `calcPhotRepeat`.
    **kwargs
        Additional keyword arguments to pass to `calcPhotRepeat`.

    Returns
    -------
    measurement : `lsst.verify.Measurement`
        Measurement of the repeatability and its associated metadata.

    See also
    --------
    calcPhotRepeat: Computes statistics of magnitudes differences of sources across
        multiple visits. This is the main computation function behind
        repeatability measurement.
    """
    results = calcPhotRepeat(*args, **kwargs)
    datums = {}
    datums['filter_name'] = Datum(
        filterName,
        label='filter',
        description='Name of filter for this measurement')
    datums['rms'] = Datum(
        results['rms'],
        label='RMS',
        description='Photometric repeatability RMS of stellar pairs for '
        'each random sampling')
    datums['iqr'] = Datum(
        results['iqr'],
        label='IQR',
        description='Photometric repeatability IQR of stellar pairs for '
        'each random sample')
    datums['magDiff'] = Datum(
        results['magDiff'],
        label='Delta mag',
        description='Photometric repeatability differences magnitudes for '
        'stellar pairs for each random sample')
    datums['magMean'] = Datum(
        results['magMean'],
        label='mag',
        description='Mean magnitude of pairs of stellar sources matched '
        'across visits, for each random sample.')
    return Measurement(metric, results['repeatability'], extras=datums)
Пример #6
0
    def test_json_output(self):
        """Verify content from json property and deserialization."""
        d = Datum(5., 'mmag', label='millimag', description='Hello world')
        dj = d.json

        self.assertEqual(d.quantity.value, dj['value'])
        self.assertEqual(d.unit_str, dj['unit'])
        self.assertEqual(d.label, dj['label'])
        self.assertEqual(d.description, dj['description'])

        new_datum = Datum.deserialize(**dj)
        self.assertEqual(d, new_datum)
Пример #7
0
def summarizeSources(blob, filterResult):
    """Calculate summary statistics for each source. These are persisted
    as object attributes.

    Parameters
    ----------
    blob : `lsst.verify.blob.Blob`
        A verification blob to store Datums in.
    filterResult : `lsst.pipe.base.Struct`
        A struct containing bright and faint filter matches, as returned by `filterSources`.
    """
    # Pass field=psfMagKey so np.mean just gets that as its input
    typeMag = "model" if filterResult.extended else "PSF"
    filter_name = blob['filterName']
    source_type = f'{"extended" if filterResult.extended else "point"} sources"'
    matches = filterResult.matchesFaint
    keys = filterResult.keys
    blob['snr'] = Datum(
        quantity=matches.aggregate(np.median, field=keys.snr) * u.Unit(''),
        label='SNR({band})'.format(band=filter_name),
        description=
        f'Median signal-to-noise ratio of {typeMag} magnitudes for {source_type}'
        f' over multiple visits')
    blob['mag'] = Datum(
        quantity=matches.aggregate(np.mean, field=keys.mag) * u.mag,
        label='{band}'.format(band=filter_name),
        description=
        f'Mean of {typeMag} magnitudes for {source_type} over multiple visits')
    blob['magrms'] = Datum(
        quantity=matches.aggregate(np.std, field=keys.mag) * u.mag,
        label='RMS({band})'.format(band=filter_name),
        description=
        f'RMS of {typeMag} magnitudes for {source_type} over multiple visits')
    blob['magerr'] = Datum(
        quantity=matches.aggregate(np.median, field=keys.magErr) * u.mag,
        label='sigma({band})'.format(band=filter_name),
        description=
        f'Median 1-sigma uncertainty of {typeMag} magnitudes for {source_type}'
        f' over multiple visits')
    # positionRmsFromCat knows how to query a group
    # so we give it the whole thing by going with the default `field=None`.
    blob['dist'] = Datum(
        quantity=matches.aggregate(positionRmsFromCat) * u.milliarcsecond,
        label='d',
        description=
        f'RMS of sky coordinates of {source_type} over multiple visits')

    # These attributes are not serialized
    blob.matchesFaint = filterResult.matchesFaint
    blob.matchesBright = filterResult.matchesBright
Пример #8
0
    def setUp(self):
        mag1 = Datum(quantity=5 * u.mag, label='mag1', description='Magnitude')
        mag2 = Datum(quantity=10 * u.mag,
                     label='mag2',
                     description='Magnitude')
        self.blob1 = Blob('blob1', mag1=mag1, mag2=mag2)

        sep1 = Datum(quantity=5 * u.arcsec,
                     label='sep1',
                     description='Separation')
        sep2 = Datum(quantity=10 * u.arcsec,
                     label='sep2',
                     description='Separation')
        self.blob2 = Blob('blob2', sep1=sep1, sep2=sep2)
Пример #9
0
    def test_unitless(self):
        """Ensure that Datums can be unitless too."""
        d = Datum(5., '')
        self.assertEqual(d.unit_str, '')
        self.assertEqual(d.unit, u.dimensionless_unscaled)

        self._checkRoundTrip(d)
Пример #10
0
    def test_repr(self):
        metric = 'test.cmodel_mag'
        self.assertEqual(repr(Measurement(metric)),
                         "Measurement('test.cmodel_mag', None)")
        value = 1235 * u.mag
        self.assertEqual(
            repr(Measurement(metric, value)),
            "Measurement('test.cmodel_mag', <Quantity 1235. mag>)")

        self.assertEqual(
            repr(Measurement(metric, value, [self.blob1])),
            "Measurement('test.cmodel_mag', <Quantity 1235. mag>, "
            f"blobs=[{self.blob1!r}])")

        notes = {metric + '.filter_name': 'r'}
        extras = {'extra1': Datum(10. * u.arcmin, 'Extra 1')}
        self.assertEqual(
            repr(
                Measurement(metric,
                            value,
                            notes=notes,
                            blobs=[self.blob1],
                            extras=extras)),
            "Measurement('test.cmodel_mag', <Quantity 1235. mag>, "
            f"blobs=[{self.blob1!r}], extras={extras!r}, notes={notes!r})")
Пример #11
0
    def makeMeasurement(self, timings):
        """Compute a wall-clock measurement from metadata provided by
        `lsst.utils.timer.timeMethod`.

        Parameters
        ----------
        timings : `dict` [`str`, any]
            A representation of the metadata passed to `run`. The `dict` has
            the following keys:

             ``"StartTime"``
                 The time the target method started (`float` or `None`).
             ``"EndTime"``
                 The time the target method ended (`float` or `None`).
             ``"StartTimestamp"``, ``"EndTimestamp"``
                 The start and end timestamps, in an ISO 8601-compliant format
                 (`str` or `None`).

        Returns
        -------
        measurement : `lsst.verify.Measurement` or `None`
            The running time of the target method.

        Raises
        ------
        MetricComputationError
            Raised if the timing metadata are invalid.
        """
        if timings["StartTime"] is not None or timings["EndTime"] is not None:
            try:
                totalTime = timings["EndTime"] - timings["StartTime"]
            except TypeError:
                raise MetricComputationError("Invalid metadata")
            else:
                meas = Measurement(self.config.metricName,
                                   totalTime * u.second)
                meas.notes["estimator"] = "utils.timer.timeMethod"
                if timings["StartTimestamp"]:
                    meas.extras["start"] = Datum(timings["StartTimestamp"])
                if timings["EndTimestamp"]:
                    meas.extras["end"] = Datum(timings["EndTimestamp"])
                return meas
        else:
            self.log.info("Nothing to do: no timing information for %s found.",
                          self.config.target)
            return None
Пример #12
0
def measurePA1(metric, matchedDataset, filterName, numRandomShuffles=50):
    """Measurement of the PA1 metric: photometric repeatability of
    measurements across a set of observations.

    Parameters
    ----------
    metric : `lsst.verify.Metric`
        A PA1 `~lsst.verify.Metric` instance.
    matchedDataset : `lsst.verify.Blob`
        This contains the spacially matched catalog to do the measurement.
    filterName : str
        filter name used in this measurement (e.g., `'r'`)
    numRandomShuffles : int
        Number of times to draw random pairs from the different observations.

    Returns
    -------
    measurement : `lsst.verify.Measurement`
        Measurement of PA1 and associated metadata.

    See also
    --------
    calcPa1: Computes statistics of magnitudes differences of stars across
        multiple visits. This is the main computation function behind
        the PA1 measurement.
    """

    matches = matchedDataset.safeMatches
    magKey = matchedDataset.magKey
    results = calcPa1(matches, magKey, numRandomShuffles=numRandomShuffles)
    datums = {}
    datums['filter_name'] = Datum(filterName, label='filter',
                                  description='Name of filter for this measurement')
    datums['rms'] = Datum(results['rms'], label='RMS',
                          description='Photometric repeatability RMS of stellar pairs for '
                          'each random sampling')
    datums['iqr'] = Datum(results['iqr'], label='IQR',
                          description='Photometric repeatability IQR of stellar pairs for '
                          'each random sample')
    datums['magDiff'] = Datum(results['magDiff'], label='Delta mag',
                              description='Photometric repeatability differences magnitudes for '
                              'stellar pairs for each random sample')
    datums['magMean'] = Datum(results['magMean'], label='mag',
                              description='Mean magnitude of pairs of stellar sources matched '
                              'across visits, for each random sample.')
    return Measurement(metric, results['PA1'], extras=datums)
Пример #13
0
    def test_deferred_extras(self):
        """Test adding extras to an existing measurement."""
        measurement = Measurement(self.pa1, 5. * u.mmag)

        self.assertIn(str(self.pa1.name), measurement.blobs)

        measurement.extras['extra1'] = Datum(10. * u.arcmin, 'Extra 1')
        self.assertIn('extra1', measurement.extras)
Пример #14
0
 def test_yamlpersist_complex(self):
     measurement = Measurement(
         self.pa1,
         5. * u.mmag,
         notes={'filter_name': 'r'},
         blobs=[self.blob1],
         extras={'extra1': Datum(10. * u.arcmin, 'Extra 1')})
     self._check_yaml_round_trip(measurement)
Пример #15
0
    def setUp(self):
        self.pa1 = Metric(
            'validate_drp.PA1',
            "The maximum rms of the unresolved source magnitude distribution "
            "around the mean value (repeatability).",
            'mmag',
            tags=['photometric', 'LPM-17'],
            reference_doc='LPM-17',
            reference_url='http://ls.st/lpm-17',
            reference_page=21)

        self.blob1 = Blob('Blob1')
        self.blob1['datum1'] = Datum(5 * u.arcsec, 'Datum 1')
        self.blob1['datum2'] = Datum(28. * u.mag, 'Datum 2')

        self.blob2 = Blob('Blob2')
        self.blob2['datumN'] = Datum(11 * u.dimensionless_unscaled, 'Count')
Пример #16
0
    def test_bool_quantity(self):
        """Quantity as a boolean."""
        d = Datum(True, label='Test boolean', description='Test description.')
        self.assertTrue(d.quantity)
        self.assertIsNone(d.unit)
        self.assertEqual(d.unit_str, '')
        self.assertEqual(d.label, 'Test boolean')
        self.assertEqual(d.description, 'Test description.')

        self._checkRoundTrip(d)
Пример #17
0
    def _checkRoundTrip(self, d):
        """Test that a Datum can be serialized and restored.
        """
        json_data = d.json
        d2 = Datum.deserialize(**json_data)
        self._assertDatumsEqual(d, d2)

        yaml_data = yaml.dump(d)
        d3 = yaml.safe_load(yaml_data)
        self._assertDatumsEqual(d, d3)
Пример #18
0
    def test_int_quantity(self):
        """Quantity as a unitless int."""
        d = Datum(5, label='Test int', description='Test description.')
        self.assertEqual(d.quantity, 5)
        self.assertIsNone(d.unit)
        self.assertEqual(d.unit_str, '')
        self.assertEqual(d.label, 'Test int')
        self.assertEqual(d.description, 'Test description.')

        self._checkRoundTrip(d)
Пример #19
0
    def test_none(self):
        """Quantity as None."""
        d = Datum(None, label='Test None', description='Test description.')
        self.assertIsNone(d.quantity)
        self.assertIsNone(d.unit)
        self.assertEqual(d.unit_str, '')
        self.assertEqual(d.label, 'Test None')
        self.assertEqual(d.description, 'Test description.')

        self._checkRoundTrip(d)
Пример #20
0
    def test_str_quantity(self):
        """Quantity as a string."""
        d = Datum('Hello world',
                  label='Test string',
                  description='Test description.')
        self.assertEqual(d.quantity, 'Hello world')
        self.assertIsNone(d.unit)
        self.assertEqual(d.unit_str, '')
        self.assertEqual(d.label, 'Test string')
        self.assertEqual(d.description, 'Test description.')

        self._checkRoundTrip(d)
Пример #21
0
    def test_properties(self):
        """Validate basic setters and getters."""
        d = Datum(5., 'mmag', label='millimag', description='Hello world')

        self.assertIsInstance(d.quantity, u.Quantity)

        self.assertEqual(d.quantity.value, 5.)

        d.quantity = 7 * u.mmag
        self.assertEqual(d.quantity.value, 7)

        self.assertEqual(d.unit_str, 'mmag')
        self.assertEqual(d.unit, u.mmag)

        # change units
        d.quantity = 5 * u.mag
        self.assertEqual(d.unit, u.mag)

        self.assertEqual(d.label, 'millimag')
        d.label = 'magnitudes'
        self.assertEqual(d.label, 'magnitudes')

        self.assertEqual(d.description, 'Hello world')
        d.description = 'Updated description.'
        self.assertEqual(d.description, 'Updated description.')
Пример #22
0
    def setUp(self):
        # Mock metrics
        self.metric_photrms = Metric('test.PhotRms', 'Photometric RMS', 'mmag')
        self.metric_photmed = Metric('test.PhotMedian', 'Median magntidue',
                                     'mag')
        self.metric_set = MetricSet([self.metric_photrms, self.metric_photmed])

        # Mock specifications
        self.spec_photrms_design = ThresholdSpecification(
            'test.PhotRms.design', 20. * u.mmag, '<')
        self.spec_set = SpecificationSet([self.spec_photrms_design])

        # Mock measurements
        self.meas_photrms = Measurement(self.metric_photrms,
                                        15 * u.mmag,
                                        notes={'note': 'value'})
        self.meas_photrms.extras['n_stars'] = Datum(
            250,
            label='N stars',
            description='Number of stars included in RMS estimate')
        self.measurement_set = MeasurementSet([self.meas_photrms])

        # Metrics for Job 2
        self.metric_test_2 = Metric('test2.SourceCount', 'Source Count', '')
        self.blob_test_2 = Blob('test2_blob',
                                sn=Datum(50 * u.dimensionless_unscaled,
                                         label='S/N'))
        self.metric_set_2 = MetricSet([self.metric_test_2])

        # Specifications for Job 2
        self.spec_test_2 = ThresholdSpecification(
            'test2.SourceCount.design', 100 * u.dimensionless_unscaled, '>=')
        self.spec_set_2 = SpecificationSet([self.spec_test_2])

        # Measurements for Job 2
        self.meas_test_2_SourceCount = Measurement(
            self.metric_test_2, 200 * u.dimensionless_unscaled)
        self.meas_test_2_SourceCount.link_blob(self.blob_test_2)
        self.measurement_set_2 = MeasurementSet([self.meas_test_2_SourceCount])
    def run(self, matchedCatalog, metric_name):
        self.log.info(f"Measuring {metric_name}")

        filteredCat = filterMatches(matchedCatalog)

        magRange = np.array([self.config.bright_mag_cut, self.config.faint_mag_cut]) * u.mag
        D = self.config.annulus_r * u.arcmin
        width = self.config.width * u.arcmin
        annulus = D + (width/2)*np.array([-1, +1])

        rmsDistances = calcRmsDistances(
            filteredCat,
            annulus,
            magRange=magRange)

        values, bins = np.histogram(rmsDistances.to(u.marcsec), bins=self.config.bins*u.marcsec)
        extras = {'bins': Datum(bins, label='binvalues', description='bins'),
                  'values': Datum(values*u.count, label='counts', description='icounts in bins')}

        if len(rmsDistances) == 0:
            return Struct(measurement=Measurement(metric_name, np.nan*u.marcsec, extras=extras))

        return Struct(measurement=Measurement(metric_name, np.median(rmsDistances.to(u.marcsec)),
                                              extras=extras))
Пример #24
0
    def calc_wPerp(self, phot, extinction_vals, metric_name):
        p1, p2, p1coeffs, p2coeffs = stellarLocusResid(
            phot['base_PsfFlux_mag_g'] - extinction_vals['A_g'],
            phot['base_PsfFlux_mag_r'] - extinction_vals['A_r'],
            phot['base_PsfFlux_mag_i'] - extinction_vals['A_i'])

        if np.size(p2) > 2:
            p2_rms = calcQuartileClippedStats(p2).rms * u.mag
            extras = {
                'p1_coeffs':
                Datum(p1coeffs * u.Unit(''),
                      label='p1_coefficients',
                      description='p1 coeffs from wPerp fit'),
                'p2_coeffs':
                Datum(p2coeffs * u.Unit(''),
                      label='p2_coefficients',
                      description='p2_coeffs from wPerp fit')
            }

            return Struct(measurement=Measurement(
                metric_name, p2_rms.to(u.mmag), extras=extras))
        else:
            return Struct(measurement=Measurement(metric_name, np.nan *
                                                  u.mmag))
Пример #25
0
    def test_creation_with_extras(self):
        """Test creating a measurement with an extra."""
        measurement = Measurement(
            self.pa1,
            5. * u.mmag,
            extras={'extra1': Datum(10. * u.arcmin, 'Extra 1')})

        self.assertIn(str(self.pa1.name), measurement.blobs)
        self.assertIn('extra1', measurement.extras)

        json_doc = measurement.json
        self.assertIn(measurement.extras.identifier, json_doc['blob_refs'])

        blobs = BlobSet([b for k, b in measurement.blobs.items()])
        new_measurement = Measurement.deserialize(blobs=blobs, **json_doc)
        self.assertIn('extra1', new_measurement.extras)
        self.assertEqual(measurement, new_measurement)
Пример #26
0
def measurePA2(metric, pa1, pf1_thresh):
    """Measurement of PA2: millimag from median RMS (see PA1) of which
    PF1 of the samples can be found.

    Parameters
    ----------
    metric : `lsst.verify.Metric`
        A PA2 `~lsst.verify.Metric` instance.
    pa1 : `lsst.verify.Measurement`
        A PA1 measurement instance.
    pf1_thresh : `astropy.units.Quantity`
        Quantity specifying the threshold at which to calculate PA2

    Returns
    -------
    measurement : `lsst.verify.Measurement`
        Measurement of PA2 and associated metadata.

    Notes
    -----
    The LSST Science Requirements Document (LPM-17) is commonly referred
    to as the SRD.  The SRD puts a limit that no more than PF1 % of difference
    will vary by more than PA2 millimag.  The design, minimum, and stretch
    goals are PF1 = (10, 20, 5) % at PA2 = (15, 15, 10) millimag following
    LPM-17 as of 2011-07-06, available at http://ls.st/LPM-17.
    """

    datums = {}
    datums['pf1_thresh'] = Datum(
        quantity=pf1_thresh,
        description="Threshold from the PF1 specification")

    # Use first random sample from original PA1 measurement
    magDiffs = pa1.extras['magDiff'].quantity[0, :]

    pf1Percentile = 100. * u.percent - pf1_thresh
    return Measurement(
        metric,
        np.percentile(np.abs(magDiffs.value), pf1Percentile.value) *
        magDiffs.unit,
        extras=datums)
Пример #27
0
def measurePF1(metric, pa1, pa2_spec):
    """Measurement of PF1: fraction of samples between median RMS (PA1) and
    PA2 specification.

    Parameters
    ----------
    metric : `lsst.verify.Metric`
        A PF1 `~lsst.verify.Metric` instance.
    pa1 : `lsst.verify.Measurement`
        A PA1 measurement instance.
    pa2_spec : `lsst.verify.Spec`
        An `lsst.verify.Spec` that holds the threshold at which to measure PF1

    Returns
    -------
    measurement : `lsst.verify.Measurement`
        Measurement of PF1 and associated metadata.

    Notes
    -----
    The LSST Science Requirements Document (LPM-17) is commonly referred
    to as the SRD.  The SRD puts a limit that no more than PF1 % of difference
    will vary by more than PA2 millimag.  The design, minimum, and stretch
    goals are PF1 = (10, 20, 5) % at PA2 = (15, 15, 10) millimag following
    LPM-17 as of 2011-07-06, available at http://ls.st/LPM-17.
    """

    datums = {}
    datums['pa2_spec'] = Datum(quantity=pa2_spec.threshold,
                               description="Threshold applied to PA2")
    # Use first random sample from original PA1 measurement
    magDiff = pa1.extras['magDiff'].quantity
    magDiffs = magDiff[0, :]

    quantity = 100 * np.mean(
        np.abs(magDiffs) > pa2_spec.threshold) * u.Unit('percent')
    return Measurement(metric, quantity, extras=datums)
Пример #28
0
def measureTEx(metric, matchedDataset, D, bin_range_operator, verbose=False):
    """Measurement of TEx (x=1,2): Correlation of PSF residual ellipticity
    on scales of D=(1, 5) arcmin.

    Parameters
    ----------
    metric : `lsst.verify.Metric`
        An TE1 or TE2 `~lsst.verify.Metric` instance.
    matchedDataset : lsst.verify.Blob
        The matched catalogs to analyze.
    D : `astropy.units.Quantity`
        Radial size of annulus in arcmin
    bin_range_operator : str
        String representation to use in comparisons
    verbose : `bool`, optional
        Output additional information on the analysis steps.

    Returns
    -------
    measurement : `lsst.verify.Measurement`
        Measurement of TEx (x=1,2) and associated metadata.

    Notes
    -----
    The TEx table below is provided in ``validate_drp``\ 's :file:`metrics.yaml`.

    LPM-17 dated 2011-07-06

    Specification:
        Using the full survey data, the E1, E2, and EX residual PSF ellipticity
        correlations averaged over an arbitrary FOV must have the median
        less than TE1 for theta <= 1 arcmin, and less than TE2 for theta >= 5 arcmin.

    The residual ellipticity correlations vary smoothly so it is sufficient to
    specify limits in these two angular ranges. On 1 arcmin to 5 arcmin scales,
    these residual ellipticity correlations put LSST systematics a factor of a
    few below the weak lensing shot noise, i.e., statistical errors will
    dominate over systematics. On larger scales, the noise level imposed by
    nature due to shot noise plus cosmic variance is almost scale-independent,
    whereas the atmospheric contribution to systematics becomes negligible.
    Therefore the specifications on 5 arcmin scales apply to all larger scales
    as well (as per section 2.1.1). On scales larger than the field of view,
    sources of systematic error have less to do with the instrumentation than
    with the operations (due to the seeing distribution), software, and algorithms.

    ========================= ====== ======= =======
    PSF Ellipticity Residuals     Specification
    ------------------------- ----------------------
                       Metric Design Minimum Stretch
    ========================= ====== ======= =======
            TE1 ()             2e-5    3e-5   1e-5
            TE2 (%)            1e-7    3e-7   5e-8
            TEF (%)             15      15     10
            TE3 ()             4e-5    6e-5   2e-5
            TE4 ()             2e-7    5e-7   1e-7
    ========================= ====== ======= =======


    Table 27: These residual PSF ellipticity correlations apply to the r and i bands.
    """

    matches = matchedDataset.safeMatches

    datums = {}
    datums['D'] = Datum(quantity=D, description="Separation distance")

    radius, xip, xip_err = correlation_function_ellipticity_from_matches(matches, verbose=verbose)
    datums['radius'] = Datum(quantity=radius, description="Correlation radius")
    datums['xip'] = Datum(quantity=xip, description="Correlation strength")
    datums['xip_err'] = Datum(quantity=xip_err, description="Correlation strength uncertainty")
    datums['bin_range_operator'] = Datum(quantity=bin_range_operator, description="Bin range operator string")

    operator = ThresholdSpecification.convert_operator_str(bin_range_operator)
    corr, corr_err = select_bin_from_corr(radius, xip, xip_err, radius=D, operator=operator)
    quantity = np.abs(corr) * u.Unit('')
    return Measurement(metric, quantity, extras=datums)
Пример #29
0
def build_matched_dataset(repo,
                          dataIds,
                          matchRadius=None,
                          safeSnr=50.,
                          useJointCal=False,
                          skipTEx=False):
    """Construct a container for matched star catalogs from multple visits, with filtering,
    summary statistics, and modelling.

    `lsst.verify.Blob` instances are serializable to JSON.

    Parameters
    ----------
    repo : `str` or `Butler`
        A Butler instance or a repository URL that can be used to construct
        one.
    dataIds : `list` of `dict`
        List of `butler` data IDs of Image catalogs to compare to reference.
        The `calexp` cpixel image is needed for the photometric calibration.
    matchRadius :  afwGeom.Angle(), optional
        Radius for matching. Default is 1 arcsecond.
    safeSnr : `float`, optional
        Minimum median SNR for a match to be considered "safe".
    useJointCal : `bool`, optional
        Use jointcal/meas_mosaic outputs to calibrate positions and fluxes.
    skipTEx : `bool`, optional
        Skip TEx calculations (useful for older catalogs that don't have
        PsfShape measurements).

    Attributes of returned Blob
    ----------
    filterName : `str`
        Name of filter used for all observations.
    mag : `astropy.units.Quantity`
        Mean PSF magnitudes of stars over multiple visits (magnitudes).
    magerr : `astropy.units.Quantity`
        Median 1-sigma uncertainty of PSF magnitudes over multiple visits
        (magnitudes).
    magrms : `astropy.units.Quantity`
        RMS of PSF magnitudes over multiple visits (magnitudes).
    snr : `astropy.units.Quantity`
        Median signal-to-noise ratio of PSF magnitudes over multiple visits
        (dimensionless).
    dist : `astropy.units.Quantity`
        RMS of sky coordinates of stars over multiple visits (milliarcseconds).

        *Not serialized.*
    goodMatches
        all good matches, as an afw.table.GroupView;
        good matches contain only objects whose detections all have

        1. a PSF Flux measurement with S/N > 1
        2. a finite (non-nan) PSF magnitude. This separate check is largely
           to reject failed zeropoints.
        3. and do not have flags set for bad, cosmic ray, edge or saturated

        *Not serialized.*

    safeMatches
        safe matches, as an afw.table.GroupView. Safe matches
        are good matches that are sufficiently bright and sufficiently
        compact.

        *Not serialized.*
    magKey
        Key for `"base_PsfFlux_mag"` in the `goodMatches` and `safeMatches`
        catalog tables.

        *Not serialized.*
    """
    blob = Blob('MatchedMultiVisitDataset')

    if not matchRadius:
        matchRadius = afwGeom.Angle(1, afwGeom.arcseconds)

    # Extract single filter
    blob['filterName'] = Datum(quantity=set([dId['filter']
                                             for dId in dataIds]).pop(),
                               description='Filter name')

    # Record important configuration
    blob['useJointCal'] = Datum(
        quantity=useJointCal,
        description='Whether jointcal/meas_mosaic calibrations were used')

    # Match catalogs across visits
    blob._catalog, blob._matchedCatalog = \
        _loadAndMatchCatalogs(repo, dataIds, matchRadius,
                              useJointCal=useJointCal, skipTEx=skipTEx)

    blob.magKey = blob._matchedCatalog.schema.find("base_PsfFlux_mag").key
    # Reduce catalogs into summary statistics.
    # These are the serialiable attributes of this class.
    _reduceStars(blob, blob._matchedCatalog, safeSnr)
    return blob
Пример #30
0
def _reduceStars(blob, allMatches, safeSnr=50.0):
    """Calculate summary statistics for each star. These are persisted
    as object attributes.

    Parameters
    ----------
    allMatches : afw.table.GroupView
        GroupView object with matches.
    safeSnr : float, optional
        Minimum median SNR for a match to be considered "safe".
    """
    # Filter down to matches with at least 2 sources and good flags
    flagKeys = [
        allMatches.schema.find("base_PixelFlags_flag_%s" % flag).key
        for flag in ("saturated", "cr", "bad", "edge")
    ]
    nMatchesRequired = 2

    psfSnrKey = allMatches.schema.find("base_PsfFlux_snr").key
    psfMagKey = allMatches.schema.find("base_PsfFlux_mag").key
    psfMagErrKey = allMatches.schema.find("base_PsfFlux_magErr").key
    extendedKey = allMatches.schema.find(
        "base_ClassificationExtendedness_value").key

    def goodFilter(cat, goodSnr=3):
        if len(cat) < nMatchesRequired:
            return False
        for flagKey in flagKeys:
            if cat.get(flagKey).any():
                return False
        if not np.isfinite(cat.get(psfMagKey)).all():
            return False
        psfSnr = np.median(cat.get(psfSnrKey))
        # Note that this also implicitly checks for psfSnr being non-nan.
        return psfSnr >= goodSnr

    goodMatches = allMatches.where(goodFilter)

    # Filter further to a limited range in S/N and extendedness
    # to select bright stars.
    safeMaxExtended = 1.0

    def safeFilter(cat):
        psfSnr = np.median(cat.get(psfSnrKey))
        extended = np.max(cat.get(extendedKey))
        return psfSnr >= safeSnr and extended < safeMaxExtended

    safeMatches = goodMatches.where(safeFilter)

    # Pass field=psfMagKey so np.mean just gets that as its input
    filter_name = blob['filterName']
    blob['snr'] = Datum(
        quantity=goodMatches.aggregate(np.median, field=psfSnrKey) *
        u.Unit(''),
        label='SNR({band})'.format(band=filter_name),
        description='Median signal-to-noise ratio of PSF magnitudes over '
        'multiple visits')
    blob['mag'] = Datum(
        quantity=goodMatches.aggregate(np.mean, field=psfMagKey) * u.mag,
        label='{band}'.format(band=filter_name),
        description='Mean PSF magnitudes of stars over multiple visits')
    blob['magrms'] = Datum(
        quantity=goodMatches.aggregate(np.std, field=psfMagKey) * u.mag,
        label='RMS({band})'.format(band=filter_name),
        description='RMS of PSF magnitudes over multiple visits')
    blob['magerr'] = Datum(
        quantity=goodMatches.aggregate(np.median, field=psfMagErrKey) * u.mag,
        label='sigma({band})'.format(band=filter_name),
        description='Median 1-sigma uncertainty of PSF magnitudes over '
        'multiple visits')
    # positionRmsFromCat knows how to query a group
    # so we give it the whole thing by going with the default `field=None`.
    blob['dist'] = Datum(
        quantity=goodMatches.aggregate(positionRmsFromCat) * u.milliarcsecond,
        label='d',
        description='RMS of sky coordinates of stars over multiple visits')

    # These attributes are not serialized
    blob.goodMatches = goodMatches
    blob.safeMatches = safeMatches