Skip to content

Commit

Permalink
Merge pull request #272 from lsst/tickets/DM-44934
Browse files Browse the repository at this point in the history
DM-44934: Reverse RA axis on WholeSkyPlot
  • Loading branch information
jtmccann authored Aug 23, 2024
2 parents 2b45b7d + c5fb81f commit 1eab82d
Show file tree
Hide file tree
Showing 5 changed files with 189 additions and 31 deletions.
4 changes: 2 additions & 2 deletions pipelines/wholeSkyCore.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,13 @@ tasks:
makeMetricTableObjectTableCoreRefCatMatch:
class: lsst.analysis.tools.tasks.MakeMetricTableTask
config:
connections.metricBundleName: "objectTable_tract_gaia_dr3_20230707_match_astrom_metrics"
connections.metricBundleName: "objectTable_tract_gaia_dr3_20230707_match_metrics"
python: |
from lsst.analysis.tools.atools import *
objectTableCoreRefCatMatchWholeSkyPlot:
class: lsst.analysis.tools.tasks.WholeSkyAnalysisTask
config:
connections.inputName: "objectTable_tract_gaia_dr3_20230707_match_astrom_metricsTable"
connections.inputName: "objectTable_tract_gaia_dr3_20230707_match_metricsTable"
connections.outputName: "objectTableCoreRefCatMatch_wholeSky"
atools.wholeSkyMetric: WholeSkyPlotTool
atools.wholeSkyMetric.plotKeys: []
Expand Down
130 changes: 108 additions & 22 deletions python/lsst/analysis/tools/actions/plot/wholeSkyPlot.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import numpy as np
from lsst.pex.config import Config, Field, ListField
from matplotlib.collections import PatchCollection
from matplotlib.colors import CenteredNorm
from matplotlib.figure import Figure
from matplotlib.patches import Patch, Polygon

Expand All @@ -50,9 +51,11 @@ class WholeSkyPlot(PlotAction):
The default axes limits and figure size were chosen to plot HSC PDR2.
"""

keyBands = ListField[str](doc="Band for each metric, if any.")
plotKeys = ListField[str](doc="Names of metrics to plot.")
xAxisLabel = Field[str](doc="Label to use for the x axis.", default="RA (degrees)")
yAxisLabel = Field[str](doc="Label to use for the y axis.", default="Dec (degrees)")
plotKeys = ListField[str](doc="Names of metrics to plot.")
autoAxesLimits = Field[bool](doc="Find axes limits automatically.", default=True)
xLimits = ListField[float](doc="Plotting limits for the x axis.", default=[-5.0, 365.0])
yLimits = ListField[float](doc="Plotting limits for the y axis.", default=[-10.0, 60.0])
figureSize = ListField[float](doc="Size of the figure.", default=[9.0, 3.5])
Expand All @@ -61,6 +64,19 @@ class WholeSkyPlot(PlotAction):
", where N is this config value.",
default=3.0,
)
sequentialMetrics = ListField[str](
doc="Partial names of metrics with sequential values. This is a placeholder until metric information "
"is available via yaml.",
default=["count", "num", "igma", "tdev", "Repeat"],
)
sequentialColorMap = ListField[str](
doc="List of hexidecimal colors for a sequential color map.",
default=["#F5F5F5", "#5AB4AC", "#284D48"],
)
divergentColorMap = ListField[str](
doc="List of hexidecimal colors for a divergent color map.",
default=["#9A6E3A", "#C6A267", "#A9A9A9", "#4F938B", "#2C665A"],
)

def getOutputNames(self, config: Config | None = None) -> Iterable[str]:
return list(self.plotKeys)
Expand Down Expand Up @@ -90,11 +106,47 @@ def _validateInput(self, data: KeyedData, **kwargs) -> None:
if isScalar and typ != Scalar:
raise ValueError(f"Data keyed by {name} has type {colType} but action requires type {typ}")

def _getMaxOutlierVals(self, tracts, values, outlierInds):
def _getAxesLimits(self, xs: list, ys: list) -> tuple(list, list):
"""Get the x and y axes limits in degrees.
Parameters
----------
xs : `list`
X coordinates for the tracts to plot.
ys : `list`
Y coordinates for the tracts to plot.
Returns
-------
xlim : `list`
Minimun and maximum x axis values.
ylim : `list`
Minimun and maximum y axis values.
"""

# Add some blank space on the edges of the plot.
xlim = [np.nanmin(xs) - 5, np.nanmax(xs) + 5]
ylim = [np.nanmin(ys) - 5, np.nanmax(ys) + 5]

# Limit to only show real RA/Dec values.
if xlim[0] < 0.0:
xlim[0] = 0.0
if xlim[1] > 360.0:
xlim[1] = 360.0
if ylim[0] < -90.0:
ylim[0] = -90.0
if ylim[1] > 90.0:
ylim[1] = 90.0

return (xlim, ylim)

def _getMaxOutlierVals(self, multiplier: float, tracts: list, values: list, outlierInds: list) -> str:
"""Get the 5 largest outlier values in a string.
Parameters
----------
multiplier : `float`
Select values whose absolute value is > multiplier * sigmaMAD.
tracts : `list`
All the tracts.
values : `list`
Expand All @@ -107,7 +159,7 @@ def _getMaxOutlierVals(self, tracts, values, outlierInds):
text : `str`
A string containing the 5 tracts with the largest outlier values.
"""
text = "Outlier values: "
text = f"Tracts with |value| > {multiplier}" + r"$\sigma_{MAD}$" + ": "
if len(outlierInds) > 0:
outlierValues = np.array(values)[outlierInds]
outlierTracts = np.array(tracts)[outlierInds]
Expand All @@ -118,7 +170,7 @@ def _getMaxOutlierVals(self, tracts, values, outlierInds):
for ind in maxInds[:5]:
val = outlierValues[ind]
tract = outlierTracts[ind]
text += f"{tract}: {val:.3}, "
text += f"{tract}, {val:.3}; "
# Remove the final trailing comma and whitespace.
text = text[:-2]
else:
Expand Down Expand Up @@ -166,14 +218,28 @@ def makePlot(
please see the
:ref:`getting started guide<analysis-tools-getting-started>`.
"""
# Make a divergent colormap.
blueGreen = mkColormap(["midnightblue", "lightcyan", "darkgreen"])
# Make colorblind-friendly colormap options.
sequentialColorMap = mkColormap(self.sequentialColorMap)
divergentColorMap = mkColormap(self.divergentColorMap)

results = {}
for key in self.plotKeys:
for key, band in zip(self.plotKeys, self.keyBands):

if plotInfo is None:
plotInfo = {}

# Choose the color map based on metric type.
for metricType in self.sequentialMetrics:
if metricType in key:
colorMap = sequentialColorMap
# Choose arbitrary color bar center.
norm = None
break
else:
colorMap = divergentColorMap
# Center color bar on zero.
norm = CenteredNorm()

# Create patches using the corners of each tract.
patches = []
colBarVals = []
Expand All @@ -189,18 +255,25 @@ def makePlot(

# Setup figure.
fig, ax = plt.subplots(1, 1, figsize=self.figureSize, dpi=500)
ax.set_xlim(self.xLimits)
ax.set_ylim(self.yLimits)
if self.autoAxesLimits:
xlim, ylim = self._getAxesLimits(ras, decs)
else:
xlim, ylim = self.xLimits, self.yLimits
ax.set_xlim(xlim)
ax.set_ylim(ylim)
ax.set_xlabel(self.xAxisLabel)
ax.set_ylabel(self.yAxisLabel)
ax.invert_xaxis()

# Add colored patches showing tract metric values.
patchCollection = PatchCollection(patches, cmap=blueGreen)
patchCollection = PatchCollection(patches, cmap=colorMap, norm=norm)
ax.add_collection(patchCollection)

# Define color bar range.
vmin = np.nanmedian(colBarVals) - self.colorBarRange * nanSigmaMad(colBarVals)
vmax = np.nanmedian(colBarVals) + self.colorBarRange * nanSigmaMad(colBarVals)
med = np.nanmedian(colBarVals)
sigmaMad = nanSigmaMad(colBarVals)
vmin = med - self.colorBarRange * sigmaMad
vmax = med + self.colorBarRange * sigmaMad

# Note tracts with metrics outside (vmin, vmax) as outliers.
outlierInds = np.where((colBarVals < vmin) | (colBarVals > vmax))[0]
Expand All @@ -215,17 +288,18 @@ def makePlot(
outlierPatches.append(patches[ind])
outlierPatchCollection = PatchCollection(
outlierPatches,
cmap=blueGreen,
cmap=colorMap,
norm=norm,
facecolors="none",
edgecolors="r",
edgecolors="k",
linewidths=0.5,
zorder=100,
)
ax.add_collection(outlierPatchCollection)
# Add legend information.
outlierPatch = Patch(
facecolor="none",
edgecolor="r",
edgecolor="k",
linewidth=0.5,
label="Outlier",
)
Expand All @@ -240,6 +314,7 @@ def makePlot(
nanPatchCollection = PatchCollection(
nanPatches,
cmap=None,
norm=norm,
facecolors="white",
edgecolors="grey",
linestyles="dotted",
Expand All @@ -260,27 +335,36 @@ def makePlot(
if len(handles) > 0:
fig.legend(handles=handles)

# Add text boxes to show the number of tracts, number of NaNs, and
# the five largest outlier values.
outlierText = self._getMaxOutlierVals(tracts, colBarVals, outlierInds)
# Add text boxes to show the number of tracts, number of NaNs,
# median, sigma MAD, and the five largest outlier values.
outlierText = self._getMaxOutlierVals(self.colorBarRange, tracts, colBarVals, outlierInds)
# Make vertical text spacing readable for different figure sizes.
multiplier = 3.5 / self.figureSize[1]
verticalSpacing = 0.026 * multiplier
verticalSpacing = 0.028 * multiplier
fig.text(
0.01,
0.01 + 2 * verticalSpacing,
0.01 + 3 * verticalSpacing,
f"Num tracts: {len(tracts)}",
transform=fig.transFigure,
fontsize=8,
alpha=0.7,
)
fig.text(
0.01,
0.01 + verticalSpacing,
0.01 + 2 * verticalSpacing,
f"Num nans: {len(nanInds)}",
transform=fig.transFigure,
fontsize=8,
alpha=0.7,
)
fig.text(
0.01,
0.01 + verticalSpacing,
f"Median: {med:.3f}; " + r"$\sigma_{MAD}$" + f": {sigmaMad:.3f}",
transform=fig.transFigure,
fontsize=8,
alpha=0.7,
)
fig.text(0.01, 0.01, outlierText, transform=fig.transFigure, fontsize=8, alpha=0.7)

# Truncate the color range to (vmin, vmax).
Expand Down Expand Up @@ -312,9 +396,11 @@ def makePlot(
text.set_path_effects([pathEffects.Stroke(linewidth=3, foreground="w"), pathEffects.Normal()])

# Finalize plot appearance.
plt.grid(True)
ax.grid()
ax.set_axisbelow(True)
ax.set_aspect("equal")
fig = plt.gcf()
plotInfo["bands"] = band
fig = addPlotInfo(fig, plotInfo)
plt.subplots_adjust(left=0.08, right=0.97, top=0.8, bottom=0.17, wspace=0.35)

Expand Down
Loading

0 comments on commit 1eab82d

Please sign in to comment.