File: plots.py

package info (click to toggle)
grass 8.4.2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 277,040 kB
  • sloc: ansic: 460,798; python: 227,732; cpp: 42,026; sh: 11,262; makefile: 7,007; xml: 3,637; sql: 968; lex: 520; javascript: 484; yacc: 450; asm: 387; perl: 157; sed: 25; objc: 6; ruby: 4
file content (338 lines) | stat: -rw-r--r-- 11,423 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
"""
@package iclass.plots

@brief wxIClass plots (histograms, coincidence plots).

Classes:
 - plots::PlotPanel

(C) 2006-2011,2013 by the GRASS Development Team
This program is free software under the GNU General Public
License (>=v2). Read the file COPYING that comes with GRASS
for details.

@author Vaclav Petras <wenzeslaus gmail.com>
@author Anna Kratochvilova <kratochanna gmail.com>
"""

import wx

import wx.lib.plot as plot
import wx.lib.scrolledpanel as scrolled
from core.gcmd import GError


class PlotPanel(scrolled.ScrolledPanel):
    """Panel for drawing multiple plots.

    There are three types of plots: histograms, coincidence plots and scatter plots.
    Histograms show frequency of cell category values in training areas
    for each band and for one category. Coincidence plots show min max range
    of classes for each band.
    """

    def __init__(self, parent, giface, stats_data):
        scrolled.ScrolledPanel.__init__(self, parent)

        self.SetupScrolling(scroll_x=False, scroll_y=True)
        self._giface = giface
        self.parent = parent
        self.canvasList = []
        self.bandList = []
        self.stats_data = stats_data
        self.currentCat = None

        self.mainSizer = wx.BoxSizer(wx.VERTICAL)

        self._createControlPanel()
        self._createPlotPanel()
        self._createScatterPlotPanel()

        self.SetSizer(self.mainSizer)
        self.mainSizer.Fit(self)
        self.Layout()

    def CloseWindow(self):
        if self.iscatt_panel:
            self.iscatt_panel.CloseWindow()

    def _createPlotPanel(self):
        self.canvasPanel = wx.Panel(parent=self)
        self.mainSizer.Add(self.canvasPanel, proportion=1, flag=wx.EXPAND, border=0)
        self.canvasSizer = wx.BoxSizer(wx.VERTICAL)
        self.canvasPanel.SetSizer(self.canvasSizer)

    def _createControlPanel(self):
        self.plotSwitch = wx.Choice(
            self,
            id=wx.ID_ANY,
            choices=[_("Histograms"), _("Coincident plots"), _("Scatter plots")],
        )
        self.mainSizer.Add(
            self.plotSwitch, proportion=0, flag=wx.EXPAND | wx.ALL, border=5
        )
        self.plotSwitch.Bind(wx.EVT_CHOICE, self.OnPlotTypeSelected)

    def _createScatterPlotPanel(self):
        """Init interactive scatter plot tool"""
        try:
            from iscatt.frame import IClassIScattPanel

            self.iscatt_panel = IClassIScattPanel(
                parent=self,
                giface=self._giface,
                iclass_mapwin=self.parent.GetFirstWindow(),
            )
            self.mainSizer.Add(
                self.iscatt_panel, proportion=1, flag=wx.EXPAND, border=0
            )
            self.iscatt_panel.Hide()
        except ImportError as e:
            self.scatt_error = _(
                "Scatter plot functionality is disabled.\n\nReason: "
                "Unable to import packages needed for scatter plot.\n%s" % e
            )
            wx.CallAfter(GError, self.scatt_error, showTraceback=False, parent=self)
            self.iscatt_panel = None

    def OnPlotTypeSelected(self, event):
        """Plot type selected"""

        if self.plotSwitch.GetSelection() in [0, 1]:
            self.SetupScrolling(scroll_x=False, scroll_y=True)
            if self.iscatt_panel:
                self.iscatt_panel.Hide()
            self.canvasPanel.Show()
            self.Layout()

        elif self.plotSwitch.GetSelection() == 2:
            self.SetupScrolling(scroll_x=False, scroll_y=False)
            if self.iscatt_panel:
                self.iscatt_panel.Show()
            else:
                GError(self.scatt_error)
            self.canvasPanel.Hide()
            self.Layout()

        if self.currentCat is None:
            return

        if self.plotSwitch.GetSelection() == 0:
            stat = self.stats_data.GetStatistics(self.currentCat)
            if not stat.IsReady():
                self.ClearPlots()
                return
            self.DrawHistograms(stat)
        else:
            self.DrawCoincidencePlots()

        self.Layout()

    def StddevChanged(self):
        """Standard deviation multiplier changed, redraw histograms"""
        if self.plotSwitch.GetSelection() == 0:
            stat = self.stats_data.GetStatistics(self.currentCat)
            self.UpdateRanges(stat)

    def EnableZoom(self, type, enable=True):
        for canvas in self.canvasList:
            canvas.enableZoom = enable

        # canvas.zoom = type

    def EnablePan(self, enable=True):
        for canvas in self.canvasList:
            canvas.SetEnableDrag(enable)

    def DestroyPlots(self):
        """Destroy all plot canvases"""
        for panel in self.canvasList:
            panel.Destroy()

        self.canvasList = []

    def ClearPlots(self):
        """Clears plot canvases"""
        for bandIdx in range(len(self.bandList)):
            self.canvasList[bandIdx].Clear()

    def Reset(self):
        """Reset plots (when new map imported)"""
        self.currentCat = None
        self.ClearPlots()
        # bands are still the same

    def CreatePlotCanvases(self):
        """Create plot canvases according to the number of bands"""
        for band in self.bandList:
            canvas = plot.PlotCanvas(self.canvasPanel)
            canvas.SetMinSize((-1, 140))
            canvas.fontSizeTitle = 10
            canvas.fontSizeAxis = 8
            self.canvasList.append(canvas)

            self.canvasSizer.Add(canvas, proportion=1, flag=wx.EXPAND, border=0)

        self.SetVirtualSize(self.GetBestVirtualSize())
        self.Layout()

    def UpdatePlots(self, group, subgroup, currentCat, stats_data):
        """Update plots after new analysis

        :param group: imagery group
        :param subgroup: imagery group
        :param currentCat: currently selected category (class)
        :param stats_data: StatisticsData instance (defined in statistics.py)
        """
        self.stats_data = stats_data
        self.currentCat = currentCat
        self.bandList = self.parent.GetGroupLayers(group, subgroup)

        graphType = self.plotSwitch.GetSelection()

        stat = self.stats_data.GetStatistics(currentCat)
        if not stat.IsReady() and graphType == 0:
            return

        self.DestroyPlots()
        self.CreatePlotCanvases()
        self.OnPlotTypeSelected(None)

    def UpdateCategory(self, cat):
        self.currentCat = cat

    def DrawCoincidencePlots(self):
        """Draw coincidence plots"""
        for bandIdx in range(len(self.bandList)):
            self.canvasList[bandIdx].ySpec = "none"
            lines = []
            level = 0.5
            lines.append(self.DrawInvisibleLine(level))

            cats = self.stats_data.GetCategories()
            for i, cat in enumerate(cats):
                stat = self.stats_data.GetStatistics(cat)
                if not stat.IsReady():
                    continue
                color = stat.color
                level = i + 1
                line = self.DrawCoincidenceLine(level, color, stat.bands[bandIdx])
                lines.append(line)

            # invisible
            level += 0.5
            lines.append(self.DrawInvisibleLine(level))

            plotGraph = plot.PlotGraphics(lines, title=self.bandList[bandIdx])
            self.canvasList[bandIdx].Draw(plotGraph)

    def DrawCoincidenceLine(self, level, color, bandValues):
        """Draw line between band min and max values

        :param level: y coordinate of line
        :param color: class color
        :param bandValues: BandStatistics instance
        """
        minim = bandValues.min
        maxim = bandValues.max
        points = [(minim, level), (maxim, level)]
        color = wx.Colour(*map(int, color.split(":")))
        return plot.PolyLine(points, colour=color, width=4)

    def DrawInvisibleLine(self, level):
        """Draw white line to achieve better margins"""
        points = [(100, level), (101, level)]
        return plot.PolyLine(points, colour=wx.WHITE, width=1)

    def DrawHistograms(self, statistics):
        """Draw histograms for one class

        :param statistics: statistics for one class
        """
        self.histogramLines = []
        for bandIdx in range(len(self.bandList)):
            self.canvasList[bandIdx].Clear()
            self.canvasList[bandIdx].ySpec = "auto"
            histgramLine = self.CreateHistogramLine(
                bandValues=statistics.bands[bandIdx]
            )

            meanLine = self.CreateMean(bandValues=statistics.bands[bandIdx])

            minLine = self.CreateMin(bandValues=statistics.bands[bandIdx])

            maxLine = self.CreateMax(bandValues=statistics.bands[bandIdx])

            self.histogramLines.append([histgramLine, meanLine, minLine, maxLine])

            maxRangeLine = self.CreateMaxRange(bandValues=statistics.bands[bandIdx])
            minRangeLine = self.CreateMinRange(bandValues=statistics.bands[bandIdx])

            plotGraph = plot.PlotGraphics(
                self.histogramLines[bandIdx] + [minRangeLine, maxRangeLine],
                title=self.bandList[bandIdx],
            )
            self.canvasList[bandIdx].Draw(plotGraph)

    def CreateMinRange(self, bandValues):
        maxVal = max(bandValues.histo)
        rMin = bandValues.rangeMin

        points = [(rMin, 0), (rMin, maxVal)]

        return plot.PolyLine(points, colour=wx.RED, width=1)

    def CreateMaxRange(self, bandValues):
        maxVal = max(bandValues.histo)
        rMax = bandValues.rangeMax
        points = [(rMax, 0), (rMax, maxVal)]

        return plot.PolyLine(points, colour=wx.RED, width=1)

    def CreateMean(self, bandValues):
        maxVal = max(bandValues.histo)
        mean = bandValues.mean
        points = [(mean, 0), (mean, maxVal)]

        return plot.PolyLine(points, colour=wx.BLUE, width=1)

    def CreateMin(self, bandValues):
        maxVal = max(bandValues.histo)
        minim = bandValues.min
        points = [(minim, 0), (minim, maxVal)]

        return plot.PolyLine(points, colour=wx.Colour(200, 200, 200), width=1)

    def CreateMax(self, bandValues):
        maxVal = max(bandValues.histo)
        maxim = bandValues.max
        points = [(maxim, 0), (maxim, maxVal)]

        return plot.PolyLine(points, colour=wx.Colour(200, 200, 200), width=1)

    def CreateHistogramLine(self, bandValues):
        points = []
        for cellCat, count in enumerate(bandValues.histo):
            if cellCat < bandValues.min - 5:
                continue
            if cellCat > bandValues.max + 5:
                break
            points.append((cellCat, count))

        return plot.PolyLine(points, colour=wx.BLACK, width=1)

    def UpdateRanges(self, statistics):
        """Redraw ranges lines in histograms when std dev multiplier changes

        :param statistics: python Statistics instance
        """
        for bandIdx in range(len(self.bandList)):
            self.canvasList[bandIdx].Clear()
            maxRangeLine = self.CreateMaxRange(bandValues=statistics.bands[bandIdx])
            minRangeLine = self.CreateMinRange(bandValues=statistics.bands[bandIdx])

            plotGraph = plot.PlotGraphics(
                self.histogramLines[bandIdx] + [minRangeLine, maxRangeLine],
                title=self.bandList[bandIdx],
            )
            self.canvasList[bandIdx].Draw(plotGraph)