Coverage for python/lsst/analysis/tools/actions/plot/diaSkyPlot.py: 29%

60 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-01-05 04:11 -0800

1# This file is part of analysis_tools. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (https://www.lsst.org). 

6# See the COPYRIGHT file at the top-level directory of this distribution 

7# for details of code ownership. 

8# 

9# This program is free software: you can redistribute it and/or modify 

10# it under the terms of the GNU General Public License as published by 

11# the Free Software Foundation, either version 3 of the License, or 

12# (at your option) any later version. 

13# 

14# This program is distributed in the hope that it will be useful, 

15# but WITHOUT ANY WARRANTY; without even the implied warranty of 

16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

17# GNU General Public License for more details. 

18# 

19# You should have received a copy of the GNU General Public License 

20# along with this program. If not, see <https://www.gnu.org/licenses/>. 

21 

22__all__ = ("DiaSkyPanel", "DiaSkyPlot") 

23 

24from typing import Mapping, cast 

25 

26import matplotlib.pyplot as plt 

27from lsst.pex.config import ConfigDictField, Field 

28from matplotlib.figure import Figure 

29 

30from ...interfaces import KeyedData, KeyedDataSchema, PlotAction, Vector 

31from .plotUtils import PanelConfig 

32 

33 

34class DiaSkyPanel(PanelConfig): 

35 """Configuration options for DiaSkyPlot panels.""" 

36 

37 xlabel = Field[str]( 

38 doc="Panel x-axis label.", 

39 default="RA (deg)", 

40 ) 

41 ylabel = Field[str]( 

42 doc="Panel y-axis label.", 

43 default="Dec (deg)", 

44 ) 

45 invertXAxis = Field[bool]( 

46 doc="Invert x-axis?", 

47 default=True, 

48 ) 

49 color = Field[str]( 

50 doc="Point color", 

51 default="C0", 

52 ) 

53 size = Field[float]( 

54 doc="Point size", 

55 default=5, 

56 ) 

57 alpha = Field[float]( 

58 doc="Point transparency", 

59 default=0.5, 

60 ) 

61 # Eventually we might retrieve data from more columns to make the plot 

62 # prettier/more information rich 

63 ra = Field[str]( 

64 doc="Name of RA column", 

65 optional=False, 

66 ) 

67 dec = Field[str]( 

68 doc="Name of Dec column", 

69 optional=False, 

70 ) 

71 

72 

73class DiaSkyPlot(PlotAction): 

74 """Generic pseudo base class for plotting DiaSources 

75 (or DiaObjects) on the sky. 

76 """ 

77 

78 panels = ConfigDictField( 

79 doc="A configurable dict describing the panels to be plotted (both data columns and layouts).", 

80 keytype=str, 

81 itemtype=DiaSkyPanel, 

82 default={}, 

83 ) 

84 

85 def getInputSchema(self) -> KeyedDataSchema: 

86 """Defines the schema this plot action expects (the keys it looks 

87 for and what type they should be). In other words, verifies that 

88 the input data has the columns we are expecting with the right dtypes. 

89 """ 

90 for panel in self.panels.values(): 

91 yield (panel.ra, Vector) 

92 yield (panel.dec, Vector) 

93 

94 def __call__(self, data: KeyedData, **kwargs) -> Mapping[str, Figure] | Figure: 

95 return self.makePlot(data, **kwargs) 

96 

97 def makePlot(self, data: KeyedData, **kwargs) -> Figure: 

98 """Make an N-panel plot with locations of DiaSources or 

99 DiaObjects displayed in each panel. 

100 

101 Parameters 

102 ---------- 

103 data : `lsst.analysis.tools.interfaces.KeyedData` 

104 

105 Returns 

106 ------- 

107 fig : `matplotlib.figure.Figure` 

108 """ 

109 if "figsize" in kwargs: 

110 figsize = kwargs.pop("figsize", "") 

111 fig = plt.figure(figsize=figsize, dpi=600) 

112 else: 

113 fig = plt.figure(figsize=(8, 6), dpi=600) 

114 axs = self._makeAxes(fig) 

115 for panel, ax in zip(self.panels.values(), axs): 

116 self._makePanel(data, panel, ax, **kwargs) 

117 plt.draw() 

118 return fig 

119 

120 def _makeAxes(self, fig): 

121 """Determine axes layout for main figure. 

122 

123 Use matplotlib's subplot2grid to determine the panel geometry, 

124 which calls gridspec. 

125 

126 Parameters 

127 ---------- 

128 fig : `matplotlib.figure.Figure` 

129 

130 Returns 

131 ------- 

132 axs : `list` containing one or more matplotlib axes, one for each panel 

133 """ 

134 axs = [] 

135 for count, panel in enumerate(self.panels.values()): 

136 subplot2gridShape = (panel.subplot2gridShapeRow, panel.subplot2gridShapeColumn) 

137 subplot2gridLoc = (panel.subplot2gridLocRow, panel.subplot2gridLocColumn) 

138 axs.append( 

139 plt.subplot2grid( 

140 subplot2gridShape, 

141 subplot2gridLoc, 

142 rowspan=panel.subplot2gridRowspan, 

143 colspan=panel.subplot2gridColspan, 

144 ) 

145 ) 

146 return axs 

147 

148 def _makePanel(self, data, panel, ax, **kwargs): 

149 """Plot a single panel. 

150 

151 Parameters 

152 ---------- 

153 data : `lsst.analysis.tools.interfaces.KeyedData` 

154 panel : `DiaSkyPanel` 

155 ax : matplotlib axis 

156 color : `str` 

157 """ 

158 ras = cast(Vector, data[panel.ra]) 

159 decs = cast(Vector, data[panel.dec]) 

160 

161 ax.scatter(ras, decs, c=panel.color, s=panel.size, alpha=panel.alpha, marker=".", linewidths=0) 

162 ax.set_xlabel(panel.xlabel) 

163 ax.set_ylabel(panel.ylabel) 

164 

165 if panel.invertXAxis: 

166 ax.invert_xaxis() 

167 if panel.topSpinesVisible: 

168 ax.spines["top"].set_visible(True) 

169 else: 

170 ax.spines["top"].set_visible(False) 

171 if not panel.bottomSpinesVisible: # default is True 

172 ax.spines["bottom"].set_visible(False) 

173 if not panel.leftSpinesVisible: 

174 # Default is True; if False, also put ticks and labels on the right 

175 ax.spines["left"].set_visible(False) 

176 ax.yaxis.tick_right() 

177 ax.yaxis.set_label_position("right") 

178 if not panel.rightSpinesVisible: # default is True 

179 ax.spines["right"].set_visible(False)