plots.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  1. """
  2. @package iclass.plots
  3. @brief wxIClass plots (histograms, coincidence plots).
  4. Classes:
  5. - plots::PlotPanel
  6. (C) 2006-2011,2013 by the GRASS Development Team
  7. This program is free software under the GNU General Public
  8. License (>=v2). Read the file COPYING that comes with GRASS
  9. for details.
  10. @author Vaclav Petras <wenzeslaus gmail.com>
  11. @author Anna Kratochvilova <kratochanna gmail.com>
  12. """
  13. import wx
  14. from core.globalvar import CheckWxVersion
  15. try:
  16. if CheckWxVersion(version=[3, 0, 0]):
  17. import gui_core.wxlibplot as plot
  18. else:
  19. import wx.lib.plot as plot
  20. except ImportError as e:
  21. print >> sys.stderr, e
  22. import wx.lib.scrolledpanel as scrolled
  23. from core.utils import _
  24. from core.gcmd import GError
  25. class PlotPanel(scrolled.ScrolledPanel):
  26. """Panel for drawing multiple plots.
  27. There are three types of plots: histograms, coincidence plots and scatter plots.
  28. Histograms show frequency of cell category values in training areas
  29. for each band and for one category. Coincidence plots show min max range
  30. of classes for each band.
  31. """
  32. def __init__(self, parent, giface, stats_data):
  33. scrolled.ScrolledPanel.__init__(self, parent)
  34. self.SetupScrolling(scroll_x=False, scroll_y=True)
  35. self._giface = giface
  36. self.parent = parent
  37. self.canvasList = []
  38. self.bandList = []
  39. self.stats_data = stats_data
  40. self.currentCat = None
  41. self.mainSizer = wx.BoxSizer(wx.VERTICAL)
  42. self._createControlPanel()
  43. self._createPlotPanel()
  44. self._createScatterPlotPanel()
  45. self.SetSizer(self.mainSizer)
  46. self.mainSizer.Fit(self)
  47. self.Layout()
  48. def CloseWindow(self):
  49. if self.iscatt_panel:
  50. self.iscatt_panel.CloseWindow()
  51. def _createPlotPanel(self):
  52. self.canvasPanel = wx.Panel(parent=self)
  53. self.mainSizer.Add(
  54. item=self.canvasPanel,
  55. proportion=1,
  56. flag=wx.EXPAND,
  57. border=0)
  58. self.canvasSizer = wx.BoxSizer(wx.VERTICAL)
  59. self.canvasPanel.SetSizer(self.canvasSizer)
  60. def _createControlPanel(self):
  61. self.plotSwitch = wx.Choice(self, id=wx.ID_ANY,
  62. choices=[_("Histograms"),
  63. _("Coincident plots"),
  64. _("Scatter plots")])
  65. self.mainSizer.Add(
  66. self.plotSwitch,
  67. proportion=0,
  68. flag=wx.EXPAND | wx.ALL,
  69. border=5)
  70. self.plotSwitch.Bind(wx.EVT_CHOICE, self.OnPlotTypeSelected)
  71. def _createScatterPlotPanel(self):
  72. """Init interactive scatter plot tool
  73. """
  74. try:
  75. from iscatt.frame import IClassIScattPanel
  76. self.iscatt_panel = IClassIScattPanel(
  77. parent=self, giface=self._giface,
  78. iclass_mapwin=self.parent.GetFirstWindow())
  79. self.mainSizer.Add(
  80. self.iscatt_panel,
  81. proportion=1,
  82. flag=wx.EXPAND,
  83. border=0)
  84. self.iscatt_panel.Hide()
  85. except ImportError as e:
  86. self.scatt_error = _(
  87. "Scatter plot functionality is disabled.\n\nReason: "
  88. "Unable to import packages needed for scatter plot.\n%s" %
  89. e)
  90. wx.CallAfter(
  91. GError,
  92. self.scatt_error,
  93. showTraceback=False,
  94. parent=self)
  95. self.iscatt_panel = None
  96. def OnPlotTypeSelected(self, event):
  97. """Plot type selected"""
  98. if self.plotSwitch.GetSelection() in [0, 1]:
  99. self.SetupScrolling(scroll_x=False, scroll_y=True)
  100. if self.iscatt_panel:
  101. self.iscatt_panel.Hide()
  102. self.canvasPanel.Show()
  103. self.Layout()
  104. elif self.plotSwitch.GetSelection() == 2:
  105. self.SetupScrolling(scroll_x=False, scroll_y=False)
  106. if self.iscatt_panel:
  107. self.iscatt_panel.Show()
  108. else:
  109. GError(self.scatt_error)
  110. self.canvasPanel.Hide()
  111. self.Layout()
  112. if self.currentCat is None:
  113. return
  114. if self.plotSwitch.GetSelection() == 0:
  115. stat = self.stats_data.GetStatistics(self.currentCat)
  116. if not stat.IsReady():
  117. self.ClearPlots()
  118. return
  119. self.DrawHistograms(stat)
  120. else:
  121. self.DrawCoincidencePlots()
  122. self.Layout()
  123. def StddevChanged(self):
  124. """Standard deviation multiplier changed, redraw histograms"""
  125. if self.plotSwitch.GetSelection() == 0:
  126. stat = self.stats_data.GetStatistics(self.currentCat)
  127. self.UpdateRanges(stat)
  128. def EnableZoom(self, type, enable=True):
  129. for canvas in self.canvasList:
  130. canvas.SetEnableZoom(enable)
  131. #canvas.zoom = type
  132. def EnablePan(self, enable=True):
  133. for canvas in self.canvasList:
  134. canvas.SetEnableDrag(enable)
  135. def DestroyPlots(self):
  136. """Destroy all plot canvases"""
  137. for panel in self.canvasList:
  138. panel.Destroy()
  139. self.canvasList = []
  140. def ClearPlots(self):
  141. """Clears plot canvases"""
  142. for bandIdx in range(len(self.bandList)):
  143. self.canvasList[bandIdx].Clear()
  144. def Reset(self):
  145. """Reset plots (when new map imported)"""
  146. self.currentCat = None
  147. self.ClearPlots()
  148. # bands are still the same
  149. def CreatePlotCanvases(self):
  150. """Create plot canvases according to the number of bands"""
  151. for band in self.bandList:
  152. canvas = plot.PlotCanvas(self.canvasPanel)
  153. canvas.SetMinSize((-1, 140))
  154. canvas.SetFontSizeTitle(10)
  155. canvas.SetFontSizeAxis(8)
  156. self.canvasList.append(canvas)
  157. self.canvasSizer.Add(
  158. item=canvas,
  159. proportion=1,
  160. flag=wx.EXPAND,
  161. border=0)
  162. self.SetVirtualSize(self.GetBestVirtualSize())
  163. self.Layout()
  164. def UpdatePlots(self, group, subgroup, currentCat, stats_data):
  165. """Update plots after new analysis
  166. :param group: imagery group
  167. :param subgroup: imagery group
  168. :param currentCat: currently selected category (class)
  169. :param stats_data: StatisticsData instance (defined in statistics.py)
  170. """
  171. self.stats_data = stats_data
  172. self.currentCat = currentCat
  173. self.bandList = self.parent.GetGroupLayers(group, subgroup)
  174. graphType = self.plotSwitch.GetSelection()
  175. stat = self.stats_data.GetStatistics(currentCat)
  176. if not stat.IsReady() and graphType == 0:
  177. return
  178. self.DestroyPlots()
  179. self.CreatePlotCanvases()
  180. self.OnPlotTypeSelected(None)
  181. def UpdateCategory(self, cat):
  182. self.currentCat = cat
  183. def DrawCoincidencePlots(self):
  184. """Draw coincidence plots"""
  185. for bandIdx in range(len(self.bandList)):
  186. self.canvasList[bandIdx].SetYSpec(type='none')
  187. lines = []
  188. level = 0.5
  189. lines.append(self.DrawInvisibleLine(level))
  190. cats = self.stats_data.GetCategories()
  191. for i, cat in enumerate(cats):
  192. stat = self.stats_data.GetStatistics(cat)
  193. if not stat.IsReady():
  194. continue
  195. color = stat.color
  196. level = i + 1
  197. line = self.DrawCoincidenceLine(
  198. level, color, stat.bands[bandIdx])
  199. lines.append(line)
  200. # invisible
  201. level += 0.5
  202. lines.append(self.DrawInvisibleLine(level))
  203. plotGraph = plot.PlotGraphics(lines, title=self.bandList[bandIdx])
  204. self.canvasList[bandIdx].Draw(plotGraph)
  205. def DrawCoincidenceLine(self, level, color, bandValues):
  206. """Draw line between band min and max values
  207. :param level: y coordinate of line
  208. :param color: class color
  209. :param bandValues: BandStatistics instance
  210. """
  211. minim = bandValues.min
  212. maxim = bandValues.max
  213. points = [(minim, level), (maxim, level)]
  214. color = wx.Colour(*map(int, color.split(':')))
  215. return plot.PolyLine(points, colour=color, width=4)
  216. def DrawInvisibleLine(self, level):
  217. """Draw white line to achieve better margins"""
  218. points = [(100, level), (101, level)]
  219. return plot.PolyLine(points, colour=wx.WHITE, width=1)
  220. def DrawHistograms(self, statistics):
  221. """Draw histograms for one class
  222. :param statistics: statistics for one class
  223. """
  224. self.histogramLines = []
  225. for bandIdx in range(len(self.bandList)):
  226. self.canvasList[bandIdx].Clear()
  227. self.canvasList[bandIdx].SetYSpec(type='auto')
  228. histgramLine = self.CreateHistogramLine(
  229. bandValues=statistics.bands[bandIdx])
  230. meanLine = self.CreateMean(bandValues=statistics.bands[bandIdx])
  231. minLine = self.CreateMin(bandValues=statistics.bands[bandIdx])
  232. maxLine = self.CreateMax(bandValues=statistics.bands[bandIdx])
  233. self.histogramLines.append(
  234. [histgramLine, meanLine, minLine, maxLine])
  235. maxRangeLine = self.CreateMaxRange(
  236. bandValues=statistics.bands[bandIdx])
  237. minRangeLine = self.CreateMinRange(
  238. bandValues=statistics.bands[bandIdx])
  239. plotGraph = plot.PlotGraphics(
  240. self.histogramLines[bandIdx] + [minRangeLine, maxRangeLine],
  241. title=self.bandList[bandIdx])
  242. self.canvasList[bandIdx].Draw(plotGraph)
  243. def CreateMinRange(self, bandValues):
  244. maxVal = max(bandValues.histo)
  245. rMin = bandValues.rangeMin
  246. points = [(rMin, 0), (rMin, maxVal)]
  247. return plot.PolyLine(points, colour=wx.RED, width=1)
  248. def CreateMaxRange(self, bandValues):
  249. maxVal = max(bandValues.histo)
  250. rMax = bandValues.rangeMax
  251. points = [(rMax, 0), (rMax, maxVal)]
  252. return plot.PolyLine(points, colour=wx.RED, width=1)
  253. def CreateMean(self, bandValues):
  254. maxVal = max(bandValues.histo)
  255. mean = bandValues.mean
  256. points = [(mean, 0), (mean, maxVal)]
  257. return plot.PolyLine(points, colour=wx.BLUE, width=1)
  258. def CreateMin(self, bandValues):
  259. maxVal = max(bandValues.histo)
  260. minim = bandValues.min
  261. points = [(minim, 0), (minim, maxVal)]
  262. return plot.PolyLine(points, colour=wx.Colour(200, 200, 200), width=1)
  263. def CreateMax(self, bandValues):
  264. maxVal = max(bandValues.histo)
  265. maxim = bandValues.max
  266. points = [(maxim, 0), (maxim, maxVal)]
  267. return plot.PolyLine(points, colour=wx.Colour(200, 200, 200), width=1)
  268. def CreateHistogramLine(self, bandValues):
  269. points = []
  270. for cellCat, count in enumerate(bandValues.histo):
  271. if cellCat < bandValues.min - 5:
  272. continue
  273. if cellCat > bandValues.max + 5:
  274. break
  275. points.append((cellCat, count))
  276. return plot.PolyLine(points, colour=wx.BLACK, width=1)
  277. def UpdateRanges(self, statistics):
  278. """Redraw ranges lines in histograms when std dev multiplier changes
  279. :param statistics: python Statistics instance
  280. """
  281. for bandIdx in range(len(self.bandList)):
  282. self.canvasList[bandIdx].Clear()
  283. maxRangeLine = self.CreateMaxRange(
  284. bandValues=statistics.bands[bandIdx])
  285. minRangeLine = self.CreateMinRange(
  286. bandValues=statistics.bands[bandIdx])
  287. plotGraph = plot.PlotGraphics(
  288. self.histogramLines[bandIdx] + [minRangeLine, maxRangeLine],
  289. title=self.bandList[bandIdx])
  290. self.canvasList[bandIdx].Draw(plotGraph)