예제 #1
0
파일: jointcal.py 프로젝트: lsst/jointcal
    def __init__(self, butler=None, profile_jointcal=False, **kwargs):
        """
        Instantiate a JointcalTask.

        Parameters
        ----------
        butler : `lsst.daf.persistence.Butler`
            The butler is passed to the refObjLoader constructor in case it is
            needed. Ignored if the refObjLoader argument provides a loader directly.
            Used to initialize the astrometry and photometry refObjLoaders.
        profile_jointcal : `bool`
            Set to True to profile different stages of this jointcal run.
        """
        pipeBase.CmdLineTask.__init__(self, **kwargs)
        self.profile_jointcal = profile_jointcal
        self.makeSubtask("sourceSelector")
        if self.config.doAstrometry:
            self.makeSubtask('astrometryRefObjLoader', butler=butler)
            self.makeSubtask("astrometryReferenceSelector")
        else:
            self.astrometryRefObjLoader = None
        if self.config.doPhotometry:
            self.makeSubtask('photometryRefObjLoader', butler=butler)
            self.makeSubtask("photometryReferenceSelector")
        else:
            self.photometryRefObjLoader = None

        # To hold various computed metrics for use by tests
        self.job = Job.load_metrics_package(subset='jointcal')
    def runDataRefs(self, datarefs, customMetadata=None):
        """Call all registered metric tasks on each dataref.

        This method loads all datasets required to compute a particular
        metric, and persists the metrics as one or more `lsst.verify.Job`
        objects. Only metrics that successfully produce a
        `~lsst.verify.Measurement` will be included in a job.

        Parameters
        ----------
        datarefs : `list` of `lsst.daf.persistence.ButlerDataRef`
            The data to measure. Datarefs may be complete or partial; each
            generates a measurement at the same granularity (e.g., a
            dataref with only ``"visit"`` specified generates visit-level
            measurements).
        customMetadata : `dict`, optional
            Any metadata that are needed for a specific pipeline, but that are
            not needed by the ``lsst.verify`` framework or by general-purpose
            measurement analysis code (these cases are handled by the
            `~MetricsControllerConfig.metadataAdder` subtask). If omitted,
            only generic metadata are added. Both keys and values must be valid
            inputs to `~lsst.verify.Metadata`.

        Returns
        -------
        struct : `lsst.pipe.base.Struct`
            A `~lsst.pipe.base.Struct` containing the following component:

            - ``jobs`` : a list of collections of measurements (`list` of
              `lsst.verify.Job`). Each job in the list contains the
              measurement(s) for the corresponding dataref, and each job has
              at most one measurement for each element in `self.measurers`. A
              particular measurement is omitted if it could not be created.

        Notes
        -----
        Some objects may be persisted, or incorrectly persisted, in the event
        of an exception.
        """
        jobs = []
        index = 0
        for dataref in datarefs:
            job = Job.load_metrics_package()
            try:
                self.metadataAdder.run(job, dataref=dataref)
                if customMetadata:
                    job.meta.update(customMetadata)

                for task in self.measurers:
                    self._computeSingleMeasurement(job, task, dataref)
            finally:
                jobFile = self._getJobFilePath(index, dataref.dataId)
                self.log.info("Persisting metrics to %s...", jobFile)
                # This call order maximizes the chance that job gets
                # written, and to a unique file
                index += 1
                job.write(jobFile)
                jobs.append(job)

        return Struct(jobs=jobs)
예제 #3
0
    def __init__(self, butler=None, profile_jointcal=False, **kwargs):
        """
        Instantiate a JointcalTask.

        Parameters
        ----------
        butler : lsst.daf.persistence.Butler
            The butler is passed to the refObjLoader constructor in case it is
            needed. Ignored if the refObjLoader argument provides a loader directly.
            Used to initialize the astrometry and photometry refObjLoaders.
        profile_jointcal : bool
            set to True to profile different stages of this jointcal run.
        """
        pipeBase.CmdLineTask.__init__(self, **kwargs)
        self.profile_jointcal = profile_jointcal
        self.makeSubtask("sourceSelector")
        if self.config.doAstrometry:
            self.makeSubtask('astrometryRefObjLoader', butler=butler)
        if self.config.doPhotometry:
            self.makeSubtask('photometryRefObjLoader', butler=butler)

        # To hold various computed metrics for use by tests
        self.job = Job.load_metrics_package(subset='jointcal')
예제 #4
0
def runOneFilter(repo,
                 visitDataIds,
                 brightSnrMin=None,
                 brightSnrMax=None,
                 makeJson=True,
                 filterName=None,
                 outputPrefix='',
                 doApplyExternalPhotoCalib=False,
                 externalPhotoCalibName=None,
                 doApplyExternalSkyWcs=False,
                 externalSkyWcsName=None,
                 skipTEx=False,
                 verbose=False,
                 metrics_package='verify_metrics',
                 instrument='Unknown',
                 dataset_repo_url='./',
                 skipNonSrd=False,
                 **kwargs):
    r"""Main executable for the case where there is just one filter.

    Plot files and JSON files are generated in the local directory
    prefixed with the repository name (where '_' replace path separators),
    unless overriden by specifying `outputPrefix`.
    E.g., Analyzing a repository ``CFHT/output``
    will result in filenames that start with ``CFHT_output_``.

    Parameters
    ----------
    repo : string or Butler
        A Butler 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` pixel image is needed for the photometric calibration
        unless doApplyExternalPhotoCalib is True such
        that the appropriate `photoCalib` dataset is used. Note that these
        have data IDs that include the tract number.
    brightSnrMin : float, optional
        Minimum median SNR for a source to be considered bright; passed to
        `lsst.validate.drp.matchreduce.build_matched_dataset`.
    brightSnrMax : float, optional
        Maximum median SNR for a source to be considered bright; passed to
        `lsst.validate.drp.matchreduce.build_matched_dataset`.
    makeJson : bool, optional
        Create JSON output file for metrics.  Saved to current working directory.
    outputPrefix : str, optional
        Specify the beginning filename for output files.
    filterName : str, optional
        Name of the filter (bandpass).
    doApplyExternalPhotoCalib : bool, optional
        Apply external photoCalib to calibrate fluxes.
    externalPhotoCalibName : str, optional
        Type of external `PhotoCalib` to apply.  Currently supported are jointcal,
        fgcm, and fgcm_tract.  Must be set if doApplyExternalPhotoCalib is True.
    doApplyExternalSkyWcs : bool, optional
        Apply external wcs to calibrate positions.
    externalSkyWcsName : str, optional
        Type of external `wcs` to apply.  Currently supported is jointcal.
        Must be set if "doApplyExternalSkyWcs" is True.
    skipTEx : bool, optional
        Skip TEx calculations (useful for older catalogs that don't have
        PsfShape measurements).
    verbose : bool, optional
        Output additional information on the analysis steps.
    skipNonSrd : bool, optional
        Skip any metrics not defined in the LSST SRD.

    Raises
    ------
    RuntimeError:
        Raised if "doApplyExternalPhotoCalib" is True and "externalPhotoCalibName"
        is None, or if "doApplyExternalSkyWcs" is True and "externalSkyWcsName" is
        None.
    """

    if kwargs:
        log.warn(
            f"Extra kwargs - {kwargs}, will be ignored. Did you add extra things to your config file?"
        )

    if doApplyExternalPhotoCalib and externalPhotoCalibName is None:
        raise RuntimeError(
            "Must set externalPhotoCalibName if doApplyExternalPhotoCalib is True."
        )
    if doApplyExternalSkyWcs and externalSkyWcsName is None:
        raise RuntimeError(
            "Must set externalSkyWcsName if doApplyExternalSkyWcs is True.")

    # collect just the common key, value pairs to omit the keys that are aggregated over
    job_metadata = dict(
        set.intersection(*[set(vid.items()) for vid in visitDataIds]))

    # update with metadata passed into the method
    job_metadata.update({
        'instrument': instrument,
        'filter_name': filterName,
        'dataset_repo_url': dataset_repo_url
    })

    job = Job.load_metrics_package(meta=job_metadata,
                                   subset='validate_drp',
                                   package_name_or_path=metrics_package)

    matchedDataset = build_matched_dataset(
        repo,
        visitDataIds,
        doApplyExternalPhotoCalib=doApplyExternalPhotoCalib,
        externalPhotoCalibName=externalPhotoCalibName,
        doApplyExternalSkyWcs=doApplyExternalSkyWcs,
        externalSkyWcsName=externalSkyWcsName,
        skipTEx=skipTEx,
        skipNonSrd=skipNonSrd,
        brightSnrMin=brightSnrMin,
        brightSnrMax=brightSnrMax)

    snr = matchedDataset['snr'].quantity
    bright = (matchedDataset['brightSnrMin'].quantity <
              snr) & (snr < matchedDataset['brightSnrMax'].quantity)
    photomModel = build_photometric_error_model(matchedDataset, bright)
    astromModel = build_astrometric_error_model(matchedDataset, bright)

    linkedBlobs = [matchedDataset, photomModel, astromModel]

    metrics = job.metrics
    specs = job.specs

    def add_measurement(measurement):
        for blob in linkedBlobs:
            measurement.link_blob(blob)
        job.measurements.insert(measurement)

    for x, D in zip((1, 2, 3), (5., 20., 200.)):
        amxName = 'AM{0:d}'.format(x)
        afxName = 'AF{0:d}'.format(x)
        adxName = 'AD{0:d}'.format(x)

        amx = measureAMx(metrics['validate_drp.' + amxName],
                         matchedDataset,
                         D * u.arcmin,
                         verbose=verbose)
        add_measurement(amx)

        afx_spec_set = specs.subset(required_meta={'instrument': 'HSC'},
                                    spec_tags=[
                                        afxName,
                                    ])
        adx_spec_set = specs.subset(required_meta={'instrument': 'HSC'},
                                    spec_tags=[
                                        adxName,
                                    ])
        for afx_spec_key, adx_spec_key in zip(afx_spec_set, adx_spec_set):
            afx_spec = afx_spec_set[afx_spec_key]
            adx_spec = adx_spec_set[adx_spec_key]
            adx = measureADx(metrics[adx_spec.metric_name], amx, afx_spec)
            add_measurement(adx)
            afx = measureAFx(metrics[afx_spec.metric_name], amx, adx, adx_spec)
            add_measurement(afx)

    pa1 = measurePA1(metrics['validate_drp.PA1'], filterName,
                     matchedDataset.matchesBright, matchedDataset.magKey)
    add_measurement(pa1)

    pf1_spec_set = specs.subset(required_meta={
        'instrument': instrument,
        'filter_name': filterName
    },
                                spec_tags=[
                                    'PF1',
                                ])
    pa2_spec_set = specs.subset(required_meta={
        'instrument': instrument,
        'filter_name': filterName
    },
                                spec_tags=[
                                    'PA2',
                                ])
    # I worry these might not always be in the right order.  Sorting...
    pf1_spec_keys = list(pf1_spec_set.keys())
    pa2_spec_keys = list(pa2_spec_set.keys())
    pf1_spec_keys.sort()
    pa2_spec_keys.sort()
    for pf1_spec_key, pa2_spec_key in zip(pf1_spec_keys, pa2_spec_keys):
        pf1_spec = pf1_spec_set[pf1_spec_key]
        pa2_spec = pa2_spec_set[pa2_spec_key]

        pa2 = measurePA2(metrics[pa2_spec.metric_name], pa1,
                         pf1_spec.threshold)
        add_measurement(pa2)

        pf1 = measurePF1(metrics[pf1_spec.metric_name], pa1, pa2_spec)
        add_measurement(pf1)

    if not skipTEx:
        for x, D, bin_range_operator in zip((1, 2), (1.0, 5.0), ("<=", ">=")):
            texName = 'TE{0:d}'.format(x)
            tex = measureTEx(metrics['validate_drp.' + texName],
                             matchedDataset,
                             D * u.arcmin,
                             bin_range_operator,
                             verbose=verbose)
            add_measurement(tex)

    if not skipNonSrd:
        model_phot_reps = measure_model_phot_rep(metrics, filterName,
                                                 matchedDataset)
        for measurement in model_phot_reps:
            add_measurement(measurement)

    if makeJson:
        job.write(outputPrefix + '.json')

    return job
예제 #5
0
def merge(jobs, lastJob):
    """Combine measurements from multiple chips or visits.

    Other job properties will be dictionary-merged (i.e., if multiple entries
    are assigned to the same key, only one will be preserved).

    Parameters
    ----------
    jobs: iterable of `lsst.verify.Job`
        The jobs containing data to combine.
    lastJob:
        The job corresponding to the final run of ap_verify.

    Return
    ------
    A single `lsst.verify.Job` object containing merged measurements from
    `jobs`.
    """
    merged = Job.load_metrics_package()
    # Visible Job state:
    #     job.measurements
    #     job.meta
    #     job.metrics (guaranteed by load_metrics_package)
    #     job.specs (guaranteed by load_metrics_package)

    measurementsPerMetric = defaultdict(list)
    for job in jobs:
        for metricName in job.measurements:
            measurementsPerMetric[str(metricName)].append(
                job.measurements[metricName])

    for metric in measurementsPerMetric:
        # Running times, object counts
        if metric.endswith("Time") or metric in {
                "ip_diffim.numSciSources", "association.numNewDiaObjects",
                "association.totalUnassociatedDiaObjects"
        }:
            addIfDefined(merged.measurements,
                         sumMeasurements(measurementsPerMetric[metric]))

    # Fractions require special handling
    addIfDefined(
        merged.measurements,
        # Due to time constraints, no metric for total DIAObjects was implemented,
        # so we have to work around its absence
        mergeFractionsPartial(
            measurementsPerMetric["association.fracUpdatedDiaObjects"],
            measurementsPerMetric["association.numUnassociatedDiaObjects"]))
    addIfDefined(
        merged.measurements,
        mergeFractions(
            measurementsPerMetric["ip_diffim.fracDiaSourcesToSciSources"],
            measurementsPerMetric["ip_diffim.numSciSources"]))

    # L1 database metrics are cumulative, not per-CCD, so just copy them over
    for metric in ["association.totalUnassociatedDiaObjects"]:
        if metric in lastJob.measurements:
            addIfDefined(merged.measurements, lastJob.measurements[metric])

    for job in jobs:
        merged.meta.update(job.meta)

    return merged
예제 #6
0
def runOneFilter(repo,
                 visitDataIds,
                 metrics,
                 brightSnr=100,
                 makeJson=True,
                 filterName=None,
                 outputPrefix='',
                 useJointCal=False,
                 skipTEx=False,
                 verbose=False,
                 metrics_package='verify_metrics',
                 **kwargs):
    """Main executable for the case where there is just one filter.

    Plot files and JSON files are generated in the local directory
    prefixed with the repository name (where '_' replace path separators),
    unless overriden by specifying `outputPrefix`.
    E.g., Analyzing a repository ``CFHT/output``
    will result in filenames that start with ``CFHT_output_``.

    Parameters
    ----------
    repo : string or Butler
        A Butler 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` pixel image is needed for the photometric calibration
        unless useJointCal is True, in which the `photoCalib` and `wcs`
        datasets are used instead.  Note that these have data IDs that include
        the tract number.
    metrics : `dict` or `collections.OrderedDict`
        Dictionary of `lsst.validate.base.Metric` instances. Typically this is
        data from ``validate_drp``\ 's ``metrics.yaml`` and loaded with
        `lsst.validate.base.load_metrics`.
    brightSnr : float, optional
        Minimum SNR for a star to be considered bright
    makeJson : bool, optional
        Create JSON output file for metrics.  Saved to current working directory.
    outputPrefix : str, optional
        Specify the beginning filename for output files.
    filterName : str, optional
        Name of the filter (bandpass).
    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).
    verbose : bool, optional
        Output additional information on the analysis steps.
    """
    matchedDataset = build_matched_dataset(repo,
                                           visitDataIds,
                                           useJointCal=useJointCal,
                                           skipTEx=skipTEx)

    photomModel = build_photometric_error_model(matchedDataset)

    astromModel = build_astrometric_error_model(matchedDataset)

    linkedBlobs = [matchedDataset, photomModel, astromModel]

    try:
        instrument = kwargs['instrument']
        dataset_repo_url = kwargs['dataset_repo_url']
    except KeyError:
        raise ValueError(
            "Instrument name and input dataset URL must be set in config file")
    job = Job.load_metrics_package(meta={
        'instrument': instrument,
        'filter_name': filterName,
        'dataset_repo_url': dataset_repo_url
    },
                                   subset='validate_drp',
                                   package_name_or_path=metrics_package)
    metrics = job.metrics

    specs = job.specs

    def add_measurement(measurement):
        for blob in linkedBlobs:
            measurement.link_blob(blob)
        job.measurements.insert(measurement)

    for x, D in zip((1, 2, 3), (5., 20., 200.)):
        amxName = 'AM{0:d}'.format(x)
        afxName = 'AF{0:d}'.format(x)
        adxName = 'AD{0:d}'.format(x)

        amx = measureAMx(metrics['validate_drp.' + amxName], matchedDataset,
                         D * u.arcmin)
        add_measurement(amx)

        afx_spec_set = specs.subset(required_meta={'instrument': 'HSC'},
                                    spec_tags=[
                                        afxName,
                                    ])
        adx_spec_set = specs.subset(required_meta={'instrument': 'HSC'},
                                    spec_tags=[
                                        adxName,
                                    ])
        for afx_spec_key, adx_spec_key in zip(afx_spec_set, adx_spec_set):
            afx_spec = afx_spec_set[afx_spec_key]
            adx_spec = adx_spec_set[adx_spec_key]
            adx = measureADx(metrics[adx_spec.metric_name], amx, afx_spec)
            add_measurement(adx)
            afx = measureAFx(metrics[afx_spec.metric_name], amx, adx, adx_spec)
            add_measurement(afx)

    pa1 = measurePA1(metrics['validate_drp.PA1'], matchedDataset, filterName)
    add_measurement(pa1)

    pf1_spec_set = specs.subset(required_meta={
        'instrument': instrument,
        'filter_name': filterName
    },
                                spec_tags=[
                                    'PF1',
                                ])
    pa2_spec_set = specs.subset(required_meta={
        'instrument': instrument,
        'filter_name': filterName
    },
                                spec_tags=[
                                    'PA2',
                                ])
    # I worry these might not always be in the right order.  Sorting...
    pf1_spec_keys = list(pf1_spec_set.keys())
    pa2_spec_keys = list(pa2_spec_set.keys())
    pf1_spec_keys.sort()
    pa2_spec_keys.sort()
    for pf1_spec_key, pa2_spec_key in zip(pf1_spec_keys, pa2_spec_keys):
        pf1_spec = pf1_spec_set[pf1_spec_key]
        pa2_spec = pa2_spec_set[pa2_spec_key]

        pa2 = measurePA2(metrics[pa2_spec.metric_name], pa1,
                         pf1_spec.threshold)
        add_measurement(pa2)

        pf1 = measurePF1(metrics[pf1_spec.metric_name], pa1, pa2_spec)
        add_measurement(pf1)

    if not skipTEx:
        for x, D, bin_range_operator in zip((1, 2), (1.0, 5.0), ("<=", ">=")):
            texName = 'TE{0:d}'.format(x)
            tex = measureTEx(metrics['validate_drp.' + texName],
                             matchedDataset, D * u.arcmin, bin_range_operator)
            add_measurement(tex)

    if makeJson:
        job.write(outputPrefix + '.json')

    return job