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)
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')
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
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
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