diff --git a/python/lsst/analysis/tools/actions/plot/histPlot.py b/python/lsst/analysis/tools/actions/plot/histPlot.py index 2b800a929..14ff2837c 100644 --- a/python/lsst/analysis/tools/actions/plot/histPlot.py +++ b/python/lsst/analysis/tools/actions/plot/histPlot.py @@ -289,7 +289,7 @@ def makePlot( if nth_panel == 0 and nrows * ncols - len(self.panels) > 0: nth_col -= 1 # Set font size for legend based on number of panels being plotted. - legend_font_size = max(4, int(8 - len(self.panels[panel].hists) / 2 - nrows // 2)) # type: ignore + legend_font_size = max(4, int(7 - len(self.panels[panel].hists) / 2 - nrows // 2)) # type: ignore nums, meds, mads, stats_dict = self._makePanel( data, panel, @@ -608,12 +608,16 @@ def _addStatisticsPanel( legend_labels = ( ([""] * (len(handles) + 1)) + [stats_dict["statLabels"][0]] - + [f"{x:.3g}" for x in stats_dict["stat1"]] + + [f"{x:.3g}" if x > 0.01 else f"{x:.2e}" for x in stats_dict["stat1"]] + [stats_dict["statLabels"][1]] - + [f"{x:.3g}" for x in stats_dict["stat2"]] + + [f"{x:.3g}" if x > 0.01 else f"{x:.2e}" for x in stats_dict["stat2"]] + [stats_dict["statLabels"][2]] - + [f"{x:.3g}" for x in stats_dict["stat3"]] + + [f"{x:.3g}" if x > 0.01 else f"{x:.2e}" for x in stats_dict["stat3"]] ) + # Replace "e+0" with "e" and "e-0" with "e-" to save space. + legend_labels = [label.replace("e+0", "e") for label in legend_labels] + legend_labels = [label.replace("e-0", "e-") for label in legend_labels] + # Set the y anchor for the legend such that it roughly lines up with # the panels. yAnchor = max(0, yAnchor0 - 0.01) + nth_col * (0.008 + len(nums) * 0.005) * legend_font_size diff --git a/python/lsst/analysis/tools/atools/calexpMetrics.py b/python/lsst/analysis/tools/atools/calexpMetrics.py index d1f18c16d..e92145f10 100644 --- a/python/lsst/analysis/tools/atools/calexpMetrics.py +++ b/python/lsst/analysis/tools/atools/calexpMetrics.py @@ -20,8 +20,15 @@ # along with this program. If not, see . from __future__ import annotations -__all__ = ("CalexpSummaryMetrics",) +__all__ = ( + "CalexpSummaryMetrics", + "CalexpMetricHists", +) +from lsst.pex.config import DictField + +from ..actions.plot import HistPanel, HistPlot +from ..actions.vector import BandSelector, LoadVector from ..interfaces import AnalysisTool @@ -77,3 +84,24 @@ def setDefaults(self): self.prep.keysToLoad = list(self._units.keys()) self.produce.metric.units = self._units + + +class CalexpMetricHists(AnalysisTool): + """ + Class to generate histograms of metrics extracted from a Metrics Table. + One plot per band. + """ + + parameterizedBand: bool = False + metrics = DictField[str, str](doc="The metrics to plot and their respective labels.") + + def setDefaults(self): + # Band is passed as a kwarg from the calling task. + self.prep.selectors.bandSelector = BandSelector() + self.produce.plot = HistPlot() + + def finalize(self): + + for metric, label in self.metrics.items(): + setattr(self.process.buildActions, metric, LoadVector(vectorKey=metric)) + self.produce.plot.panels[metric] = HistPanel(hists={metric: "Number of calexps"}, label=label) diff --git a/python/lsst/analysis/tools/tasks/metricAnalysis.py b/python/lsst/analysis/tools/tasks/metricAnalysis.py index 30f14117a..fd3817507 100644 --- a/python/lsst/analysis/tools/tasks/metricAnalysis.py +++ b/python/lsst/analysis/tools/tasks/metricAnalysis.py @@ -26,6 +26,7 @@ ) +from lsst.pex.config import ListField from lsst.pipe.base import connectionTypes as ct from ..interfaces import AnalysisBaseConfig, AnalysisBaseConnections, AnalysisPipelineTask @@ -33,26 +34,68 @@ class MetricAnalysisConnections( AnalysisBaseConnections, - dimensions=("skymap",), - defaultTemplates={"metricBundleName": "objectTableCore_metrics"}, + dimensions=(), + defaultTemplates={"metricBundleName": ""}, ): + data = ct.Input( - doc="A summary table of all metrics by tract.", + doc="A table containing metrics.", name="{metricBundleName}Table", storageClass="ArrowAstropy", - dimensions=("skymap",), deferLoad=True, + dimensions=(), ) + def __init__(self, *, config=None): + + self.dimensions.update(frozenset(sorted(config.outputDataDimensions))) + super().__init__(config=config) + self.data = ct.Input( + doc=self.data.doc, + name=self.data.name, + storageClass=self.data.storageClass, + deferLoad=self.data.deferLoad, + dimensions=frozenset(sorted(config.inputDataDimensions)), + ) + -class MetricAnalysisConfig(AnalysisBaseConfig, pipelineConnections=MetricAnalysisConnections): - pass +class MetricAnalysisConfig( + AnalysisBaseConfig, + pipelineConnections=MetricAnalysisConnections, +): + inputDataDimensions = ListField[str]( + doc="Dimensions of the input data.", + default=(), + optional=False, + ) + outputDataDimensions = ListField[str]( + doc="Dimensions of the output data.", + default=(), + optional=False, + ) class MetricAnalysisTask(AnalysisPipelineTask): - """Turn metric bundles which are per tract into a - summary metric table. - """ + """Perform an analysis of a metric table.""" ConfigClass = MetricAnalysisConfig _DefaultName = "metricAnalysis" + + def runQuantum(self, butlerQC, inputRefs, outputRefs): + + inputs = butlerQC.get(inputRefs) + dataId = butlerQC.quantum.dataId + plotInfo = self.parsePlotInfo(inputs, dataId) + + data = self.loadData(inputs.pop("data")) + + # TODO: "bands" kwarg is a workaround for DM-47941. + outputs = self.run( + data=data, + plotInfo=plotInfo, + bands=dataId["band"], + band=dataId["band"], + **inputs, + ) + + butlerQC.put(outputs, outputRefs)