[GRASS-SVN] r57861 - in grass/trunk/gui/wxpython: . iscatt

svn_grass at osgeo.org svn_grass at osgeo.org
Sun Sep 29 13:54:52 PDT 2013


Author: turek
Date: 2013-09-29 13:54:52 -0700 (Sun, 29 Sep 2013)
New Revision: 57861

Added:
   grass/trunk/gui/wxpython/iscatt/
   grass/trunk/gui/wxpython/iscatt/__init__.py
   grass/trunk/gui/wxpython/iscatt/controllers.py
   grass/trunk/gui/wxpython/iscatt/core_c.py
   grass/trunk/gui/wxpython/iscatt/dialogs.py
   grass/trunk/gui/wxpython/iscatt/frame.py
   grass/trunk/gui/wxpython/iscatt/iscatt_core.py
   grass/trunk/gui/wxpython/iscatt/plots.py
   grass/trunk/gui/wxpython/iscatt/toolbars.py
Log:
wx.iscatt: frontend

Added: grass/trunk/gui/wxpython/iscatt/__init__.py
===================================================================
--- grass/trunk/gui/wxpython/iscatt/__init__.py	                        (rev 0)
+++ grass/trunk/gui/wxpython/iscatt/__init__.py	2013-09-29 20:54:52 UTC (rev 57861)
@@ -0,0 +1,9 @@
+all = [
+    'dialogs',
+    'controllers',
+    'frame',
+    'plots',
+    'iscatt_core',
+    'toolbars',
+    'core_c',
+    ]


Property changes on: grass/trunk/gui/wxpython/iscatt/__init__.py
___________________________________________________________________
Added: svn:mime-type
   + text/x-python
Added: svn:eol-style
   + native

Added: grass/trunk/gui/wxpython/iscatt/controllers.py
===================================================================
--- grass/trunk/gui/wxpython/iscatt/controllers.py	                        (rev 0)
+++ grass/trunk/gui/wxpython/iscatt/controllers.py	2013-09-29 20:54:52 UTC (rev 57861)
@@ -0,0 +1,1090 @@
+"""!
+ at package iscatt.controllers
+
+ at brief Controller layer wx.iscatt.
+
+Classes:
+ - controllers::ScattsManager
+ - controllers::PlotsRenderingManager
+ - controllers::CategoriesManager
+ - controllers::IMapWinDigitConnection
+ - controllers::IClassDigitConnection
+ - controllers::IMapDispConnection
+ - controllers::IClassConnection
+ - controllers::gThread
+
+(C) 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.
+
+ at author Stepan Turek <stepan.turek seznam.cz> (mentor: Martin Landa)
+"""
+import os
+import sys
+from copy import deepcopy
+import wx
+
+import time
+import threading
+import Queue
+from core.gconsole import EVT_CMD_DONE
+
+from core.gcmd import GException, GError, GMessage, RunCommand
+from core.settings import UserSettings
+from core.gconsole import wxCmdRun, wxCmdDone, wxCmdPrepare
+from iscatt.iscatt_core import Core, idBandsToidScatt
+from iscatt.dialogs import AddScattPlotDialog, ExportCategoryRaster
+
+from iclass.dialogs import IClassGroupDialog
+
+import grass.script as grass
+from grass.pydispatch.signal import Signal
+
+class ScattsManager:
+    """!Main controller
+    """
+    def __init__(self, guiparent, giface, iclass_mapwin = None):
+        self.giface = giface
+        self.mapDisp  = giface.GetMapDisplay()
+
+        if iclass_mapwin:
+            self.mapWin = iclass_mapwin
+        else:
+            self.mapWin = giface.GetMapWindow()
+
+        self.guiparent = guiparent
+
+        self.show_add_scatt_plot = False
+
+        self.core = Core()
+
+        self.cats_mgr = CategoriesManager(self, self.core)
+        self.render_mgr = PlotsRenderingManager(scatt_mgr=self, 
+                                                cats_mgr=self.cats_mgr, 
+                                                core=self.core)
+
+        self.thread = gThread();
+        
+        self.plots = {}
+
+        self.plot_mode =  None
+        self.pol_sel_mode = [False, None]
+
+        self.data_set = False
+
+        self.cursorPlotMove = Signal("ScattsManager.cursorPlotMove")
+
+        self.renderingStarted = self.render_mgr.renderingStarted
+        self.renderingFinished = self.render_mgr.renderingFinished
+
+        self.computingStarted = Signal("ScattsManager.computingStarted")
+
+        if iclass_mapwin: 
+            self.digit_conn = IClassDigitConnection(self, 
+                                                    self.mapWin, 
+                                                    self.core.CatRastUpdater())
+            self.iclass_conn = IClassConnection(self, 
+                                                iclass_mapwin.parent, 
+                                                self.cats_mgr)
+        else:
+            self.digit_conn = IMapWinDigitConnection()
+            self.iclass_conn = IMapDispConnection(scatt_mgr=self, 
+                                                  cats_mgr=self.cats_mgr, 
+                                                  giface=self.giface)
+
+        self._initSettings()
+
+        self.modeSet = Signal("ScattsManager.mondeSet")
+
+    def CleanUp(self):
+        self.thread.Terminate() 
+        # there should be better way hot to clean up the thread
+        # than calling the clean up function outside the thread,
+        # which still may running
+        self.core.CleanUp()
+
+    def CleanUpDone(self):
+        for scatt_id, scatt in self.plots.items():
+            if scatt['scatt']:
+                scatt['scatt'].CleanUp()
+
+        self.plots.clear()
+    
+    def _initSettings(self):
+        """!Initialization of settings (if not already defined)
+        """
+        # initializes default settings
+        initSettings = [
+                        ['selection', 'sel_pol', (255,255,0)],
+                        ['selection', 'sel_pol_vertex', (255,0,0)],
+                        ['selection', 'sel_area', (0,255,19)],
+                        ['selection', "snap_tresh", 10],
+                        ['selection', 'sel_area_opacty', 50],
+                        ['ellipses', 'show_ellips', True],
+                       ]
+
+        for init in initSettings: 
+            UserSettings.ReadSettingsFile()
+            UserSettings.Append(dict = UserSettings.userSettings, 
+                                group ='scatt',
+                                key = init[0],
+                                subkey =init[1],
+                                value = init[2],
+                                overwrite = False)
+
+    def SetData(self):
+        self.iclass_conn.SetData()
+        self.digit_conn.SetData()
+
+    def SetBands(self, bands):
+        self.busy = wx.BusyInfo(_("Loading data..."))
+        self.data_set = False
+        self.thread.Run(callable=self.core.CleanUp, 
+                        ondone=lambda event : self.CleanUpDone())
+
+        if self.show_add_scatt_plot:
+            show_add=True
+        else:
+            show_add=False
+
+        self.thread.Run(callable=self.core.SetData, 
+                        bands=bands, 
+                        ondone=self.SetDataDone, 
+                        userdata={"show_add" : show_add})
+
+    def SetDataDone(self, event):
+        del self.busy
+        self.data_set = True
+
+        self.cats_mgr.SetData()
+        if event.userdata['show_add']:
+          self.AddScattPlot()
+
+    def GetBands(self):
+        return self.core.GetBands()
+
+    def AddScattPlot(self):
+        if not self.data_set and self.iclass_conn:
+            self.show_add_scatt_plot = True
+            self.iclass_conn.SetData()
+            self.show_add_scatt_plot = False
+            return
+        if not self.data_set:
+            GError(_('No data set.'))
+            return
+
+        self.computingStarted.emit()
+
+        bands = self.core.GetBands()
+
+        #added_bands_ids = []
+        #for scatt_id in self.plots):
+        #    added_bands_ids.append[idBandsToidScatt(scatt_id)]
+
+        self.digit_conn.Update()
+        dlg = AddScattPlotDialog(parent = self.guiparent, 
+                                 bands = bands,
+                                 added_scatts_ids = self.plots.keys())
+        if dlg.ShowModal() == wx.ID_OK:
+
+            scatt_ids = []
+            sel_bands = dlg.GetBands()
+
+            for b_1, b_2 in sel_bands:
+           
+                transpose = False
+                if b_1 > b_2:
+                    transpose = True
+                    tmp_band = b_2
+                    b_2 = b_1
+                    b_1 = tmp_band
+
+                scatt_id = idBandsToidScatt(b_1, b_2, len(bands))
+                if self.plots.has_key(scatt_id):
+                    continue
+
+                self.plots[scatt_id] = {'transpose' : transpose,
+                                        'scatt' : None}
+                scatt_ids.append(scatt_id)
+        
+            self._addScattPlot(scatt_ids)
+            
+        dlg.Destroy()
+         
+    def _addScattPlot(self, scatt_ids):
+        self.render_mgr.NewRunningProcess()
+        self.thread.Run(callable=self.core.AddScattPlots,
+                        scatt_ids=scatt_ids, ondone=self.AddScattPlotDone)
+
+    def AddScattPlotDone(self, event):
+        if not self.data_set:
+            return
+
+        scatt_ids = event.kwds['scatt_ids']
+        for s_id in scatt_ids:
+            trans = self.plots[s_id]['transpose']
+
+            self.plots[s_id]['scatt'] = self.guiparent.NewScatterPlot(scatt_id=s_id, 
+                                                                      transpose=trans)
+
+            self.plots[s_id]['scatt'].plotClosed.connect(self.PlotClosed)
+            self.plots[s_id]['scatt'].cursorMove.connect(
+                                        lambda x, y, scatt_id: 
+                                        self.cursorPlotMove.emit(x=x, y=y, 
+                                                                 scatt_id=scatt_id))
+
+            if self.plot_mode:
+                self.plots[s_id]['scatt'].SetMode(self.plot_mode)
+                self.plots[s_id]['scatt'].ZoomToExtend()
+
+        self.render_mgr.RunningProcessDone()
+
+    def PlotClosed(self, scatt_id):
+        del self.plots[scatt_id]
+
+    def SetPlotsMode(self, mode):
+
+        self.plot_mode = mode
+        for scatt in self.plots.itervalues():
+            if scatt['scatt']:
+                scatt['scatt'].SetMode(mode)
+
+        self.modeSet.emit(mode = mode)
+
+    def ActivateSelectionPolygonMode(self, activate):
+        self.pol_sel_mode[0] = activate
+        for scatt in self.plots.itervalues():
+            if not scatt['scatt']:
+                continue
+            scatt['scatt'].SetSelectionPolygonMode(activate)
+            if not activate and self.plot_mode not in ['zoom', 'pan', 'zoom_extend']:
+                self.SetPlotsMode(None)
+
+        self.render_mgr.RunningProcessDone()
+        return activate
+
+    def ProcessSelectionPolygons(self, process_mode):        
+        scatts_polygons = {}        
+        for scatt_id, scatt in self.plots.iteritems():
+            if not scatt['scatt']:
+                continue
+            coords = scatt['scatt'].GetCoords()
+            if coords is not None:
+                scatts_polygons[scatt_id] = coords
+
+        if not scatts_polygons:
+            return
+
+        value = 1
+        if process_mode == 'remove':
+            value = 0
+
+        sel_cat_id = self.cats_mgr.GetSelectedCat()
+        if not sel_cat_id:
+            dlg = wx.MessageDialog(parent = self.guiparent,
+                      message = _("In order to select arrea in scatter plot, "
+                                  "you have to select class first.\n\n"
+                                  "There is no class yet, "
+                                  "do you want to create one?"),
+                      caption = _("No class selected"),
+                      style = wx.YES_NO)
+            if dlg.ShowModal() == wx.ID_YES:
+                self.iclass_conn.EmptyCategories()
+
+        sel_cat_id = self.cats_mgr.GetSelectedCat()
+        if not sel_cat_id:
+            return
+
+        for scatt in self.plots.itervalues():
+            if scatt['scatt']:
+                scatt['scatt'].SetEmpty()
+
+        self.computingStarted.emit()
+
+        self.render_mgr.NewRunningProcess()
+        self.render_mgr.CategoryChanged(cat_ids=[sel_cat_id])
+        self.render_mgr.CategoryCondsChanged(cat_ids=[sel_cat_id])
+
+        self.thread.Run(callable = self.core.UpdateCategoryWithPolygons, 
+                        cat_id = sel_cat_id,
+                        scatts_pols = scatts_polygons,
+                        value = value, ondone=self.SetEditCatDataDone)
+
+    def SetEditCatDataDone(self, event):
+        if not self.data_set:
+            return
+
+        self.render_mgr.RunningProcessDone()
+        if event.exception:
+            GError(_("Error occured during computation of scatter plot category:\n%s"), 
+                      parent = self.guiparent, showTraceback = False)
+
+        cat_id = event.ret
+        self.iclass_conn.RenderCatRast(cat_id)
+            
+    def SettingsUpdated(self, chanaged_setts):
+        self.render_mgr.RenderRequest()
+
+        #['ellipses', 'show_ellips']
+    def GetCategoriesManager(self):
+        return self.cats_mgr
+
+class PlotsRenderingManager:
+    """!Manages rendering of scatter plot.
+
+    @todo still space for optimalization
+    """
+    def __init__(self, scatt_mgr, cats_mgr, core):
+        self.scatt_mgr = scatt_mgr
+        self.cats_mgr = cats_mgr
+        self.core = core
+        self.scatts_dt, self.scatt_conds_dt = self.core.GetScattsData()
+
+        self.runningProcesses = 0
+
+        self.data_to_render = {}
+        self.render_queue = []
+
+        self.cat_ids = []
+        self.cat_cond_ids = []
+
+        self.renderingStarted = Signal("ScattsManager.renderingStarted")
+        self.renderingFinished = Signal("ScattsManager.renderingFinished")
+
+    def AddRenderRequest(self, scatts):
+        for scatt_id, cat_ids in scatts:
+            if not self.data_to_render.has_key[scatt_id]:
+                self.data_to_render = cat_ids
+            else:
+                for c in cat_ids:
+                    if c not in self.data_to_render[scatt_id]:
+                         self.data_to_render[scatt_id].append(c) 
+
+    def NewRunningProcess(self):
+        self.runningProcesses += 1 
+
+    def RunningProcessDone(self):
+        self.runningProcesses -= 1
+        if self.runningProcesses <= 1:
+            self.RenderScattPlts()
+
+    def RenderRequest(self):
+        if self.runningProcesses <= 1:
+            self.RenderScattPlts()
+
+    def CategoryChanged(self, cat_ids):
+        for c in cat_ids:
+            if c not in self.cat_ids:
+                self.cat_ids.append(c)
+
+    def CategoryCondsChanged(self, cat_ids):
+        for c in cat_ids:
+            if c not in self.cat_cond_ids:
+                self.cat_cond_ids.append(c)
+
+    def RenderScattPlts(self, scatt_ids = None):
+        if len(self.render_queue) > 1:
+            return 
+        
+        self.renderingStarted.emit()
+        self.render_queue.append(self.scatt_mgr.thread.GetId())
+
+        cats_attrs = deepcopy(self.cats_mgr.GetCategoriesAttrs())
+        cats = self.cats_mgr.GetCategories()[:]
+        self.scatt_mgr.thread.Run(callable=self._renderscattplts, scatt_ids=scatt_ids,
+                                  cats=cats, cats_attrs=cats_attrs,
+                                  ondone=self.RenderingDone)
+
+    def _renderscattplts(self, scatt_ids, cats, cats_attrs):
+        cats.reverse()
+        cats.insert(0, 0)
+        for i_scatt_id, scatt in self.scatt_mgr.plots.items():
+            if scatt_ids is not None and \
+               i_scatt_id not in scatt_ids:
+                continue
+            if not scatt['scatt']:
+                continue
+
+            scatt_dt = self.scatts_dt.GetScatt(i_scatt_id)
+            if self._showConfEllipses():
+                ellipses_dt = self.scatts_dt.GetEllipses(i_scatt_id, cats_attrs)
+            else:
+                ellipses_dt = {}
+
+            for c in scatt_dt.iterkeys():
+                try:
+                    self.cat_ids.remove(c)
+                    scatt_dt[c]['render']=True
+                except:
+                    scatt_dt[c]['render']=False
+
+            if self.scatt_mgr.pol_sel_mode[0]:
+                self._getSelectedAreas(cats, i_scatt_id, scatt_dt, cats_attrs)
+
+
+
+            scatt['scatt'].Plot(cats_order=cats,
+                                scatts=scatt_dt, 
+                                ellipses=ellipses_dt, 
+                                styles=cats_attrs)
+
+    def RenderingDone(self, event):
+        self.render_queue.remove(event.pid)
+        if not self.render_queue:    
+            self.renderingFinished.emit()
+
+    def _getSelectedAreas(self, cats_order, scatt_id, scatt_dt, cats_attrs):
+
+        cat_id = self.cats_mgr.GetSelectedCat()
+        if not cat_id:
+            return
+
+        sel_a_cat_id = -1        
+
+        s = self.scatt_conds_dt.GetScatt(scatt_id, [cat_id])
+        if not s:
+            return
+
+        cats_order.append(sel_a_cat_id)
+
+        col = UserSettings.Get(group='scatt', 
+                                 key='selection', 
+                                 subkey='sel_area')
+
+        col = ":".join(map(str, col))
+        opac = UserSettings.Get(group='scatt', 
+                                key='selection', 
+                                subkey='sel_area_opacty') / 100.0
+
+        cats_attrs[sel_a_cat_id] = {'color' : col,
+                                    'opacity' : opac,
+                                    'show' : True}
+
+        scatt_dt[sel_a_cat_id] = s[cat_id]
+        
+        scatt_dt[sel_a_cat_id]['render'] = False
+        if cat_id in self.cat_cond_ids:
+            scatt_dt[sel_a_cat_id]['render'] = True
+            self.cat_cond_ids.remove(cat_id)
+
+    def _showConfEllipses(self):
+        return UserSettings.Get(group='scatt', 
+                                key="ellipses", 
+                                subkey="show_ellips")
+
+class CategoriesManager:
+    """!Manages categories list of scatter plot.
+    """
+    def __init__(self, scatt_mgr, core):
+
+        self.core = core
+        self.scatt_mgr = scatt_mgr
+
+        self.cats = {}
+        self.cats_ids = []
+
+        self.sel_cat_id = None
+
+        self.exportRaster = None
+
+        self.initialized = Signal('CategoriesManager.initialized')
+        self.setCategoryAttrs = Signal('CategoriesManager.setCategoryAttrs')
+        self.deletedCategory = Signal('CategoriesManager.deletedCategory')
+        self.addedCategory = Signal('CategoriesManager.addedCategory')  
+
+    def ChangePosition(self, cat_id, new_pos):
+        if new_pos >= len(self.cats_ids):
+            return False
+
+        try:
+            pos = self.cats_ids.index(cat_id)
+        except:
+            return False
+
+        if pos > new_pos:
+            pos -= 1
+
+        self.cats_ids.remove(cat_id)
+
+        self.cats_ids.insert(new_pos, cat_id)
+
+        self.scatt_mgr.render_mgr.RenderRequest()
+        return True
+
+    def _addCategory(self, cat_id):
+        self.scatt_mgr.thread.Run(callable = self.core.AddCategory, 
+                                  cat_id = cat_id)
+
+    def SetData(self):
+
+        if not self.scatt_mgr.data_set:
+            return
+
+        for cat_id in self.cats_ids:
+            self.scatt_mgr.thread.Run(callable = self.core.AddCategory, 
+                                      cat_id = cat_id)
+
+    def AddCategory(self, cat_id = None, name = None, color = None, nstd = None):
+
+        if cat_id is None:
+            if self.cats_ids:
+                cat_id = max(self.cats_ids) + 1
+            else:
+                cat_id = 1
+
+        if self.scatt_mgr.data_set:
+            self.scatt_mgr.thread.Run(callable = self.core.AddCategory, 
+                                      cat_id = cat_id)
+            #TODO check number of cats
+            #if ret < 0: #TODO
+            #    return -1;
+
+        self.cats[cat_id] = {
+                                'name' : 'class_%d' % cat_id,
+                                'color' : "0:0:0",
+                                'opacity' : 1.0,
+                                'show' : True,
+                                'nstd' : 1.0,
+                            }
+
+        self.cats_ids.insert(0, cat_id)
+
+        if name is not None:
+            self.cats[cat_id]["name"] = name
+   
+        if color is not None:
+            self.cats[cat_id]["color"] = color
+
+        if nstd is not None:
+            self.cats[cat_id]["nstd"] = nstd
+
+        self.addedCategory.emit(cat_id = cat_id,
+                                name = self.cats[cat_id]["name"], 
+                                color = self.cats[cat_id]["color"] )
+        return cat_id
+
+    def SetCategoryAttrs(self, cat_id, attrs_dict):
+        render = False
+        update_cat_rast = []
+
+        for k, v in attrs_dict.iteritems():
+            if not render and k in ['color', 'opacity', 'show', 'nstd']:
+                render = True
+            if k in ['color', 'name']:
+                update_cat_rast.append(k)
+
+            self.cats[cat_id][k] = v
+
+        if render:
+            self.scatt_mgr.render_mgr.CategoryChanged(cat_ids=[cat_id])
+            self.scatt_mgr.render_mgr.RenderRequest()
+        
+        if update_cat_rast:
+            self.scatt_mgr.iclass_conn.UpdateCategoryRaster(cat_id, update_cat_rast)
+
+        self.setCategoryAttrs.emit(cat_id = cat_id, attrs_dict = attrs_dict)
+
+    def DeleteCategory(self, cat_id):
+
+        if self.scatt_mgr.data_set:
+            self.scatt_mgr.thread.Run(callable = self.core.DeleteCategory, 
+                                      cat_id = cat_id)
+        del self.cats[cat_id]
+        self.cats_ids.remove(cat_id)
+
+        self.deletedCategory.emit(cat_id = cat_id)
+
+    #TODO emit event?
+    def SetSelectedCat(self, cat_id):
+        self.sel_cat_id = cat_id
+        if self.scatt_mgr.pol_sel_mode[0]:
+            self.scatt_mgr.render_mgr.RenderRequest()
+
+    def GetSelectedCat(self):
+        return self.sel_cat_id
+
+    def GetCategoryAttrs(self, cat_id):
+        #TODO is mutable
+        return self.cats[cat_id]
+
+    def GetCategoriesAttrs(self):
+        #TODO is mutable
+        return self.cats
+     
+    def GetCategories(self):
+        return self.cats_ids[:]
+
+    def SetCategoryPosition(self):
+        if newindex > oldindex:
+            newindex -= 1
+        
+        self.cats_ids.insert(newindex, self.cats_ids.pop(oldindex))
+
+    def ExportCatRast(self, cat_id):
+
+        cat_attrs = self.GetCategoryAttrs(cat_id)
+
+        dlg = ExportCategoryRaster(parent=self.scatt_mgr.guiparent, 
+                                   rasterName=self.exportRaster, 
+                                   title=_("Export scatter plot raster of class <%s>")
+                                            % cat_attrs['name'])
+        
+        if dlg.ShowModal() == wx.ID_OK:
+            self.exportCatRast = dlg.GetRasterName()
+            dlg.Destroy()
+
+            self.scatt_mgr.thread.Run(callable=self.core.ExportCatRast,
+                                      userdata={'name' : cat_attrs['name']},
+                                      cat_id=cat_id,
+                                      rast_name=self.exportCatRast, 
+                                      ondone=self.OnExportCatRastDone)
+
+    def OnExportCatRastDone(self, event):
+        ret, err = event.ret
+        if ret == 0:
+            cat_attrs = self.GetCategoryAttrs(event.kwds['cat_id'])
+            GMessage(_("Scatter plot raster of class <%s> exported to raster map <%s>.") % 
+                      (event.userdata['name'], event.kwds['rast_name']))
+        else:
+            GMessage(_("Export of scatter plot raster of class <%s> to map <%s> failed.\n%s") % 
+                      (event.userdata['name'], event.kwds['rast_name'], err))
+
+
+class IMapWinDigitConnection:
+    """!Manage communication of the scatter plot with digitizer in mapwindow (does not work).
+    """
+    def Update(self):
+        pass
+
+    def SetData(self):
+        pass
+
+class IClassDigitConnection:
+    """!Manages communication of the scatter plot with digitizer in wx.iclass.
+    """
+    def __init__(self, scatt_mgr, mapWin, scatt_rast_updater):
+        self.mapWin = mapWin
+        self.vectMap = None
+        self.scatt_rast_updater = scatt_rast_updater
+        self.scatt_mgr = scatt_mgr
+        self.cats_mgr = scatt_mgr.cats_mgr
+
+        self.cats_to_update = []
+        self.pids = {'mapwin_conn' : []}
+
+        self.thread = self.scatt_mgr.thread
+
+        #TODO
+        self.mapWin.parent.toolbars["vdigit"].editingStarted.connect(self.DigitDataChanged)
+
+    def Update(self):
+        self.thread.Run(callable=self.scatt_rast_updater.SyncWithMap)
+
+    def SetData(self):
+        self.cats_to_update = []
+        self.pids = {'mapwin_conn' : []}
+
+    def _connectSignals(self):
+        self.digit.featureAdded.connect(self.AddFeature)
+        self.digit.areasDeleted.connect(self.DeleteAreas)
+        self.digit.featuresDeleted.connect(self.DeleteAreas)
+        self.digit.vertexMoved.connect(self.EditedFeature)
+        self.digit.vertexRemoved.connect(self.EditedFeature)
+        self.digit.lineEdited.connect(self.EditedFeature)
+        self.digit.featuresMoved.connect(self.EditedFeature)
+
+    def AddFeature(self, new_bboxs, new_areas_cats):
+        if not self.scatt_mgr.data_set:
+            return
+        self.scatt_mgr.computingStarted.emit()
+
+        self.pids['mapwin_conn'].append(self.thread.GetId())
+        self.thread.Run(callable = self.scatt_rast_updater.EditedFeature, 
+                        new_bboxs = new_bboxs, 
+                        old_bboxs = [], 
+                        old_areas_cats = [],
+                        new_areas_cats = new_areas_cats,
+                        ondone=self.OnDone)
+
+    def DeleteAreas(self, old_bboxs, old_areas_cats):
+        if not self.scatt_mgr.data_set:
+            return
+        self.scatt_mgr.computingStarted.emit()
+
+        self.pids['mapwin_conn'].append(self.thread.GetId())
+        self.thread.Run(callable = self.scatt_rast_updater.EditedFeature, 
+                        new_bboxs = [], 
+                        old_bboxs = old_bboxs, 
+                        old_areas_cats = old_areas_cats,
+                        new_areas_cats = [],
+                        ondone=self.OnDone)
+
+    def EditedFeature(self, new_bboxs, new_areas_cats, old_bboxs, old_areas_cats):
+        if not self.scatt_mgr.data_set:
+            return
+        self.scatt_mgr.computingStarted.emit()
+
+        self.pids['mapwin_conn'].append(self.thread.GetId())
+        self.thread.Run(callable = self.scatt_rast_updater.EditedFeature, 
+                        new_bboxs = new_bboxs, 
+                        old_bboxs = old_bboxs, 
+                        old_areas_cats = old_areas_cats,
+                        new_areas_cats = new_areas_cats,
+                        ondone=self.OnDone)
+
+    def DigitDataChanged(self, vectMap, digit):
+
+        self.digit = digit
+        self.vectMap = vectMap
+
+        self.digit.EmitSignals(emit = True)
+
+        self.scatt_rast_updater.SetVectMap(vectMap)
+
+        self._connectSignals()
+
+    def OnDone(self, event):
+        if not self.scatt_mgr.data_set:
+            return
+        self.pids['mapwin_conn'].remove(event.pid)
+        updated_cats = event.ret
+        for cat in updated_cats:
+            if cat not in  self.cats_to_update:
+                self.cats_to_update.append(cat)
+
+        if not self.pids['mapwin_conn']:
+            self.thread.Run(callable = self.scatt_mgr.core.ComputeCatsScatts, 
+                            cats_ids = self.cats_to_update[:], ondone=self.Render)
+            del self.cats_to_update[:]
+
+    def Render(self, event):
+        self.scatt_mgr.render_mgr.RenderScattPlts()
+
+class IMapDispConnection:
+    """!Manage comunication of the scatter plot with mapdisplay in mapwindow.
+    """
+    def __init__(self, scatt_mgr, cats_mgr, giface):
+        self.scatt_mgr = scatt_mgr
+        self.cats_mgr = cats_mgr
+        self.set_g = {'group' :  None, 'subg' :  None}
+        self.giface = giface
+        self.added_cats_rasts = {}
+
+    def SetData(self):
+
+        dlg = IClassGroupDialog(self.scatt_mgr.guiparent, 
+                                group=self.set_g['group'],
+                                subgroup=self.set_g['subg'])
+        
+        bands = []
+        while True:
+            if dlg.ShowModal() == wx.ID_OK:
+                
+                bands = dlg.GetGroupBandsErr(parent=self.scatt_mgr.guiparent)
+                if bands:
+                    name, s = dlg.GetData()
+                    group = grass.find_file(name = name, element = 'group')
+                    self.set_g['group'] = group['name']
+                    self.set_g['subg'] = s
+
+                    break
+            else: 
+                break
+        
+        dlg.Destroy()
+        self.added_cats_rasts = {}
+
+        if bands:
+            self.scatt_mgr.SetBands(bands)
+
+    def EmptyCategories(self):
+        return None
+
+    def UpdateCategoryRaster(self, cat_id, attrs, render = True):
+
+        cat_rast = self.scatt_mgr.core.GetCatRast(cat_id)
+        if not grass.find_file(cat_rast, element = 'cell', mapset = '.')['file']:
+            return
+        cats_attrs = self.cats_mgr.GetCategoryAttrs(cat_id)
+
+        if "color" in attrs:
+            ret, err_msg = RunCommand('r.colors',
+                                      map=cat_rast,
+                                      rules="-",
+                                      stdin="1 %s" % cats_attrs["color"],
+                                      getErrorMsg=True)
+
+            if ret != 0:
+                GError("r.colors failed\n%s" % err_msg)
+            if render:
+                self.giface.updateMap.emit()
+
+        if "name" in attrs:
+            #TODO hack
+            self.giface.GetLayerList()._tree.SetItemText(self.added_cats_rasts[cat_id], 
+                                                         cats_attrs['name'])
+            cats_attrs["name"]
+
+    def RenderCatRast(self, cat_id):
+
+        if not cat_id in self.added_cats_rasts.iterkeys():
+            cat_rast = self.scatt_mgr.core.GetCatRast(cat_id)
+
+            cat_name = self.cats_mgr.GetCategoryAttrs(cat_id)['name']
+            self.UpdateCategoryRaster(cat_id, ['color'], render = False)
+
+            cmd = ['d.rast', 'map=%s' % cat_rast]
+            #TODO HACK
+            layer = self.giface.GetLayerList()._tree.AddLayer(ltype="raster",
+                                                         lname=cat_name,
+                                                         lcmd=cmd,
+                                                         lchecked=True)
+            self.added_cats_rasts[cat_id] = layer
+        else: #TODO settings
+            self.giface.updateMap.emit()
+
+class IClassConnection:
+    """!Manage comunication of the scatter plot with mapdisplay in wx.iclass.
+    """
+    def __init__(self, scatt_mgr, iclass_frame, cats_mgr):
+        self.iclass_frame = iclass_frame
+        self.stats_data = self.iclass_frame.stats_data
+        self.cats_mgr = cats_mgr
+        self.scatt_mgr= scatt_mgr
+        self.added_cats_rasts = []
+
+        self.stats_data.statisticsAdded.connect(self.AddCategory)
+        self.stats_data.statisticsDeleted.connect(self.DeleteCategory)
+        self.stats_data.allStatisticsDeleted.connect(self.DeletAllCategories)
+        self.stats_data.statisticsSet.connect(self.SetCategory)
+
+        self.iclass_frame.groupSet.connect(self.GroupSet)
+
+        self.cats_mgr.setCategoryAttrs.connect(self.SetStatistics)
+        self.cats_mgr.deletedCategory.connect(self.DeleteStatistics)
+        self.cats_mgr.addedCategory.connect(self.AddStatistics)
+
+        self.iclass_frame.categoryChanged.connect(self.CategoryChanged)
+
+        self.SyncCats()
+
+    def UpdateCategoryRaster(self, cat_id, attrs, render = True):
+        if not self.scatt_mgr.data_set:
+            return
+
+        cat_rast = self.scatt_mgr.core.GetCatRast(cat_id)
+        if not cat_rast:
+            return
+
+        if not grass.find_file(cat_rast, element = 'cell', mapset = '.')['file']:
+            return
+        cats_attrs = self.cats_mgr.GetCategoryAttrs(cat_id)
+        train_mgr, preview_mgr = self.iclass_frame.GetMapManagers()
+
+        if "color" in attrs:
+            ret, err_msg = RunCommand('r.colors',
+                                      map=cat_rast,
+                                      rules="-",
+                                      stdin="1 %s" % cats_attrs["color"],
+                                      getErrorMsg=True)
+
+            if ret != 0:
+                GError("r.colors failed\n%s" % err_msg)
+            if render:
+                train_mgr.Render()
+
+        if "name" in attrs:
+            cat_rast = self.scatt_mgr.core.GetCatRast(cat_id)
+
+            train_mgr.SetAlias(original=cat_rast, alias=cats_attrs['name'])
+            cats_attrs["name"]
+
+    def RenderCatRast(self, cat_id):
+
+        train_mgr, preview_mgr = self.iclass_frame.GetMapManagers()
+        if not cat_id in self.added_cats_rasts:
+            cat_rast = self.scatt_mgr.core.GetCatRast(cat_id)
+
+            cat_name = self.cats_mgr.GetCategoryAttrs(cat_id)['name']
+            self.UpdateCategoryRaster(cat_id, ['color'], render = False)
+            train_mgr.AddLayer(cat_rast, alias = cat_name)
+
+            self.added_cats_rasts.append(cat_id)
+        else: #TODO settings
+            train_mgr.Render()
+
+    def SetData(self):
+        self.iclass_frame.AddBands()
+        self.added_cats_rasts = []
+
+    def EmptyCategories(self):
+        self.iclass_frame.OnCategoryManager(None)
+
+    def SyncCats(self, cats_ids = None):
+        self.cats_mgr.addedCategory.disconnect(self.AddStatistics)
+        cats = self.stats_data.GetCategories()
+        for c in cats:
+            if cats_ids and c not in cats_ids:
+                continue
+            stats = self.stats_data.GetStatistics(c)
+            self.cats_mgr.AddCategory(c, stats.name, stats.color, stats.nstd)
+        self.cats_mgr.addedCategory.connect(self.AddStatistics)
+
+    def CategoryChanged(self, cat):
+        self.cats_mgr.SetSelectedCat(cat) 
+
+    def AddCategory(self, cat, name, color):
+        self.cats_mgr.addedCategory.disconnect(self.AddStatistics)
+        stats = self.stats_data.GetStatistics(cat)
+        self.cats_mgr.AddCategory(cat_id = cat, name = name, color = color, nstd = stats.nstd)
+        self.cats_mgr.addedCategory.connect(self.AddStatistics)
+
+    def DeleteCategory(self, cat):
+        self.cats_mgr.deletedCategory.disconnect(self.DeleteStatistics)
+        self.cats_mgr.DeleteCategory(cat)
+        self.cats_mgr.deletedCategory.connect(self.DeleteStatistics)
+
+    def DeletAllCategories(self):
+
+        self.cats_mgr.deletedCategory.disconnect(self.DeleteStatistics)
+        cats = self.stats_data.GetCategories()
+        for c in cats:
+            self.cats_mgr.DeleteCategory(c)
+        self.cats_mgr.deletedCategory.connect(self.DeleteStatistics)
+
+    def SetCategory(self, cat, stats):
+
+        self.cats_mgr.setCategoryAttrs.disconnect(self.SetStatistics)
+        cats_attr = {}
+
+        for attr in ['name', 'color', 'nstd']:
+            if stats.has_key(attr):
+                cats_attr[attr] = stats[attr]
+
+        if cats_attr:
+            self.cats_mgr.SetCategoryAttrs(cat, cats_attr)
+        self.cats_mgr.setCategoryAttrs.connect(self.SetStatistics)
+
+
+    def SetStatistics(self, cat_id, attrs_dict):
+        self.stats_data.statisticsSet.disconnect(self.SetCategory)
+        self.stats_data.GetStatistics(cat_id).SetStatistics(attrs_dict)
+        self.stats_data.statisticsSet.connect(self.SetCategory)
+
+    def AddStatistics(self, cat_id, name, color):
+        self.stats_data.statisticsAdded.disconnect(self.AddCategory)
+        self.stats_data.AddStatistics(cat_id, name, color)
+        self.stats_data.statisticsAdded.connect(self.AddCategory)
+
+    def DeleteStatistics(self, cat_id):
+        self.stats_data.statisticsDeleted.disconnect(self.DeleteCategory)
+        self.stats_data.DeleteStatistics(cat_id)
+        self.stats_data.statisticsDeleted.connect(self.DeleteCategory)
+
+    def GroupSet(self, group, subgroup):
+        kwargs = {}
+        if subgroup:
+            kwargs['subgroup'] = subgroup
+
+        res = RunCommand('i.group',
+                         flags = 'g',
+                         group = group,
+                         read = True, **kwargs).strip()
+
+        if res.split('\n')[0]:
+            bands = res.split('\n')
+            self.scatt_mgr.SetBands(bands)
+
+class gThread(threading.Thread, wx.EvtHandler):
+    """!Thread for scatter plot backend"""
+    requestId = 0
+
+    def __init__(self, requestQ=None, resultQ=None, **kwds):
+        wx.EvtHandler.__init__(self)
+        self.terminate = False
+
+        threading.Thread.__init__(self, **kwds)
+
+        if requestQ is None:
+            self.requestQ = Queue.Queue()
+        else:
+            self.requestQ = requestQ
+
+        if resultQ is None:
+            self.resultQ = Queue.Queue()
+        else:
+            self.resultQ = resultQ
+
+        #self.setDaemon(True)
+
+        self.Bind(EVT_CMD_DONE, self.OnDone)
+        self.start()
+
+    def Run(self, *args, **kwds):
+        """!Run command in queue
+
+        @param args unnamed command arguments
+        @param kwds named command arguments
+
+        @return request id in queue
+        """
+        gThread.requestId += 1
+        self.requestQ.put((gThread.requestId, args, kwds))
+
+        return gThread.requestId
+
+    def GetId(self):
+         """!Get id for next command"""
+         return gThread.requestId + 1
+
+    def SetId(self, id):
+        """!Set starting id"""
+        gThread.requestId = id
+
+    def run(self):
+        while True:
+            requestId, args, kwds = self.requestQ.get()
+            for key in ('callable', 'ondone', 'userdata'):
+                if key in kwds:
+                    vars()[key] = kwds[key]
+                    del kwds[key]
+                else:
+                    vars()[key] = None
+
+            requestTime = time.time()
+
+            ret = None
+            exception = None
+            time.sleep(.1)
+
+            if self.terminate:
+                return
+
+            ret = vars()['callable'](*args, **kwds)
+
+            if self.terminate:
+                return
+            #except Exception as e:
+            #    exception  = e;
+
+            self.resultQ.put((requestId, ret))
+
+            event = wxCmdDone(ondone=vars()['ondone'],
+                              kwds=kwds,
+                              args=args, #TODO expand args to kwds
+                              ret=ret,
+                              exception=exception,
+                              userdata=vars()['userdata'],
+                              pid=requestId)
+
+            # send event
+            wx.PostEvent(self, event)
+
+    def OnDone(self, event):
+        if event.ondone:
+            event.ondone(event)
+
+    def Terminate(self):
+        """!Abort command(s)"""
+        self.terminate = True


Property changes on: grass/trunk/gui/wxpython/iscatt/controllers.py
___________________________________________________________________
Added: svn:mime-type
   + text/x-python
Added: svn:eol-style
   + native

Added: grass/trunk/gui/wxpython/iscatt/core_c.py
===================================================================
--- grass/trunk/gui/wxpython/iscatt/core_c.py	                        (rev 0)
+++ grass/trunk/gui/wxpython/iscatt/core_c.py	2013-09-29 20:54:52 UTC (rev 57861)
@@ -0,0 +1,239 @@
+"""!
+ at package iscatt.core_c
+
+ at brief Wrappers for scatter plot C backend.
+
+(C) 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.
+
+ at author Stepan Turek <stepan.turek seznam.cz> (mentor: Martin Landa)
+"""
+
+import sys
+import numpy as np
+from multiprocessing import Process, Queue
+
+from ctypes import *
+try:
+    from grass.lib.imagery import *
+    from grass.lib.gis import Cell_head, G_get_window
+except ImportError, e:
+    sys.stderr.write(_("Loading ctypes libs failed"))
+
+from core.gcmd import GException
+
+def Rasterize(polygon, rast, region, value):
+    rows, cols = rast.shape 
+
+    #TODO creating of region is on many places
+    region['rows'] = rows
+    region['cols'] = cols
+
+    region['nsres'] = 1.0
+    region['ewres'] = 1.0
+
+    q = Queue()
+    p = Process(target=_rasterize, args=(polygon, rast, region, value, q))
+    p.start()
+    rast = q.get()
+    p.join()
+
+    return rast
+
+def ApplyColormap(vals, vals_mask, colmap, out_vals):
+    
+    c_uint8_p = POINTER(c_uint8)
+
+    vals_p = vals.ctypes.data_as(c_uint8_p)
+
+
+    if hasattr(vals_mask, "ctypes"):
+        vals_mask_p = vals_mask.ctypes.data_as(c_uint8_p)
+    else: #vals mask is empty (all data are selected)
+        vals_mask_p = None
+    colmap_p = colmap.ctypes.data_as(c_uint8_p)
+    out_vals_p = out_vals.ctypes.data_as(c_uint8_p)
+
+    vals_size = vals.reshape((-1)).shape[0]
+    I_apply_colormap(vals_p, vals_mask_p, vals_size, colmap_p, out_vals_p)
+
+def MergeArrays(merged_arr, overlay_arr, alpha):
+    if merged_arr.shape != overlay_arr.shape:
+        GException("MergeArrays: merged_arr.shape != overlay_arr.shape")
+
+    c_uint8_p = POINTER(c_uint8)
+    merged_p = merged_arr.ctypes.data_as(c_uint8_p)
+    overlay_p = overlay_arr.ctypes.data_as(c_uint8_p)
+
+    I_merge_arrays(merged_p, overlay_p, merged_arr.shape[0], merged_arr.shape[1], alpha)
+
+def MergeArrays(merged_arr, overlay_arr, alpha):
+    if merged_arr.shape != overlay_arr.shape:
+        GException("MergeArrays: merged_arr.shape != overlay_arr.shape")
+
+    c_uint8_p = POINTER(c_uint8)
+    merged_p = merged_arr.ctypes.data_as(c_uint8_p)
+    overlay_p = overlay_arr.ctypes.data_as(c_uint8_p)
+
+    I_merge_arrays(merged_p, overlay_p, merged_arr.shape[0], merged_arr.shape[1], alpha)
+
+def ComputeScatts(region, scatt_conds, bands, n_bands, scatts, cats_rasts_conds, cats_rasts):
+
+    q = Queue()
+    p = Process(target=_computeScattsProcess, args=(region, scatt_conds, bands, 
+                                                    n_bands, scatts, cats_rasts_conds, cats_rasts, q))
+    p.start()
+    ret = q.get()
+    p.join()
+    
+    return ret[0], ret[1]
+
+def UpdateCatRast(patch_rast, region, cat_rast):
+    q = Queue()
+    p = Process(target=_updateCatRastProcess, args=(patch_rast, region, cat_rast, q))
+    p.start()
+    ret = q.get()
+    p.join()
+
+    return ret
+
+def CreateCatRast(region, cat_rast):
+    cell_head = _regionToCellHead(region)
+    I_create_cat_rast(pointer(cell_head), cat_rast)   
+
+def _computeScattsProcess(region, scatt_conds, bands, n_bands, scatts, 
+                          cats_rasts_conds, cats_rasts, output_queue):
+
+    #TODO names for types not 0 and 1?
+    sccats_c, cats_rasts_c, refs = _getComputationStruct(scatts, cats_rasts, 
+                                                         SC_SCATT_DATA, n_bands)
+    scatt_conds_c, cats_rasts_conds_c, refs2 = _getComputationStruct(scatt_conds, cats_rasts_conds, 
+                                                                     SC_SCATT_CONDITIONS, n_bands)
+
+    char_bands = _stringListToCharArr(bands)
+   
+    cell_head = _regionToCellHead(region)
+
+    ret = I_compute_scatts(pointer(cell_head),
+                           pointer(scatt_conds_c),
+                           pointer(cats_rasts_conds_c),
+                           pointer(char_bands),
+                           n_bands,
+                           pointer(sccats_c),
+                           pointer(cats_rasts_c))
+
+    I_sc_free_cats(pointer(sccats_c))
+    I_sc_free_cats(pointer(scatt_conds_c))
+
+    output_queue.put((ret, scatts))
+
+def _getBandcRange( band_info):
+    band_c_range = struct_Range()
+
+    band_c_range.max = band_info['max']
+    band_c_range.min = band_info['min']
+
+    return band_c_range
+
+def _regionToCellHead(region):
+    cell_head = struct_Cell_head()
+    G_get_window(pointer(cell_head))
+
+    convert_dict = {'n' : 'north', 'e' : 'east', 
+                    'w' : 'west',  's' : 'south', 
+                    'nsres' : 'ns_res',
+                    'ewres' : 'ew_res'}
+
+    for k, v in region.iteritems():
+        if k in ["rows", "cols", "cells"]:
+            v = int(v)
+        else:
+            v = float(v)
+
+        if convert_dict.has_key(k):
+            k = convert_dict[k]
+           
+        setattr(cell_head, k, v)
+
+    return cell_head
+
+def _stringListToCharArr(str_list):
+
+    arr = c_char_p * len(str_list)
+    char_arr = arr()
+    for i, st in enumerate(str_list):
+        if st:
+            char_arr[i] = st
+        else:
+            char_arr[i] = None
+
+    return char_arr
+
+def _getComputationStruct(cats, cats_rasts, cats_type, n_bands):
+
+    sccats = struct_scCats()
+    I_sc_init_cats(pointer(sccats), c_int(n_bands), c_int(cats_type));
+
+    refs = []        
+    cats_rasts_core = []
+    
+    for cat_id, scatt_ids in cats.iteritems():
+        cat_c_id = I_sc_add_cat(pointer(sccats))
+        cats_rasts_core.append(cats_rasts[cat_id])
+
+        for scatt_id, dt in scatt_ids.iteritems():
+            # if key is missing condition is always True (full scatter plor is computed)
+                vals = dt['np_vals']
+
+                scatt_vals = scdScattData()
+
+                c_void_p = ctypes.POINTER(ctypes.c_void_p)
+
+                if cats_type == SC_SCATT_DATA:
+                    vals[:] = 0
+                elif cats_type == SC_SCATT_CONDITIONS:
+                    pass
+                else:
+                    return None
+                data_p = vals.ctypes.data_as(c_void_p)
+                I_scd_init_scatt_data(pointer(scatt_vals), cats_type, len(vals), data_p)
+
+                refs.append(scatt_vals)
+
+                I_sc_insert_scatt_data(pointer(sccats),  
+                                       pointer(scatt_vals),
+                                       cat_c_id, scatt_id)
+
+    cats_rasts_c = _stringListToCharArr(cats_rasts_core)
+
+    return sccats, cats_rasts_c, refs
+
+def _updateCatRastProcess(patch_rast, region, cat_rast, output_queue):
+    cell_head = _regionToCellHead(region)
+    
+    
+    ret = I_insert_patch_to_cat_rast(patch_rast, 
+                                     pointer(cell_head), 
+                                     cat_rast)
+
+    output_queue.put(ret)
+
+def _rasterize(polygon, rast, region, value, output_queue):    
+    pol_size = len(polygon) * 2
+    pol = np.array(polygon, dtype=float)
+
+    c_uint8_p = POINTER(c_uint8)
+    c_double_p = POINTER(c_double)
+
+    pol_p = pol.ctypes.data_as(c_double_p)
+    rast_p = rast.ctypes.data_as(c_uint8_p)
+
+    cell_h = _regionToCellHead(region)
+    I_rasterize(pol_p, 
+                len(polygon), 
+                value, 
+                pointer(cell_h), rast_p)
+
+    output_queue.put(rast)


Property changes on: grass/trunk/gui/wxpython/iscatt/core_c.py
___________________________________________________________________
Added: svn:mime-type
   + text/x-python
Added: svn:eol-style
   + native

Added: grass/trunk/gui/wxpython/iscatt/dialogs.py
===================================================================
--- grass/trunk/gui/wxpython/iscatt/dialogs.py	                        (rev 0)
+++ grass/trunk/gui/wxpython/iscatt/dialogs.py	2013-09-29 20:54:52 UTC (rev 57861)
@@ -0,0 +1,517 @@
+"""!
+ at package iscatt.dialogs
+
+ at brief Dialogs widgets.
+
+Classes:
+ - dialogs::AddScattPlotDialog
+ - dialogs::ExportCategoryRaster
+ - dialogs::SettingsDialog
+ - dialogs::ManageBusyCursorMixin
+ - dialogs::RenameClassDialog
+
+(C) 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.
+
+ at author Stepan Turek <stepan.turek seznam.cz> (mentor: Martin Landa)
+"""
+import os
+import sys
+
+import wx
+from iscatt.iscatt_core import idBandsToidScatt
+from gui_core.gselect import Select
+import wx.lib.colourselect as csel
+
+import grass.script as grass
+
+from core import globalvar
+from core.gcmd import GMessage
+from core.settings import UserSettings
+from gui_core.dialogs import SimpleDialog
+
+class AddScattPlotDialog(wx.Dialog):
+
+    def __init__(self, parent, bands, added_scatts_ids, id  = wx.ID_ANY):
+        wx.Dialog.__init__(self, parent, title = ("Add scatter plots"), id = id)
+
+        self.added_scatts_ids = added_scatts_ids
+        self.bands = bands
+
+        self.x_band = None
+        self.y_band = None
+
+        self.added_bands_ids = []
+        self.sel_bands_ids = []
+        self._createWidgets()
+
+    def _createWidgets(self):
+
+        self.labels = {}
+        self.params = {}
+
+        self.band_1_label = wx.StaticText(parent = self, id = wx.ID_ANY, label = _("x axis:"))
+
+        self.band_1_ch = wx.ComboBox(parent = self, id = wx.ID_ANY,
+                                     choices = self.bands,
+                                     style = wx.CB_READONLY, size = (350, 30))
+
+        self.band_2_label = wx.StaticText(parent = self, id = wx.ID_ANY, label = _("y axis:"))
+
+        self.band_2_ch = wx.ComboBox(parent = self, id = wx.ID_ANY,
+                                     choices = self.bands,
+                                     style = wx.CB_READONLY, size = (350, 30))
+
+        self.scattsBox = wx.ListBox(parent = self,  id = wx.ID_ANY, size = (-1, 150),
+                                    style = wx.LB_MULTIPLE | wx.LB_NEEDED_SB)
+
+        # buttons
+        self.btn_add = wx.Button(parent=self, id=wx.ID_ANY, label = _("Add"))
+        self.btn_remove = wx.Button(parent=self, id=wx.ID_ANY, label = _("Remove"))
+        
+        self.btn_close = wx.Button(parent=self, id=wx.ID_CANCEL)        
+        self.btn_ok = wx.Button(parent=self, id=wx.ID_OK, label = _("&OK"))
+
+        self._layout()
+
+    def _layout(self):
+
+        border = wx.BoxSizer(wx.VERTICAL) 
+        dialogSizer = wx.BoxSizer(wx.VERTICAL)
+
+        regionSizer = wx.BoxSizer(wx.HORIZONTAL)
+
+        dialogSizer.Add(item = self._addSelectSizer(title = self.band_1_label, 
+                                                    sel = self.band_1_ch))
+
+        dialogSizer.Add(item = self._addSelectSizer(title = self.band_2_label, 
+                                                    sel = self.band_2_ch))
+
+
+        dialogSizer.Add(item=self.btn_add, proportion=0,  flag = wx.TOP, border = 5)
+
+        box = wx.StaticBox(self, id = wx.ID_ANY,
+                           label = " %s " % _("Bands of scatter plots to be added (x y):"))
+        sizer = wx.StaticBoxSizer(box, wx.VERTICAL)
+
+        sizer.Add(item=self.scattsBox, proportion=1, flag=wx.EXPAND | wx.TOP, border=5)
+        sizer.Add(item=self.btn_remove, proportion=0, flag=wx.TOP, border = 5)
+
+        dialogSizer.Add(item=sizer, proportion=1,  flag = wx.EXPAND | wx.TOP, border = 5)
+
+        # buttons
+        self.btnsizer = wx.BoxSizer(orient = wx.HORIZONTAL)
+
+        self.btnsizer.Add(item = self.btn_close, proportion = 0,
+                          flag = wx.RIGHT | wx.LEFT | wx.ALIGN_CENTER,
+                          border = 10)
+        
+        self.btnsizer.Add(item = self.btn_ok, proportion = 0,
+                          flag = wx.RIGHT | wx.LEFT | wx.ALIGN_CENTER,
+                          border = 10)
+
+        dialogSizer.Add(item = self.btnsizer, proportion = 0,
+                        flag = wx.ALIGN_CENTER | wx.TOP, border = 5)
+
+        border.Add(item = dialogSizer, proportion = 0,
+                   flag = wx.LEFT | wx.RIGHT | wx.BOTTOM, border = 10)
+
+        self.SetSizer(border)
+        self.Layout()
+        self.Fit()
+
+        # bindings
+        self.btn_close.Bind(wx.EVT_BUTTON, self.OnClose)
+        self.btn_ok.Bind(wx.EVT_BUTTON, self.OnOk)
+        self.btn_add.Bind(wx.EVT_BUTTON, self.OnAdd)
+        self.btn_remove.Bind(wx.EVT_BUTTON, self.OnRemoveLayer)
+
+    def OnOk(self, event):
+        
+        if not self.GetBands():
+            GMessage(parent=self, message=_("No scatter plots selected."))
+            return
+
+        event.Skip()
+
+    def _addSelectSizer(self, title, sel): 
+        """!Helper layout function.
+        """
+        selSizer = wx.BoxSizer(orient = wx.VERTICAL)
+
+        selTitleSizer = wx.BoxSizer(wx.HORIZONTAL)
+        selTitleSizer.Add(item = title, proportion = 1,
+                          flag = wx.TOP | wx.EXPAND, border = 5)
+
+        selSizer.Add(item = selTitleSizer, proportion = 0,
+                     flag = wx.EXPAND)
+
+        selSizer.Add(item = sel, proportion = 1,
+                     flag = wx.EXPAND | wx.TOP| wx.ALIGN_CENTER_VERTICAL,
+                     border = 5)
+
+        return selSizer
+
+    def GetBands(self):
+        """!Get layers"""
+        return self.sel_bands_ids
+
+    def OnClose(self, event):
+        """!Close dialog
+        """
+        if not self.IsModal():
+            self.Destroy()
+        event.Skip()
+
+    def OnRemoveLayer(self, event):
+        """!Remove layer from listbox"""
+        while self.scattsBox.GetSelections():
+            sel = self.scattsBox.GetSelections()[0]
+            self.scattsBox.Delete(sel)
+            self.sel_bands_ids.pop(sel)
+
+    def OnAdd(self, event):
+        """!
+        """
+        b_x = self.band_1_ch.GetSelection()
+        b_y = self.band_2_ch.GetSelection()
+
+        if b_x < 0 or b_y < 0:
+            GMessage(parent=self, message=_("Select both x and y bands."))
+            return
+        if b_y == b_x:
+            GMessage(parent=self, message=_("Selected bands must be different."))
+            return
+
+        scatt_id = idBandsToidScatt(b_x, b_y, len(self.bands))
+        if scatt_id in self.added_scatts_ids:
+            GMessage(parent=self, 
+                     message=_("Scatter plot with same band combination (regardless x y order) " 
+                               "is already displayed."))
+            return
+
+        if [b_x, b_y] in self.sel_bands_ids or [b_y, b_x] in self.sel_bands_ids:
+            GMessage(parent=self, 
+                     message=_("Scatter plot with same bands combination (regardless x y order) " 
+                               "has been already added into the list."))
+            return
+
+        self.sel_bands_ids.append([b_x, b_y])
+
+        b_x_str = self.band_1_ch.GetStringSelection()
+        b_y_str = self.band_2_ch.GetStringSelection()
+
+        text = b_x_str + " " + b_y_str
+
+        self.scattsBox.Append(text)
+        event.Skip()
+
+class ExportCategoryRaster(wx.Dialog):
+    def __init__(self, parent, title, rasterName = None, id = wx.ID_ANY,
+                 style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER,
+                 **kwargs):
+        """!Dialog for export of category raster.
+        
+        @param parent window
+        @param rasterName name of vector layer for export
+        @param title window title
+        """
+        wx.Dialog.__init__(self, parent, id, title, style = style, **kwargs)
+        
+        self.rasterName = rasterName
+        self.panel = wx.Panel(parent = self, id = wx.ID_ANY)
+        
+        self.btnCancel = wx.Button(parent = self.panel, id = wx.ID_CANCEL)
+        self.btnOK     = wx.Button(parent = self.panel, id = wx.ID_OK)
+        self.btnOK.SetDefault()
+        self.btnOK.Enable(False)
+        self.btnOK.Bind(wx.EVT_BUTTON, self.OnOK)
+        
+        self.__layout()
+        
+        self.vectorNameCtrl.Bind(wx.EVT_TEXT, self.OnTextChanged)
+        self.OnTextChanged(None)
+        wx.CallAfter(self.vectorNameCtrl.SetFocus)
+
+    def OnTextChanged(self, event):
+        """!Name of new vector map given.
+        
+        Enable/diable OK button.
+        """
+        file = self.vectorNameCtrl.GetValue()
+        if len(file) > 0:
+            self.btnOK.Enable(True)
+        else:
+            self.btnOK.Enable(False)
+        
+    def __layout(self):
+        """!Do layout"""
+        sizer = wx.BoxSizer(wx.VERTICAL)
+        
+        dataSizer = wx.BoxSizer(wx.VERTICAL)
+        
+        dataSizer.Add(item = wx.StaticText(parent = self.panel, id = wx.ID_ANY,
+                                           label = _("Enter name of new vector map:")),
+                      proportion = 0, flag = wx.ALL, border = 3)
+        self.vectorNameCtrl = Select(parent = self.panel, type = 'raster',
+                                     mapsets = [grass.gisenv()['MAPSET']],
+                                     size = globalvar.DIALOG_GSELECT_SIZE)
+        if self.rasterName:
+            self.vectorNameCtrl.SetValue(self.rasterName)
+        dataSizer.Add(item = self.vectorNameCtrl,
+                      proportion = 0, flag = wx.ALL | wx.EXPAND, border = 3)
+        
+                      
+        # buttons
+        btnSizer = wx.StdDialogButtonSizer()
+        btnSizer.AddButton(self.btnCancel)
+        btnSizer.AddButton(self.btnOK)
+        btnSizer.Realize()
+        
+        sizer.Add(item = dataSizer, proportion = 1,
+                       flag = wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border = 5)
+        
+        sizer.Add(item = btnSizer, proportion = 0,
+                       flag = wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border = 5)
+        
+        self.panel.SetSizer(sizer)
+        sizer.Fit(self)
+        
+        self.SetMinSize(self.GetSize())
+        
+    def GetRasterName(self):
+        """!Returns vector name"""
+        return self.vectorNameCtrl.GetValue()
+
+    def OnOK(self, event):
+        """!Checks if map exists and can be overwritten."""
+        overwrite = UserSettings.Get(group = 'cmd', key = 'overwrite', subkey = 'enabled')
+        rast_name = self.GetRasterName()
+        res = grass.find_file(rast_name, element = 'cell')
+        if res['fullname'] and overwrite is False:
+            qdlg = wx.MessageDialog(parent = self,
+                                        message = _("Raster map <%s> already exists."
+                                                    " Do you want to overwrite it?" % rast_name) ,
+                                        caption = _("Raster <%s> exists" % rast_name),
+                                        style = wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION | wx.CENTRE)
+            if qdlg.ShowModal() == wx.ID_YES:
+                event.Skip()
+            qdlg.Destroy()
+        else:
+            event.Skip()
+
+class SettingsDialog(wx.Dialog):
+    def __init__(self, parent, id, title, scatt_mgr, pos=wx.DefaultPosition, size=wx.DefaultSize,
+                 style=wx.DEFAULT_DIALOG_STYLE):
+        """!Settings dialog"""
+        wx.Dialog.__init__(self, parent, id, title, pos, size, style)
+
+        self.scatt_mgr = scatt_mgr
+
+        maxValue = 1e8
+        self.parent = parent
+        self.settings = {}
+
+        settsLabels = {} 
+
+        self.settings["show_ellips"] = wx.CheckBox(parent = self, id=wx.ID_ANY,
+                                                   label = _('Show confidence ellipses'))
+        show_ellips = UserSettings.Get(group ='scatt', key = "ellipses", subkey = "show_ellips")
+        self.settings["show_ellips"].SetValue(show_ellips)
+
+
+        self.colorsSetts = {
+                            "sel_pol" : ["selection", _("Selection polygon color:")],
+                            "sel_pol_vertex" : ["selection", _("Color of selection polygon vertex:")], 
+                            "sel_area" : ["selection", _("Selected area color:")]
+                           }
+
+        for settKey, sett in self.colorsSetts.iteritems():
+            settsLabels[settKey] = wx.StaticText(parent = self, id = wx.ID_ANY, label = sett[1])
+            col = UserSettings.Get(group ='scatt', key = sett[0], subkey = settKey)
+            self.settings[settKey] = csel.ColourSelect(parent = self, id = wx.ID_ANY,
+                                            colour = wx.Colour(col[0],
+                                                               col[1],
+                                                               col[2], 
+                                                               255))
+
+        self.sizeSetts = {
+                          "snap_tresh" : ["selection", _("Snapping treshold in pixels:")],
+                          "sel_area_opacty" : ["selection", _("Selected area opacity:")]
+                         }
+
+        for settKey, sett in self.sizeSetts.iteritems():
+            settsLabels[settKey] = wx.StaticText(parent = self, id = wx.ID_ANY, label = sett[1])
+            self.settings[settKey] = wx.SpinCtrl(parent = self, id = wx.ID_ANY, min=0, max = 100)
+            size = int(UserSettings.Get(group = 'scatt', key = sett[0], subkey = settKey))
+            self.settings[settKey].SetValue(size)
+
+
+        # buttons
+        self.btnSave = wx.Button(self, wx.ID_SAVE)
+        self.btnApply = wx.Button(self, wx.ID_APPLY)
+        self.btnClose = wx.Button(self, wx.ID_CLOSE)
+        self.btnApply.SetDefault()
+
+        # bindings
+        self.btnApply.Bind(wx.EVT_BUTTON, self.OnApply)
+        self.btnApply.SetToolTipString(_("Apply changes for the current session"))
+        self.btnSave.Bind(wx.EVT_BUTTON, self.OnSave)
+        self.btnSave.SetToolTipString(_("Apply and save changes to user settings file (default for next sessions)"))
+        self.btnClose.Bind(wx.EVT_BUTTON, self.OnClose)
+        self.btnClose.SetToolTipString(_("Close dialog"))
+
+        #Layout
+
+        # Analysis result style layout
+        self.SetMinSize(self.GetBestSize())
+
+        sizer = wx.BoxSizer(wx.VERTICAL)
+
+        sel_pol_box = wx.StaticBox(parent = self, id = wx.ID_ANY,
+                                   label =" %s " % _("Selection style:"))
+        selPolBoxSizer = wx.StaticBoxSizer(sel_pol_box, wx.VERTICAL)
+
+        gridSizer = wx.GridBagSizer(vgap = 1, hgap = 1)
+
+        row = 0
+        setts = dict(self.colorsSetts.items() + self.sizeSetts.items())
+
+        settsOrder = ["sel_pol", "sel_pol_vertex", "sel_area",
+                      "sel_area_opacty", "snap_tresh"]
+        for settKey in settsOrder:
+            sett = setts[settKey]
+            gridSizer.Add(item = settsLabels[settKey], flag = wx.ALIGN_CENTER_VERTICAL, pos =(row, 0))
+            gridSizer.Add(item = self.settings[settKey],
+                          flag = wx.ALIGN_RIGHT | wx.ALL, border = 5,
+                          pos =(row, 1))  
+            row += 1
+
+        gridSizer.AddGrowableCol(1)
+        selPolBoxSizer.Add(item = gridSizer, flag = wx.EXPAND)
+
+        ell_box = wx.StaticBox(parent = self, id = wx.ID_ANY,
+                               label =" %s " % _("Ellipses settings:"))
+        ellPolBoxSizer = wx.StaticBoxSizer(ell_box, wx.VERTICAL)
+        gridSizer = wx.GridBagSizer(vgap = 1, hgap = 1)
+
+        sett = setts[settKey]
+
+        row = 0
+        gridSizer.Add(item=self.settings["show_ellips"], 
+                      flag=wx.ALIGN_CENTER_VERTICAL, 
+                      pos=(row, 0))
+
+        gridSizer.AddGrowableCol(1)
+        ellPolBoxSizer.Add(item=gridSizer, flag=wx.EXPAND)
+
+        btnSizer = wx.BoxSizer(wx.HORIZONTAL)
+        btnSizer.Add(self.btnApply, flag=wx.LEFT | wx.RIGHT, border=5)
+        btnSizer.Add(self.btnSave, flag=wx.LEFT | wx.RIGHT, border=5)
+        btnSizer.Add(self.btnClose, flag=wx.LEFT | wx.RIGHT, border=5)
+
+        sizer.Add(item=selPolBoxSizer, flag=wx.EXPAND | wx.ALL, border=5)
+        sizer.Add(item=ellPolBoxSizer, flag=wx.EXPAND | wx.ALL, border=5)
+        sizer.Add(item=btnSizer, flag=wx.EXPAND | wx.ALL, border=5, proportion=0)    
+
+        self.SetSizer(sizer)
+        sizer.Fit(self)
+     
+    def OnSave(self, event):
+        """!Button 'Save' pressed"""
+        self.UpdateSettings()
+
+        fileSettings = {}
+        UserSettings.ReadSettingsFile(settings=fileSettings)
+        fileSettings['scatt'] = UserSettings.Get(group='scatt')
+        UserSettings.SaveToFile(fileSettings)
+
+        self.Close()
+
+    def UpdateSettings(self):
+
+        chanaged_setts = [];
+        for settKey, sett in self.colorsSetts.iteritems():
+            col = tuple(self.settings[settKey].GetColour())
+            col_s = UserSettings.Get(group='scatt', key=sett[0], subkey=settKey)
+            if col_s != col:
+                UserSettings.Set(group='scatt', 
+                                 key=sett[0], 
+                                 subkey=settKey,
+                                 value=col)
+                chanaged_setts.append([settKey, sett[0]])
+
+        for settKey, sett in self.sizeSetts.iteritems():
+            val = self.settings[settKey].GetValue()
+            val_s = UserSettings.Get(group ='scatt', key = sett[0], subkey = settKey)
+
+            if val_s != val:
+                UserSettings.Set(group = 'scatt', key = sett[0], subkey = settKey, 
+                                 value = val)
+                chanaged_setts.append([settKey, sett[0]])
+
+        val = self.settings['show_ellips'].IsChecked()
+        val_s = UserSettings.Get(group ='scatt', key='ellipses', subkey='show_ellips')
+
+        if val != val_s:
+            UserSettings.Set(group='scatt', key='ellipses', subkey='show_ellips', 
+                             value=val)
+            chanaged_setts.append(['ellipses', 'show_ellips'])
+
+        if chanaged_setts: 
+            self.scatt_mgr.SettingsUpdated(chanaged_setts)
+
+    def OnApply(self, event):
+        """!Button 'Apply' pressed"""
+        self.UpdateSettings()
+        #self.Close()
+
+    def OnClose(self, event):
+        """!Button 'Cancel' pressed"""
+        self.Close()
+
+class ManageBusyCursorMixin:
+    def __init__(self, window):
+        self.win = window
+
+        self.is_busy = False
+        self.cur_inside = False
+
+        self.win.Bind(wx.EVT_ENTER_WINDOW, self.OnEnter)
+        self.win.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave)
+
+    def OnLeave(self, event):   
+        self.cur_inside = False     
+        self.busy_cur = None
+
+    def OnEnter(self, event):
+        self.cur_inside = True
+        if self.is_busy:
+            self.busy_cur = wx.BusyCursor()
+
+    def UpdateCur(self, busy):
+        self.is_busy = busy
+        if self.cur_inside and self.is_busy:
+            self.busy_cur = wx.BusyCursor()
+            return
+
+        self.busy_cur = None
+
+class RenameClassDialog(SimpleDialog):
+    def __init__(self, parent, old_name, title=("Change class name")):
+        SimpleDialog.__init__(self, parent, title)
+
+        self.name = wx.TextCtrl(self.panel, id = wx.ID_ANY) 
+        self.name.SetValue(old_name)
+
+        self.dataSizer.Add(self.name, proportion = 0,
+                           flag = wx.EXPAND | wx.ALL, border = 1)
+
+        self.panel.SetSizer(self.sizer)
+        self.name.SetMinSize((200, -1))
+        self.sizer.Fit(self)
+
+    def GetNewName(self):
+        return self.name.GetValue()


Property changes on: grass/trunk/gui/wxpython/iscatt/dialogs.py
___________________________________________________________________
Added: svn:mime-type
   + text/x-python
Added: svn:eol-style
   + native

Added: grass/trunk/gui/wxpython/iscatt/frame.py
===================================================================
--- grass/trunk/gui/wxpython/iscatt/frame.py	                        (rev 0)
+++ grass/trunk/gui/wxpython/iscatt/frame.py	2013-09-29 20:54:52 UTC (rev 57861)
@@ -0,0 +1,694 @@
+"""!
+ at package iscatt.frame
+
+ at brief Main scatter plot widgets.
+
+Classes:
+ - frame::IClassIScattPanel
+ - frame::IScattDialog
+ - frame::MapDispIScattPanel
+ - frame::ScatterPlotsPanel
+ - frame::CategoryListCtrl
+
+(C) 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.
+
+ at author Stepan Turek <stepan.turek seznam.cz> (mentor: Martin Landa)
+"""
+import os
+import sys
+
+import wx
+import wx.lib.scrolledpanel as scrolled
+import wx.lib.mixins.listctrl as listmix
+
+from core import globalvar
+from core.gcmd import GException, GError, RunCommand
+
+from gui_core.gselect import Select
+from gui_core.dialogs import SetOpacityDialog
+from iscatt.controllers import ScattsManager
+from iscatt.toolbars import MainToolbar, EditingToolbar, CategoryToolbar
+from iscatt.iscatt_core import idScattToidBands
+from iscatt.dialogs import ManageBusyCursorMixin, RenameClassDialog
+from iscatt.plots import ScatterPlotWidget
+from iclass.dialogs import ContrastColor
+
+try:
+    from agw import aui
+except ImportError:
+    import wx.lib.agw.aui as aui
+
+class IClassIScattPanel(wx.Panel, ManageBusyCursorMixin):
+    def __init__(self, parent, giface, iclass_mapwin = None,
+                 id = wx.ID_ANY):
+
+        #wx.SplitterWindow.__init__(self, parent = parent, id = id,
+        #                           style = wx.SP_LIVE_UPDATE)
+        wx.Panel.__init__(self, parent = parent, id = wx.ID_ANY)
+        ManageBusyCursorMixin.__init__(self, window=self)
+
+        self.scatt_mgr = self._createScattMgr(guiparent=parent, giface=giface, 
+                                              iclass_mapwin=iclass_mapwin)
+
+        # toobars
+        self.toolbars = {}
+        self.toolbars['mainToolbar'] = self._createMainToolbar()
+        self.toolbars['editingToolbar'] = EditingToolbar(parent = self, scatt_mgr = self.scatt_mgr)
+        
+        self._createCategoryPanel(self)
+
+        self.plot_panel = ScatterPlotsPanel(self, self.scatt_mgr)
+
+        self.mainsizer = wx.BoxSizer(wx.VERTICAL)
+        self.mainsizer.Add(item = self.toolbars['mainToolbar'], proportion = 0, flag = wx.EXPAND)
+        self.mainsizer.Add(item = self.toolbars['editingToolbar'], proportion = 0, flag = wx.EXPAND)
+        self.mainsizer.Add(item = self.catsPanel, proportion = 0, 
+                           flag = wx.EXPAND | wx.LEFT | wx.RIGHT , border = 5)
+        self.mainsizer.Add(item = self.plot_panel, proportion = 1, flag = wx.EXPAND)
+
+        self.catsPanel.Hide()
+        self.toolbars['editingToolbar'].Hide()
+
+        self.SetSizer(self.mainsizer)
+        
+        self.scatt_mgr.computingStarted.connect(lambda : self.UpdateCur(busy=True))
+        self.scatt_mgr.renderingStarted.connect(lambda : self.UpdateCur(busy=True))
+        self.scatt_mgr.renderingFinished.connect(lambda : self.UpdateCur(busy=False))
+
+        #self.SetSashGravity(0.5)
+        #self.SplitHorizontally(self.head_panel, self.plot_panel, -50)
+        self.Layout()
+
+    def CloseWindow(self):
+        self.scatt_mgr.CleanUp()
+
+    def UpdateCur(self, busy):
+        self.plot_panel.SetBusy(busy)
+        ManageBusyCursorMixin.UpdateCur(self, busy)
+
+    def _selCatInIScatt(self):
+         return False
+
+    def _createMainToolbar(self):
+         return MainToolbar(parent = self, scatt_mgr = self.scatt_mgr)
+
+    def _createScattMgr(self, guiparent, giface, iclass_mapwin):
+        return ScattsManager(guiparent=self, giface=giface, iclass_mapwin=iclass_mapwin)
+
+
+    def NewScatterPlot(self, scatt_id, transpose):
+        return self.plot_panel.NewScatterPlot(scatt_id, transpose)
+
+    def ShowPlotEditingToolbar(self, show):
+        self.toolbars["editingToolbar"].Show(show)
+        self.Layout()
+
+    def ShowCategoryPanel(self, show):
+        self.catsPanel.Show(show)
+        
+        #if show:
+        #    self.SetSashSize(5) 
+        #else:
+        #    self.SetSashSize(0)
+        self.plot_panel.SetVirtualSize(self.plot_panel.GetBestVirtualSize())
+        self.Layout()
+
+    def _createCategoryPanel(self, parent):
+        self.catsPanel = wx.Panel(parent=parent)
+        self.cats_list = CategoryListCtrl(parent=self.catsPanel, 
+                                          cats_mgr=self.scatt_mgr.GetCategoriesManager(),
+                                          sel_cats_in_iscatt=self._selCatInIScatt())
+
+        self.catsPanel.SetMinSize((-1, 100))
+        self.catsPanel.SetInitialSize((-1, 150))
+
+        box_capt = wx.StaticBox(parent=self.catsPanel, id=wx.ID_ANY,
+                             label=' %s ' % _("Classes"),)
+        catsSizer = wx.StaticBoxSizer(box_capt, wx.VERTICAL)
+
+        self.toolbars['categoryToolbar'] = self._createCategoryToolbar(self.catsPanel)
+
+        catsSizer.Add(item=self.cats_list, proportion=1,  flag=wx.EXPAND | wx.TOP, border = 5)
+        if self.toolbars['categoryToolbar']:
+            catsSizer.Add(item=self.toolbars['categoryToolbar'], proportion=0)
+
+        self.catsPanel.SetSizer(catsSizer)
+    
+    def _createCategoryToolbar(self, parent):
+        return CategoryToolbar(parent=parent,
+                               scatt_mgr=self.scatt_mgr, 
+                               cats_list=self.cats_list)
+
+class IScattDialog(wx.Dialog):
+    def __init__(self, parent, giface, title=_("GRASS GIS Interactive Scatter Plot Tool"),
+                 id=wx.ID_ANY, style=wx.DEFAULT_FRAME_STYLE, **kwargs):
+        wx.Dialog.__init__(self, parent, id, style=style, title = title, **kwargs)
+        self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
+
+        self.iscatt_panel = MapDispIScattPanel(self, giface)
+
+        mainsizer = wx.BoxSizer(wx.VERTICAL)
+        mainsizer.Add(item=self.iscatt_panel, proportion=1, flag=wx.EXPAND)
+
+        self.SetSizer(mainsizer)
+
+        self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
+
+        self.SetMinSize((300, 300))
+
+    def OnCloseWindow(self, event):
+        event.Skip()
+        #self.
+
+class MapDispIScattPanel(IClassIScattPanel):
+    def __init__(self, parent, giface,
+                 id=wx.ID_ANY, **kwargs):
+        IClassIScattPanel.__init__(self, parent=parent, giface=giface,
+                                         id=id, **kwargs)
+
+    def _createScattMgr(self, guiparent, giface, iclass_mapwin):
+        return ScattsManager(guiparent = self, giface = giface)
+
+    def _createMainToolbar(self):
+         return MainToolbar(parent = self, scatt_mgr = self.scatt_mgr, opt_tools=['add_group'])
+
+    def _selCatInIScatt(self):
+         return True
+
+class ScatterPlotsPanel(scrolled.ScrolledPanel):
+    def __init__(self, parent, scatt_mgr, id=wx.ID_ANY):
+    
+        scrolled.ScrolledPanel.__init__(self, parent)
+        self.SetupScrolling(scroll_x=False, scroll_y=True, scrollToTop=False)
+
+        self.scatt_mgr = scatt_mgr
+
+        self.mainPanel = wx.Panel(parent=self, id=wx.ID_ANY)
+
+        #self._createCategoryPanel()
+        # Fancy gui
+        self._mgr = aui.AuiManager(self.mainPanel)
+        #self._mgr.SetManagedWindow(self)
+
+        self._mgr.Update()
+
+        self._doLayout()
+        self.Bind(wx.EVT_SCROLLWIN, self.OnScroll)
+        self.Bind(wx.EVT_SCROLL_CHANGED, self.OnScrollChanged)
+
+        self.Bind(aui.EVT_AUI_PANE_CLOSE, self.OnPlotPaneClosed)
+
+        dlgSize = (-1, 400)
+        #self.SetBestSize(dlgSize)
+        #self.SetInitialSize(dlgSize)
+        self.SetAutoLayout(1)
+        #fix goutput's pane size (required for Mac OSX)
+        #if self.gwindow:         
+        #    self.gwindow.SetSashPosition(int(self.GetSize()[1] * .75))
+        self.ignore_scroll = 0
+        self.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel)
+
+        self.scatt_i = 1
+        self.scatt_id_scatt_i = {}
+        self.transpose = {}
+        self.scatts = {}
+
+        self.Bind(wx.EVT_CLOSE, self.OnClose)
+        
+        self.scatt_mgr.cursorPlotMove.connect(self.CursorPlotMove)
+
+    def SetBusy(self, busy):
+        for scatt in self.scatts.itervalues():
+            scatt.UpdateCur(busy)
+
+    def CursorPlotMove(self, x, y, scatt_id):
+
+        try:
+            x = int(round(x))
+            y = int(round(y))
+            coords = True
+        except:
+            coords = False
+
+        pane = self._getPane(scatt_id)
+        caption = self._creteCaption(scatt_id)
+        if coords:
+            caption += "   %d, %d" % (x, y)
+
+        pane.Caption(caption)
+        self._mgr.RefreshCaptions()
+
+    def _getPane(self, scatt_id):
+        scatt_i = self.scatt_id_scatt_i[scatt_id]
+        name = self._getScatterPlotName(scatt_i)
+        return self._mgr.GetPane(name)
+
+    def ScatterPlotClosed(self, scatt_id):
+
+        scatt_i = self.scatt_id_scatt_i[scatt_id]
+
+        name = self._getScatterPlotName(scatt_i)
+        pane = self._mgr.GetPane(name)
+
+        del self.scatt_id_scatt_i[scatt_id]
+        del self.scatts[scatt_id]
+
+        if pane.IsOk(): 
+          self._mgr.ClosePane(pane) 
+        self._mgr.Update() 
+
+    def OnMouseWheel(self, event):
+        #TODO very ugly find some better solution        
+        self.ignore_scroll = 3
+        event.Skip()
+
+    def ScrollChildIntoView(self, child):
+        #For aui manager it does not work and returns position always to the top -> deactivated.
+        pass
+
+    def OnPlotPaneClosed(self, event):
+        if isinstance(event.pane.window, ScatterPlotWidget):
+            event.pane.window.CleanUp()
+
+    def OnScrollChanged(self, event):
+        wx.CallAfter(self.Layout)
+
+    def OnScroll(self, event):
+        if self.ignore_scroll > 0:
+            self.ignore_scroll -= 1
+        else:
+            event.Skip()
+
+        #wx.CallAfter(self._mgr.Update)
+        #wx.CallAfter(self.Layout)
+
+    def _doLayout(self):
+
+        mainsizer = wx.BoxSizer(wx.VERTICAL)
+        mainsizer.Add(item = self.mainPanel, proportion = 1, flag = wx.EXPAND)
+        self.SetSizer(mainsizer)
+
+        self.Layout()
+        self.SetupScrolling()
+
+    def OnClose(self, event):
+        """!Close dialog"""
+        #TODO
+        print "closed"
+        self.scatt_mgr.CleanUp()
+        self.Destroy()
+
+    def OnSettings(self, event):
+        pass
+
+    def _newScatterPlotName(self, scatt_id):
+        name = self._getScatterPlotName(self.scatt_i) 
+        self.scatt_id_scatt_i[scatt_id] = self.scatt_i
+        self.scatt_i += 1
+        return name
+
+    def _getScatterPlotName(self, i):
+        name = "scatter plot %d" % i
+        return name
+
+    def NewScatterPlot(self, scatt_id, transpose):
+        #TODO needs to be resolved (should be in this class)
+
+        scatt = ScatterPlotWidget(parent = self.mainPanel, 
+                                  scatt_mgr = self.scatt_mgr, 
+                                  scatt_id = scatt_id, 
+                                  transpose = transpose)
+        scatt.plotClosed.connect(self.ScatterPlotClosed)
+        self.transpose[scatt_id] = transpose
+        
+        caption = self._creteCaption(scatt_id)
+        self._mgr.AddPane(scatt,
+                           aui.AuiPaneInfo().Dockable(True).Floatable(True).
+                           Name(self._newScatterPlotName(scatt_id)).MinSize((-1, 300)).
+                           Caption(caption).
+                           Center().Position(1).MaximizeButton(True).
+                           MinimizeButton(True).CaptionVisible(True).
+                           CloseButton(True).Layer(0))
+
+        self._mgr.Update()
+  
+        self.SetVirtualSize(self.GetBestVirtualSize())
+        self.Layout()
+
+        self.scatts[scatt_id] = scatt
+
+        return scatt
+
+    def _creteCaption(self, scatt_id):
+
+        transpose = self.transpose[scatt_id]
+        bands = self.scatt_mgr.GetBands()
+
+        #TODO too low level
+        b1_id, b2_id = idScattToidBands(scatt_id, len(bands))
+
+        x_b =  bands[b1_id].split('@')[0]
+        y_b = bands[b2_id].split('@')[0]
+
+        if transpose:
+            tmp = x_b
+            x_b = y_b
+            y_b = tmp
+
+        return "%s x: %s y: %s" % (_("scatter plot"), x_b, y_b)
+
+    def GetScattMgr(self):
+        return  self.scatt_mgr
+
+class CategoryListCtrl(wx.ListCtrl,
+                       listmix.ListCtrlAutoWidthMixin):
+                       #listmix.TextEditMixin):
+
+    def __init__(self, parent, cats_mgr, sel_cats_in_iscatt, id = wx.ID_ANY):
+
+        wx.ListCtrl.__init__(self, parent, id,
+                             style = wx.LC_REPORT|wx.LC_VIRTUAL|wx.LC_HRULES|
+                                     wx.LC_VRULES|wx.LC_SINGLE_SEL|wx.LC_NO_HEADER)
+        self.columns = ((_('Class name'), 'name'), )
+                        #(_('Color'), 'color'))
+
+        self.sel_cats_in_iscatt = sel_cats_in_iscatt
+
+        self.Populate(columns = self.columns)
+        
+        self.cats_mgr = cats_mgr
+        self.SetItemCount(len(self.cats_mgr.GetCategories()))
+
+        self.rightClickedItemIdx = wx.NOT_FOUND
+        
+        listmix.ListCtrlAutoWidthMixin.__init__(self)
+
+        #listmix.TextEditMixin.__init__(self)
+      
+        self.Bind(wx.EVT_COMMAND_RIGHT_CLICK, self.OnCategoryRightUp) #wxMSW
+        self.Bind(wx.EVT_RIGHT_UP,            self.OnCategoryRightUp) #wxGTK
+
+        #self.Bind(wx.EVT_LIST_BEGIN_LABEL_EDIT, self.OnEdit)
+        self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnSel)
+             
+        self.cats_mgr.setCategoryAttrs.connect(self.Update)
+        self.cats_mgr.deletedCategory.connect(self.Update)
+        self.cats_mgr.addedCategory.connect(self.Update)
+
+    def Update(self, **kwargs):
+        self.SetItemCount(len(self.cats_mgr.GetCategories()))
+        self.RefreshItems(0, len(self.cats_mgr.GetCategories()))
+
+    def InitCoreCats(self):
+        self.SetItemCount(len(self.cats_mgr.GetCategories()))
+        self.RefreshItems(0, len(self.cats_mgr.GetCategories()))
+
+    def SetVirtualData(self, row, column, text):
+        attr = self.columns[column][1]
+        if attr == 'name':
+            try:
+                text.encode('ascii')
+            except UnicodeEncodeError:
+                GMessage(parent = self, message = _("Please use only ASCII characters."))
+                return
+
+        cat_id = self.cats_mgr.GetCategories()[row]
+
+        self.cats_mgr.setCategoryAttrs.disconnect(self.Update)
+        self.cats_mgr.SetCategoryAttrs(cat_id, {attr : text})
+        self.cats_mgr.setCategoryAttrs.connect(self.Update)
+        
+        self.Select(row)
+        
+    def Populate(self, columns):
+        for i, col in enumerate(columns):
+            self.InsertColumn(i, col[0])#wx.LIST_FORMAT_RIGHT
+
+        #self.SetColumnWidth(0, 100)
+        #self.SetColumnWidth(1, 100)
+        
+    def AddCategory(self):
+
+        self.cats_mgr.addedCategory.disconnect(self.Update)
+        cat_id = self.cats_mgr.AddCategory()
+        self.cats_mgr.addedCategory.connect(self.Update)
+
+        if cat_id < 0:
+            GError(_("Maximum limit of categories number was reached."))
+            return
+        self.SetItemCount(len(self.cats_mgr.GetCategories()))
+                        
+    def DeleteCategory(self):
+        indexList = sorted(self.GetSelectedIndices(), reverse = True)
+        cats = []
+        for i in indexList:
+            # remove temporary raster
+            cat_id = self.cats_mgr.GetCategories()[i]
+            
+            cats.append(cat_id)
+
+            self.cats_mgr.deletedCategory.disconnect(self.Update)
+            self.cats_mgr.DeleteCategory(cat_id)
+            self.cats_mgr.deletedCategory.connect(self.Update)
+            
+        self.SetItemCount(len(self.cats_mgr.GetCategories()))
+        
+    def OnSel(self, event):
+        if self.sel_cats_in_iscatt:
+            indexList = self.GetSelectedIndices()
+            sel_cats = []
+            cats = self.cats_mgr.GetCategories()
+            for i in indexList:
+                sel_cats.append(cats[i])       
+
+            if sel_cats:
+                self.cats_mgr.SetSelectedCat(sel_cats[0])
+        event.Skip()
+
+    def GetSelectedIndices(self, state =  wx.LIST_STATE_SELECTED):
+        indices = []
+        lastFound = -1
+        while True:
+            index = self.GetNextItem(lastFound, wx.LIST_NEXT_ALL, state)
+            if index == -1:
+                break
+            else:
+                lastFound = index
+                indices.append(index)
+        return indices        
+
+    def DeselectAll(self):
+        """!Deselect all items"""
+        indexList = self.GetSelectedIndices()
+        for i in indexList:
+            self.Select(i, on = 0)
+         
+        # no highlight
+        self.OnCategorySelected(None)
+        
+    def OnGetItemText(self, item, col):
+        attr = self.columns[col][1]
+        cat_id = self.cats_mgr.GetCategories()[item]
+
+        return self.cats_mgr.GetCategoryAttrs(cat_id)[attr]
+
+    def OnGetItemImage(self, item):
+        return -1
+
+    def OnGetItemAttr(self, item):
+        cat_id = self.cats_mgr.GetCategories()[item]
+
+        cattr = self.cats_mgr.GetCategoryAttrs(cat_id)
+        
+        if cattr['show']:
+            c = cattr['color']
+            
+            back_c = wx.Colour(*map(int, c.split(':')))
+            text_c = wx.Colour(*ContrastColor(back_c))
+        else:
+            back_c = wx.SystemSettings.GetColour(wx.SYS_COLOUR_INACTIVECAPTION)
+            text_c = wx.SystemSettings.GetColour(wx.SYS_COLOUR_INACTIVECAPTIONTEXT)
+
+        # if it is in scope of the method, gui falls, using self solved it 
+        self.l = wx.ListItemAttr(colText=text_c, colBack=back_c)
+        return self.l
+
+    def OnCategoryRightUp(self, event):
+        """!Show context menu on right click"""
+        item, flags = self.HitTest((event.GetX(), event.GetY()))
+        if item != wx.NOT_FOUND and flags & wx.LIST_HITTEST_ONITEM:
+            self.rightClickedItemIdx = item
+
+        # generate popup-menu
+        cat_idx = self.rightClickedItemIdx
+
+        cats = self.cats_mgr.GetCategories()
+        cat_id = cats[cat_idx]
+        showed = self.cats_mgr.GetCategoryAttrs(cat_id)['show']
+        
+        menu = wx.Menu()
+
+        item_id = wx.NewId()
+        menu.Append(item_id, text=_("Rename class"))
+        self.Bind(wx.EVT_MENU, self.OnRename, id=item_id)
+
+        item_id = wx.NewId()
+        menu.Append(item_id, text=_("Set color"))
+        self.Bind(wx.EVT_MENU, self.OnSetColor, id=item_id)
+
+        item_id = wx.NewId()
+        menu.Append(item_id, text=_("Change opacity level"))
+        self.Bind(wx.EVT_MENU, self.OnPopupOpacityLevel, id=item_id)
+
+        if showed:
+            text = _("Hide")
+        else:
+            text = _("Show")
+
+        item_id = wx.NewId()
+        menu.Append(item_id, text = text)
+        self.Bind(wx.EVT_MENU, lambda event : self._setCatAttrs(cat_id=cat_id,
+                                                                attrs={'show' : not showed}), 
+                                                                id=item_id) 
+        
+        menu.AppendSeparator()
+        
+        item_id = wx.NewId()
+        menu.Append(item_id, text=_("Move to top"))
+        self.Bind(wx.EVT_MENU, self.OnMoveTop, id=item_id)
+        if cat_idx == 0:
+            menu.Enable(item_id, False)
+
+        item_id = wx.NewId()
+        menu.Append(item_id, text=_("Move to bottom"))
+        self.Bind(wx.EVT_MENU, self.OnMoveBottom, id=item_id)
+        if cat_idx == len(cats) - 1:
+            menu.Enable(item_id, False)
+
+        menu.AppendSeparator()
+
+        item_id = wx.NewId()
+        menu.Append(item_id, text=_("Move category up"))
+        self.Bind(wx.EVT_MENU, self.OnMoveUp, id=item_id)
+        if cat_idx == 0:
+            menu.Enable(item_id, False)
+
+        item_id = wx.NewId()
+        menu.Append(item_id, text=_("Move category down"))
+        self.Bind(wx.EVT_MENU, self.OnMoveDown, id=item_id)
+        if cat_idx == len(cats) - 1:
+            menu.Enable(item_id, False)
+
+        menu.AppendSeparator()
+
+        item_id = wx.NewId()
+        menu.Append(item_id, text=_("Export class raster"))
+        self.Bind(wx.EVT_MENU, self.OnExportCatRast, id=item_id)
+
+        self.PopupMenu(menu)
+        menu.Destroy()
+
+    def OnExportCatRast(self, event):
+        """!Export training areas"""
+        #TODO
+        #   GMessage(parent=self, message=_("No class raster to export."))
+        #    return
+
+        cat_idx = self.rightClickedItemIdx
+        cat_id = self.cats_mgr.GetCategories()[cat_idx]
+
+        self.cats_mgr.ExportCatRast(cat_id)
+
+    def OnMoveUp(self, event):
+        cat_idx = self.rightClickedItemIdx
+        cat_id = self.cats_mgr.GetCategories()[cat_idx]
+        self.cats_mgr.ChangePosition(cat_id, cat_idx - 1)
+        self.RefreshItems(0, len(self.cats_mgr.GetCategories()))
+
+    def OnMoveDown(self, event):
+        cat_idx = self.rightClickedItemIdx
+        cat_id = self.cats_mgr.GetCategories()[cat_idx]
+        self.cats_mgr.ChangePosition(cat_id, cat_idx + 1)
+        self.RefreshItems(0, len(self.cats_mgr.GetCategories()))
+
+    def OnMoveTop(self, event):
+        cat_idx = self.rightClickedItemIdx
+        cat_id = self.cats_mgr.GetCategories()[cat_idx]
+        self.cats_mgr.ChangePosition(cat_id, 0)
+        self.RefreshItems(0, len(self.cats_mgr.GetCategories()))
+
+    def OnMoveBottom(self, event):
+        cat_idx = self.rightClickedItemIdx
+        cats = self.cats_mgr.GetCategories()
+        cat_id = cats[cat_idx]
+        self.cats_mgr.ChangePosition(cat_id, len(cats) - 1)
+        self.RefreshItems(0, len(self.cats_mgr.GetCategories()))
+
+    def OnSetColor(self, event):
+        """!Popup opacity level indicator"""
+        cat_idx = self.rightClickedItemIdx
+        cat_id = self.cats_mgr.GetCategories()[cat_idx]
+
+        col = self.cats_mgr.GetCategoryAttrs(cat_id)['color']
+        col = map(int, col.split(':'))
+
+        col_data =  wx.ColourData()
+        col_data.SetColour(wx.Colour(*col))
+
+        dlg = wx.ColourDialog(self, col_data)
+        dlg.GetColourData().SetChooseFull(True)
+
+        if dlg.ShowModal() == wx.ID_OK:
+            color = dlg.GetColourData().GetColour().Get()
+            color = ':'.join(map(str, color))
+            self.cats_mgr.SetCategoryAttrs(cat_id, {"color" : color})
+
+        dlg.Destroy()
+
+    def OnPopupOpacityLevel(self, event):
+        """!Popup opacity level indicator"""
+
+        cat_id = self.cats_mgr.GetCategories()[self.rightClickedItemIdx]
+        cat_attrs = self.cats_mgr.GetCategoryAttrs(cat_id)
+        value = cat_attrs['opacity'] * 100
+        name = cat_attrs['name']
+
+        dlg = SetOpacityDialog(self, opacity = value,
+                               title = _("Change opacity of class <%s>" % name))
+
+        dlg.applyOpacity.connect(lambda value:
+                                 self._setCatAttrs(cat_id=cat_id, attrs={'opacity' : value}))
+        dlg.CentreOnParent()
+
+        if dlg.ShowModal() == wx.ID_OK:
+            self._setCatAttrs(cat_id=cat_id, attrs={'opacity' : value})
+        
+        dlg.Destroy()
+
+    def OnRename(self, event):
+        cat_id = self.cats_mgr.GetCategories()[self.rightClickedItemIdx]
+        cat_attrs = self.cats_mgr.GetCategoryAttrs(cat_id)
+
+        dlg = RenameClassDialog(self, old_name=cat_attrs['name'])
+        dlg.CentreOnParent()
+
+        while True:
+            if dlg.ShowModal() == wx.ID_OK:
+                name = dlg.GetNewName().strip()
+                if not name:
+                    GMessage(parent=self, message=_("Empty name was inserted."))
+                else:
+                    self.cats_mgr.SetCategoryAttrs(cat_id, {"name" : name})
+                    break
+            else:
+                break
+
+        dlg.Destroy()
+
+    def _setCatAttrs(self, cat_id, attrs):
+        self.cats_mgr.setCategoryAttrs.disconnect(self.Update)
+        self.cats_mgr.SetCategoryAttrs(cat_id, attrs)
+        self.cats_mgr.setCategoryAttrs.connect(self.Update)


Property changes on: grass/trunk/gui/wxpython/iscatt/frame.py
___________________________________________________________________
Added: svn:mime-type
   + text/x-python
Added: svn:eol-style
   + native

Added: grass/trunk/gui/wxpython/iscatt/iscatt_core.py
===================================================================
--- grass/trunk/gui/wxpython/iscatt/iscatt_core.py	                        (rev 0)
+++ grass/trunk/gui/wxpython/iscatt/iscatt_core.py	2013-09-29 20:54:52 UTC (rev 57861)
@@ -0,0 +1,837 @@
+"""!
+ at package iscatt.iscatt_core
+
+ at brief Non GUI functions.
+
+Classes:
+ - iscatt_core::Core
+ - iscatt_core::CatRastUpdater
+ - iscatt_core::AnalyzedData
+ - iscatt_core::ScattPlotsCondsData
+ - iscatt_core::ScattPlotsData
+
+(C) 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.
+
+ at author Stepan Turek <stepan.turek seznam.cz> (mentor: Martin Landa)
+"""
+import os
+import sys
+
+import time
+
+import numpy as np
+
+# used iclass perimeters algorithm instead of convolve2d
+#from matplotlib.path import Path 
+#from scipy.signal import convolve2d
+
+from math import sqrt, ceil, floor
+from copy import deepcopy
+
+from core.gcmd import GException, GError, RunCommand
+
+import grass.script as grass
+
+from core_c import CreateCatRast, ComputeScatts, UpdateCatRast, \
+                   Rasterize, SC_SCATT_DATA, SC_SCATT_CONDITIONS
+
+class Core:
+    """!Represents scatter plot backend.
+    """
+    def __init__(self):
+        
+        self.an_data = AnalyzedData()
+
+        self.scatts_dt = ScattPlotsData(self.an_data)
+        self.scatt_conds_dt = ScattPlotsCondsData(self.an_data)
+
+        self.cat_rast_updater = CatRastUpdater(self.scatts_dt, self.an_data, self)
+
+    def SetData(self, bands):
+        """Set bands for analysis.
+        """
+        self.an_data.Create(bands)
+
+        n_bands = len(self.GetBands())
+
+        self.scatts_dt.Create(n_bands)
+        self.scatt_conds_dt.Create(n_bands)
+
+    def AddCategory(self, cat_id):
+        self.scatts_dt.AddCategory(cat_id)
+        return self.scatt_conds_dt.AddCategory(cat_id)
+
+    def DeleteCategory(self, cat_id):
+        self.scatts_dt.DeleteCategory(cat_id)
+        self.scatt_conds_dt.DeleteCategory(cat_id)
+
+    def CleanUp(self):
+        self.scatts_dt.CleanUp()
+        self.scatt_conds_dt.CleanUp()
+
+    def GetBands(self):
+        return self.an_data.GetBands()
+
+    def GetScattsData(self):
+        return self.scatts_dt, self.scatt_conds_dt;
+
+    def GetRegion(self):
+        return self.an_data.GetRegion()
+
+    def GetCatRast(self, cat_id):
+        return self.scatts_dt.GetCatRast(cat_id)
+
+    def AddScattPlots(self, scatt_ids):
+    
+        for s_id in scatt_ids:
+            self.scatts_dt.AddScattPlot(scatt_id = s_id)
+
+        cats_ids = self.scatts_dt.GetCategories()
+        self.ComputeCatsScatts(cats_ids)
+
+    def SetEditCatData(self, cat_id, scatt_id, bbox, value):
+
+        if cat_id not in self.scatts_dt.GetCategories():
+            raise GException(_("Select category for editing."))
+
+        if self.scatt_conds_dt.AddScattPlot(cat_id, scatt_id) < 0:
+            return None
+
+        arr = self.scatt_conds_dt.GetValuesArr(cat_id, scatt_id)
+
+        for k, v in bbox.iteritems():
+            bbox[k] = self._validExtend(v)
+
+        arr[bbox['btm_y'] : bbox['up_y'], bbox['btm_x'] : bbox['up_x']] = value
+
+        self.ComputeCatsScatts([cat_id])
+        return cat_id
+
+    def ComputeCatsScatts(self, cats_ids):
+
+        requested_dt = {}
+        requested_dt_conds = {}
+
+        for c in cats_ids:
+            requested_dt_conds[c] = self.scatt_conds_dt.GetCatScatts(c)
+            requested_dt[c] = self.scatts_dt.GetCatScatts(c)
+
+        scatt_conds = self.scatt_conds_dt.GetData(requested_dt_conds)
+        scatts = self.scatts_dt.GetData(requested_dt)
+
+        bands = self.an_data.GetBands()
+
+        cats_rasts = self.scatts_dt.GetCatsRasts()
+        cats_rasts_conds = self.scatts_dt.GetCatsRastsConds()
+
+        returncode, scatts = ComputeScatts(self.an_data.GetRegion(), 
+                                           scatt_conds, 
+                                           bands,
+                                           len(self.GetBands()), 
+                                           scatts, 
+                                           cats_rasts_conds, 
+                                           cats_rasts)
+
+        if returncode < 0:
+            GException(_("Computing of scatter plots failed."))
+
+    def CatRastUpdater(self):
+        return self.cat_rast_updater
+    
+    def UpdateCategoryWithPolygons(self, cat_id, scatts_pols, value):
+        start_time = time.clock()
+
+        if cat_id not in self.scatts_dt.GetCategories():
+            raise GException(_("Select category for editing."))
+
+        for scatt_id, coords in scatts_pols.iteritems():
+
+            if self.scatt_conds_dt.AddScattPlot(cat_id, scatt_id) < 0:
+                return False
+
+            b1, b2 = idScattToidBands(scatt_id, len(self.an_data.GetBands()))    
+            b = self.scatts_dt.GetBandsInfo(scatt_id)
+
+            region = {}
+            region['s'] = b['b2']['min'] - 0.5
+            region['n'] = b['b2']['max'] + 0.5
+
+            region['w'] = b['b1']['min'] - 0.5
+            region['e'] = b['b1']['max'] + 0.5
+
+            arr = self.scatt_conds_dt.GetValuesArr(cat_id, scatt_id)
+            arr = Rasterize(polygon=coords, 
+                            rast=arr, 
+                            region=region, 
+                            value=value)
+
+            # previous way of rasterization / used scipy
+            #raster_pol = RasterizePolygon(coords, b['b1']['range'], b['b1']['min'], 
+            #                                      b['b2']['range'], b['b2']['min'])
+
+            #raster_ind = np.where(raster_pol > 0) 
+            #arr = self.scatt_conds_dt.GetValuesArr(cat_id, scatt_id)
+
+            #arr[raster_ind] = value
+            #arr.flush()
+        
+        self.ComputeCatsScatts([cat_id])
+        return cat_id
+
+    def ExportCatRast(self, cat_id, rast_name):
+
+        cat_rast = self.scatts_dt.GetCatRast(cat_id);
+        if not cat_rast:
+            return 1
+        
+        return RunCommand("g.copy", 
+                          rast=cat_rast + ',' + rast_name,
+                          getErrorMsg=True,
+                          overwrite=True)
+
+    def _validExtend(self, val):
+        #TODO do it general
+        if  val > 255:
+            val = 255
+        elif val < 0:
+            val = 0
+
+        return val
+
+class CatRastUpdater:
+    """!Update backend data structures according to selected areas in mapwindow.
+    """
+    def __init__(self, scatts_dt, an_data, core):
+        self.scatts_dt = scatts_dt
+        self.an_data = an_data # TODO may be confusing
+        self.core = core
+        self.vectMap = None
+
+    def SetVectMap(self, vectMap):
+        self.vectMap = vectMap
+
+    def SyncWithMap(self):
+        #TODO possible optimization - bbox only of vertex and its two neighbours
+
+        region = self.an_data.GetRegion()
+
+        bbox = {}
+        bbox['maxx'] = region['e']
+        bbox['minx'] = region['w']
+        bbox['maxy'] = region['n']
+        bbox['miny'] = region['s']
+
+        updated_cats = []
+
+        for cat_id in self.scatts_dt.GetCategories():
+            if cat_id == 0:
+                continue
+            
+            cat = [{1 : [cat_id]}]
+            self._updateCatRast(bbox, cat, updated_cats)
+
+        return updated_cats
+
+    def EditedFeature(self, new_bboxs, new_areas_cats, old_bboxs, old_areas_cats):
+        #TODO possible optimization - bbox only of vertex and its two neighbours
+
+        bboxs = old_bboxs + new_bboxs
+        areas_cats = old_areas_cats + new_areas_cats 
+
+        updated_cats = []
+
+        for i in range(len(areas_cats)):
+            self._updateCatRast(bboxs[i], areas_cats[i], updated_cats)
+        
+        return updated_cats
+
+    def _updateCatRast(self, bbox, areas_cats, updated_cats):
+
+        rasterized_cats = []
+        for c in range(len(areas_cats)):
+
+            if not areas_cats[c]:
+                continue
+
+            layer = areas_cats[c].keys()[0]
+            cat =  areas_cats[c][layer][0]
+
+            if cat in rasterized_cats:
+                continue
+
+            rasterized_cats.append(cat)
+            updated_cats.append(cat)
+
+            grass_region = self._create_grass_region_env(bbox)
+
+            #TODO hack check if raster exists?
+            patch_rast = "temp_scatt_patch_%d" % (os.getpid())
+            self._rasterize(grass_region, layer, cat, patch_rast)
+
+            region = self.an_data.GetRegion()
+            ret = UpdateCatRast(patch_rast, region, self.scatts_dt.GetCatRastCond(cat))
+            if ret < 0:
+                GException(_("Patching category raster conditions file failed."))            
+            RunCommand("g.remove",
+                      rast = patch_rast)
+
+    def _rasterize(self, grass_region, layer, cat, out_rast):
+
+        #TODO different thread may be problem when user edits map
+        environs = os.environ.copy()
+        environs['GRASS_VECTOR_TEMPORARY'] = '1'
+
+        ret, text, msg = RunCommand("v.category",
+                                    input=self.vectMap,
+                                    getErrorMsg = True,
+                                    option='report',
+                                    read=True,
+                                    env=environs)
+
+        ret, text, msg = RunCommand("v.build",
+                                    map = self.vectMap,
+                                    getErrorMsg = True,
+                                    read = True,
+                                    env = environs)
+
+        if ret != 0:
+            GException(_("v.build failed:\n%s" % msg))
+
+        environs = os.environ.copy()
+        environs["GRASS_REGION"] = grass_region["GRASS_REGION"]
+        environs['GRASS_VECTOR_TEMPORARY'] = '1'
+
+        ret, text, msg = RunCommand("v.to.rast",
+                                    input = self.vectMap,
+                                    use = "cat",
+                                    layer = str(layer),
+                                    cat = str(cat),
+                                    output = out_rast,
+                                    getErrorMsg = True,
+                                    read = True,
+                                    overwrite = True,
+                                    env = environs)
+
+        if ret != 0:
+            GException(_("v.to.rast failed:\n%s" % msg))
+
+    def _create_grass_region_env(self, bbox):
+
+        r = self.an_data.GetRegion()
+        new_r = {}
+
+        if bbox["maxy"] <= r["s"]:
+            return 0
+        elif bbox["maxy"] >= r["n"]:
+            new_r["n"] = bbox["maxy"]
+        else:
+            new_r["n"] = ceil((bbox["maxy"] - r["s"]) / r["nsres"]) * r["nsres"] + r["s"]
+
+        if bbox["miny"] >= r["n"]:
+            return 0
+        elif bbox["miny"] <= r["s"]:
+            new_r["s"] = bbox["miny"]
+        else:
+            new_r["s"] = floor((bbox["miny"] - r["s"]) / r["nsres"]) * r["nsres"] + r["s"]
+
+        if bbox["maxx"] <= r["w"]:
+            return 0
+        elif bbox["maxx"] >= r["e"]:
+            new_r["e"] = bbox["maxx"]
+        else:
+            new_r["e"] = ceil((bbox["maxx"] - r["w"]) / r["ewres"]) * r["ewres"] + r["w"]
+
+        if bbox["minx"] >= r["e"]:
+            return 0
+        elif bbox["minx"] <= r["w"]:
+            new_r["w"] = bbox["minx"]
+        else:
+            new_r["w"] = floor((bbox["minx"] - r["w"]) / r["ewres"]) * r["ewres"] + r["w"]
+
+        #TODO check regions resolution
+        new_r["nsres"] = r["nsres"]
+        new_r["ewres"] = r["ewres"]
+
+        return {"GRASS_REGION" :  grass.region_env(**new_r)}
+
+class AnalyzedData:
+    """!Represents analyzed data (bands, region).
+    """
+    def __init__(self):
+        
+        self.bands = []
+        self.bands_info = {}
+
+        self.region = None
+
+    def GetRegion(self):
+        return self.region
+
+    def Create(self, bands):
+        self.bands = bands
+        self.region = None
+
+        ret, region, msg = RunCommand("g.region",
+                                      flags = "gp",
+                                      getErrorMsg = True,
+                                      read = True)
+
+        if  ret != 0:
+            raise GException("g.region failed:\n%s" % msg)
+
+        self.bands_info = {}
+        #TODO show some warning
+        for b in self.bands[:]:
+            i = self._getRasterInfo(b)
+            if i["datatype"] != "CELL":
+                self.bands.remove(b)
+                continue
+            self.bands_info[b] = i
+            #TODO check size of raster
+
+        self.region = self._parseRegion(region)
+
+    def _getRasterInfo(self, rast):
+        """
+        """
+        ret, out, msg = RunCommand("r.info",
+                                    map = rast,
+                                    flags = "rg",
+                                    getErrorMsg = True,
+                                    read = True)
+
+        if  ret != 0:
+            raise GException("r.info failed:\n%s" % msg)
+
+        out = out.split("\n")
+        raster_info = {} 
+
+        for b in out:
+            if not b.strip():
+                continue
+            k, v = b.split("=")
+            if k == "datatype":
+                pass
+            elif k in ['rows', 'cols', 'cells', 'min', 'max']:
+                v = int(v)
+            else:
+                v = float(v)
+
+            raster_info[k] = v
+
+        raster_info['range'] = raster_info['max'] - raster_info['min'] + 1
+        return raster_info
+
+    def GetBands(self):
+        return self.bands
+
+    def GetBandInfo(self, band_id):
+        band = self.bands[band_id]
+        return self.bands_info[band]
+
+    def _parseRegion(self, region_str):
+
+        region = {}
+        region_str = region_str.splitlines()
+
+        for param in region_str:
+            k, v = param.split("=")
+            if k in ["rows", "cols", "cells"]:
+                v = int(v)
+            else:
+                v = float(v)
+            region[k] = v
+
+        return region
+
+class ScattPlotsCondsData:
+    """!Data structure for selected areas in scatter plot(condtions).
+    """
+    def __init__(self, an_data):
+
+        self.an_data = an_data
+
+        #TODO
+        self.max_n_cats = 10
+    
+        self.dtype = 'uint8'
+        self.type = 1;
+        self.CleanUp()
+
+    def CleanUp(self):
+    
+        self.cats = {}
+
+        self.n_scatts = -1
+        self.n_bands = -1
+
+        for cat_id in self.cats.keys():
+            self.DeleteCategory(cat_id)
+
+    def Create(self, n_bands):
+
+        self.CleanUp()
+
+        self.n_scatts =  (n_bands - 1) * n_bands / 2;
+        self.n_bands = n_bands
+
+        self.AddCategory(cat_id = 0)
+
+    def AddCategory(self, cat_id):
+
+        if cat_id not in self.cats.keys():
+            self.cats[cat_id] = {}
+            return cat_id
+        return -1
+
+    def DeleteCategory(self, cat_id):
+
+        if cat_id not in self.cats.keys():
+            return False
+
+        for scatt in self.cats[cat_id].itervalues():
+            grass.try_remove(scatt['np_vals'])
+            del scatt['np_vals']
+
+        del self.cats[cat_id]
+
+        return True
+
+    def GetCategories(self):
+        return self.cats.keys()
+
+    def GetCatScatts(self, cat_id):
+
+        if not self.cats.has_key(cat_id):
+            return False
+
+        return self.cats[cat_id].keys()
+
+
+    def AddScattPlot(self, cat_id, scatt_id):
+
+        if not self.cats.has_key(cat_id):
+            return -1
+
+        if self.cats[cat_id].has_key(scatt_id):
+            return 0
+
+        b_i = self.GetBandsInfo(scatt_id)
+
+        shape = (b_i['b2']['max'] - b_i['b2']['min'] + 1, b_i['b1']['max'] - b_i['b1']['min'] + 1)
+
+        np_vals = np.memmap(grass.tempfile(), dtype=self.dtype, mode='w+', shape = shape)
+
+        self.cats[cat_id][scatt_id] = {'np_vals' : np_vals}
+
+        return 1
+
+    def GetBandsInfo(self, scatt_id):
+        b1, b2 = idScattToidBands(scatt_id, len(self.an_data.GetBands()))
+
+        b1_info = self.an_data.GetBandInfo(b1)
+        b2_info = self.an_data.GetBandInfo(b2)
+
+        bands_info = {'b1' : b1_info,
+                      'b2' : b2_info}
+
+        return bands_info
+
+    def DeleScattPlot(self, cat_id, scatt_id):
+
+        if not self.cats.has_key(cat_id):
+            return False
+
+        if not self.cats[cat_id].has_key(scatt_id):
+            return False
+
+        del self.cats[cat_id][scatt_id]
+        return True
+
+    def GetValuesArr(self, cat_id, scatt_id):
+
+        if not self.cats.has_key(cat_id):
+            return None
+
+        if not self.cats[cat_id].has_key(scatt_id):
+            return None
+
+        return self.cats[cat_id][scatt_id]['np_vals']
+
+    def GetData(self, requested_dt):
+        
+        cats = {}
+        for cat_id, scatt_ids in requested_dt.iteritems():
+            if not cats.has_key(cat_id):
+                cats[cat_id] = {}
+            for scatt_id in scatt_ids:
+                # if key is missing condition is always True (full scatter plor is computed)
+                if self.cats[cat_id].has_key(scatt_id):
+                    cats[cat_id][scatt_id] = {'np_vals' : self.cats[cat_id][scatt_id]['np_vals'],
+                                              'bands_info' : self.GetBandsInfo(scatt_id)}
+                        
+        return cats
+
+    def SetData(self, cats):
+        
+        for cat_id, scatt_ids in cats.iteritems():            
+            for scatt_id in scatt_ids:
+                # if key is missing condition is always True (full scatter plor is computed)
+                if self.cats[cat_id].has_key(scatt_id):
+                    self.cats[cat_id][scatt_id]['np_vals'] = cats[cat_id][scatt_id]['np_vals']
+
+    def GetScatt(self, scatt_id, cats_ids = None):
+        scatts = {}
+        for cat_id in self.cats.iterkeys():
+            if cats_ids and cat_id not in cats_ids:
+                continue
+            if not self.cats[cat_id].has_key(scatt_id):
+                continue
+
+            scatts[cat_id] = {'np_vals' : self.cats[cat_id][scatt_id]['np_vals'],
+                              'bands_info' : self.GetBandsInfo(scatt_id)}
+        return scatts
+
+                   
+class ScattPlotsData(ScattPlotsCondsData):
+    """!Data structure for computed points (classes) in scatter plots.\
+    """
+    def __init__(self, an_data):
+
+        self.cats_rasts = {}
+        self.cats_rasts_conds = {}    
+        self.scatts_ids = []    
+
+        ScattPlotsCondsData.__init__(self, an_data)
+
+        self.dtype = 'uint32'
+
+        #TODO
+        self.type = 0
+
+    def AddCategory(self, cat_id):
+        cat_id = ScattPlotsCondsData.AddCategory(self, cat_id)
+        if cat_id < 0:
+            return cat_id
+
+        for scatt_id in self.scatts_ids:
+            ScattPlotsCondsData.AddScattPlot(self, cat_id, scatt_id)
+
+        if cat_id == 0:
+            self.cats_rasts_conds[cat_id] = None
+            self.cats_rasts[cat_id] = None
+        else:
+            self.cats_rasts_conds[cat_id] = grass.tempfile()
+            self.cats_rasts[cat_id] = "temp_cat_rast_%d_%d" % (cat_id, os.getpid())
+            region = self.an_data.GetRegion()
+            CreateCatRast(region, self.cats_rasts_conds[cat_id])
+
+        return cat_id
+
+    def DeleteCategory(self, cat_id):
+
+        ScattPlotsCondsData.DeleteCategory(self, cat_id)
+        
+        grass.try_remove(self.cats_rasts_conds[cat_id])
+        del self.cats_rasts_conds[cat_id]
+
+        RunCommand("g.remove",
+                   rast=self.cats_rasts[cat_id])
+        del self.cats_rasts[cat_id]
+
+        return True
+
+    def AddScattPlot(self, scatt_id):
+        
+        if scatt_id in self.scatts_ids:
+            return False
+
+        self.scatts_ids.append(scatt_id)
+        for cat_id in self.cats.iterkeys():
+                ScattPlotsCondsData.AddScattPlot(self, cat_id, scatt_id)
+                self.cats[cat_id][scatt_id]['ellipse'] = None
+
+        return True
+
+    def DeleteScatterPlot(self, scatt_id):
+        
+        if scatt_id not in self.scatts_ids:
+            return False
+
+        self.scatts_ids.remove(scatt_id)
+
+        for cat_id in self.cats.iterkeys():
+                ScattPlotsCondsData.DeleteScattPlot(self, cat_id, scatt_id)
+
+        return True
+
+    def GetEllipses(self, scatt_id, styles):
+        if scatt_id not in self.scatts_ids:
+            return False
+
+        scatts = {}
+        for cat_id in self.cats.iterkeys():
+            if cat_id == 0:
+                continue
+            nstd = styles[cat_id]['nstd']
+            scatts[cat_id] = self._getEllipse(cat_id, scatt_id, nstd)
+
+        return scatts
+
+    def _getEllipse(self, cat_id, scatt_id, nstd):
+        # Joe Kington
+        # http://stackoverflow.com/questions/12301071/multidimensional-confidence-intervals
+
+        data = np.copy(self.cats[cat_id][scatt_id]['np_vals'])
+
+        b = self.GetBandsInfo(scatt_id)
+        sel_pts = np.where(data > 0)
+
+        x = sel_pts[1]
+        y = sel_pts[0]
+
+        flatten_data = data.reshape([-1])
+        flatten_sel_pts = np.nonzero(flatten_data)
+        weights = flatten_data[flatten_sel_pts]
+        if len(weights) == 0:
+            return None
+
+        x_avg = np.average(x, weights=weights)
+        y_avg = np.average(y, weights=weights)
+        pos = np.array([x_avg + b['b1']['min'], y_avg + b['b2']['min']])
+
+        x_diff = (x - x_avg)
+        y_diff = (y - y_avg)
+        
+        x_diff = (x - x_avg) 
+        y_diff = (y - y_avg) 
+
+        diffs = x_diff * y_diff.T
+        cov = np.dot(diffs, weights) / (np.sum(weights) - 1)
+
+        diffs = x_diff * x_diff.T
+        var_x = np.dot(diffs, weights) /  (np.sum(weights) - 1)
+        
+        diffs = y_diff * y_diff.T
+        var_y = np.dot(diffs, weights) /  (np.sum(weights) - 1)
+
+        cov = np.array([[var_x, cov],[cov, var_y]])
+
+        def eigsorted(cov):
+            vals, vecs = np.linalg.eigh(cov)
+            order = vals.argsort()[::-1]
+            return vals[order], vecs[:,order]
+
+        vals, vecs = eigsorted(cov)
+        theta = np.degrees(np.arctan2(*vecs[:,0][::-1]))
+
+        # Width and height are "full" widths, not radius
+        width, height = 2 * nstd * np.sqrt(vals)
+
+        ellipse = {'pos' : pos, 
+                   'width' : width,
+                   'height' : height,
+                   'theta' : theta}
+
+        del data
+        del flatten_data
+        del flatten_sel_pts
+        del weights
+        del sel_pts
+        return ellipse
+
+    def CleanUp(self):
+
+        ScattPlotsCondsData.CleanUp(self)        
+        for tmp in self.cats_rasts_conds.itervalues():
+            grass.try_remove(tmp) 
+        for tmp in self.cats_rasts.itervalues():
+            RunCommand("g.remove",
+                       rast=tmp,
+                       getErrorMsg=True)
+
+        self.cats_rasts = {}
+        self.cats_rasts_conds = {}
+
+    def GetCatRast(self, cat_id):
+        if self.cats_rasts.has_key(cat_id):
+            return self.cats_rasts[cat_id]
+        return None
+
+    def GetCatRastCond(self, cat_id):
+        return self.cats_rasts_conds[cat_id]
+
+    def GetCatsRastsConds(self):
+        max_cat_id = max(self.cats_rasts_conds.keys())
+
+        cats_rasts_conds = [''] * (max_cat_id + 1)
+        for i_cat_id, i_rast in self.cats_rasts_conds.iteritems():
+            cats_rasts_conds[i_cat_id] = i_rast
+
+        return cats_rasts_conds
+
+    def GetCatsRasts(self):
+        max_cat_id = max(self.cats_rasts.keys())
+
+        cats_rasts = [''] * (max_cat_id + 1)
+        for i_cat_id, i_rast in self.cats_rasts.iteritems():
+            cats_rasts[i_cat_id] = i_rast
+
+        return cats_rasts
+
+
+# not used,  using iclass_perimeter algorithm instead of scipy convolve2d
+"""
+def RasterizePolygon(pol, height, min_h, width, min_w):
+
+    # Joe Kington
+    # http://stackoverflow.com/questions/3654289/scipy-create-2d-polygon-mask
+
+    #poly_verts = [(1,1), (1,4), (4,4),(4,1), (1,1)]
+
+    nx = width
+    ny = height
+
+    x, y =  np.meshgrid(np.arange(-0.5 + min_w, nx + 0.5 + min_w, dtype=float), 
+                        np.arange(-0.5 + min_h, ny + 0.5 + min_h, dtype=float))
+    x, y = x.flatten(), y.flatten()
+
+    points = np.vstack((x,y)).T
+
+    p = Path(pol)
+    grid = p.contains_points(points)
+    grid = grid.reshape((ny + 1, nx + 1))
+    raster = np.zeros((height, width), dtype=np.uint8)#TODO bool
+
+    #TODO shift by 0.5
+    B = np.ones((2,2))/4
+    raster = convolve2d(grid, B, 'valid')
+
+    return raster
+"""
+
+def idScattToidBands(scatt_id, n_bands):
+    """!Get bands ids from scatter plot id.""" 
+    n_b1 = n_bands - 1
+
+    band_1 = (int) ((2 * n_b1 + 1 - sqrt(((2 * n_b1 + 1) * (2 * n_b1 + 1) - 8 * scatt_id))) / 2)
+
+    band_2 = scatt_id - (band_1 * (2 * n_b1 + 1) - band_1 * band_1) / 2 + band_1 + 1
+
+    return band_1, band_2
+
+def idBandsToidScatt(band_1_id, band_2_id, n_bands):
+    """!Get scatter plot id from band ids."""
+    if band_2_id <  band_1_id:
+        tmp = band_1_id
+        band_1_id = band_2_id
+        band_2_id = tmp
+
+    n_b1 = n_bands - 1
+
+    scatt_id = (band_1_id * (2 * n_b1 + 1) - band_1_id * band_1_id) / 2 + band_2_id - band_1_id - 1
+
+    return scatt_id
\ No newline at end of file


Property changes on: grass/trunk/gui/wxpython/iscatt/iscatt_core.py
___________________________________________________________________
Added: svn:mime-type
   + text/x-python
Added: svn:eol-style
   + native

Added: grass/trunk/gui/wxpython/iscatt/plots.py
===================================================================
--- grass/trunk/gui/wxpython/iscatt/plots.py	                        (rev 0)
+++ grass/trunk/gui/wxpython/iscatt/plots.py	2013-09-29 20:54:52 UTC (rev 57861)
@@ -0,0 +1,955 @@
+"""!
+ at package iscatt.plots
+
+ at brief Ploting widgets.
+
+Classes:
+ - plots::ScatterPlotWidget
+ - plots::PolygonDrawer
+ - plots::ModestImage
+ 
+(C) 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.
+
+ at author Stepan Turek <stepan.turek seznam.cz> (mentor: Martin Landa)
+"""
+import wx
+import numpy as np
+from math import ceil
+from multiprocessing import Process, Queue
+
+from copy import deepcopy
+from iscatt.core_c import MergeArrays, ApplyColormap
+from iscatt.dialogs import ManageBusyCursorMixin
+from core.settings import UserSettings
+
+try:
+    import matplotlib
+    matplotlib.use('WXAgg')
+    from matplotlib.figure import Figure
+    from matplotlib.backends.backend_wxagg import \
+    FigureCanvasWxAgg as FigCanvas
+    from matplotlib.lines import Line2D
+    from matplotlib.artist import Artist
+    from matplotlib.mlab import dist_point_to_segment
+    from matplotlib.patches import Polygon, Ellipse, Rectangle
+    import matplotlib.image as mi
+    import matplotlib.colors as mcolors
+    import matplotlib.cbook as cbook
+except ImportError as e:
+    raise ImportError(_("Unable to import matplotlib (try to install it).\n%s") % e)
+
+import grass.script as grass
+from grass.pydispatch.signal import Signal
+
+class ScatterPlotWidget(wx.Panel, ManageBusyCursorMixin):
+    def __init__(self, parent, scatt_id, scatt_mgr, transpose,
+                 id = wx.ID_ANY):
+        #TODO should not be transpose and scatt_id but x, y
+        wx.Panel.__init__(self, parent, id)
+        # bacause of aui (if floatable it can not take cursor from parent)        
+        ManageBusyCursorMixin.__init__(self, window=self)
+
+        self.parent = parent
+        self.full_extend = None
+        self.mode = None
+
+        self._createWidgets()
+        self._doLayout()
+        self.scatt_id = scatt_id
+        self.scatt_mgr = scatt_mgr
+
+        self.cidpress = None
+        self.cidrelease = None
+
+        self.rend_dt = {}
+
+        self.transpose = transpose
+
+        self.inverse = False
+
+        self.SetSize((200, 100))
+        self.Layout()
+
+        self.base_scale = 1.2
+        self.Bind(wx.EVT_CLOSE,lambda event : self.CleanUp())
+
+        self.plotClosed = Signal("ScatterPlotWidget.plotClosed")
+        self.cursorMove = Signal("ScatterPlotWidget.cursorMove")
+
+        self.contex_menu = ScatterPlotContextMenu(plot = self)
+
+        self.ciddscroll = None
+
+        self.canvas.mpl_connect('motion_notify_event', self.Motion)
+        self.canvas.mpl_connect('button_press_event', self.OnPress)
+        self.canvas.mpl_connect('button_release_event', self.OnRelease)
+        self.canvas.mpl_connect('draw_event', self.DrawCallback)
+        self.canvas.mpl_connect('figure_leave_event', self.OnCanvasLeave)
+
+    def DrawCallback(self, event):
+        self.polygon_drawer.DrawCallback(event)
+        self.axes.draw_artist(self.zoom_rect)
+
+    def _createWidgets(self):
+
+        # Create the mpl Figure and FigCanvas objects. 
+        # 5x4 inches, 100 dots-per-inch
+        #
+        self.dpi = 100
+        self.fig = Figure((1.0, 1.0), dpi=self.dpi)
+        self.fig.autolayout = True
+
+        self.canvas = FigCanvas(self, -1, self.fig)
+        
+        self.axes = self.fig.add_axes([0.0,0.0,1,1])
+
+        pol = Polygon(list(zip([0], [0])), animated=True)
+        self.axes.add_patch(pol)
+        self.polygon_drawer = PolygonDrawer(self.axes, pol = pol, empty_pol = True)
+
+        self.zoom_wheel_coords = None
+        self.zoom_rect_coords = None
+        self.zoom_rect = Polygon(list(zip([0], [0])), facecolor = 'none')
+        self.zoom_rect.set_visible(False)
+        self.axes.add_patch(self.zoom_rect)
+
+    def ZoomToExtend(self):
+        if self.full_extend:
+            self.axes.axis(self.full_extend)
+            self.canvas.draw()
+
+    def SetMode(self, mode):
+        self._deactivateMode()
+        if mode == 'zoom':
+            self.ciddscroll = self.canvas.mpl_connect('scroll_event', self.ZoomWheel)
+            self.mode = 'zoom'
+        elif mode == 'zoom_extend':
+            self.mode = 'zoom_extend'
+        elif mode == 'pan':
+            self.mode = 'pan'
+        elif mode:
+            self.polygon_drawer.SetMode(mode)
+
+    def SetSelectionPolygonMode(self, activate):
+        self.polygon_drawer.SetSelectionPolygonMode(activate)
+
+    def _deactivateMode(self):
+        self.mode  = None
+        self.polygon_drawer.SetMode(None)
+
+        if self.ciddscroll:
+            self.canvas.mpl_disconnect(self.ciddscroll)
+
+        self.zoom_rect.set_visible(False)
+        self._stopCategoryEdit()
+
+    def GetCoords(self):
+
+        coords = self.polygon_drawer.GetCoords()
+        if coords is None:
+            return
+
+        if self.transpose:
+            for c in coords:
+                tmp = c[0]
+                c[0] = c[1]
+                c[1] = tmp
+
+        return coords
+
+    def SetEmpty(self):
+        return self.polygon_drawer.SetEmpty()
+
+    def OnRelease(self, event):
+        if not self.mode == "zoom": return
+        self.zoom_rect.set_visible(False)
+        self.ZoomRectangle(event)
+        self.canvas.draw()
+    
+    def OnPress(self, event):
+        'on button press we will see if the mouse is over us and store some data'
+        if not event.inaxes:
+            return
+        if self.mode == "zoom_extend":
+            self.ZoomToExtend()
+
+        if event.xdata and event.ydata:
+            self.zoom_wheel_coords = { 'x' : event.xdata, 'y' : event.ydata}
+            self.zoom_rect_coords = { 'x' : event.xdata, 'y' : event.ydata}
+        else:
+            self.zoom_wheel_coords = None
+            self.zoom_rect_coords = None
+
+    def _stopCategoryEdit(self):
+        'disconnect all the stored connection ids'
+
+        if self.cidpress:
+            self.canvas.mpl_disconnect(self.cidpress)
+        if self.cidrelease:
+            self.canvas.mpl_disconnect(self.cidrelease)
+        #self.canvas.mpl_disconnect(self.cidmotion)
+
+    def _doLayout(self):
+        
+        self.main_sizer = wx.BoxSizer(wx.VERTICAL)
+        self.main_sizer.Add(self.canvas, 1, wx.LEFT | wx.TOP | wx.GROW)
+        self.SetSizer(self.main_sizer)
+        self.main_sizer.Fit(self)
+    
+    def Plot(self, cats_order, scatts, ellipses, styles):
+        """ Redraws the figure
+        """
+
+        callafter_list = []
+
+        if self.full_extend:
+            cx = self.axes.get_xlim()
+            cy = self.axes.get_ylim()
+            c = cx + cy
+        else:
+            c = None
+
+        q = Queue()
+        p = Process(target=MergeImg, args=(cats_order, scatts, styles, 
+                                           self.rend_dt, q))
+        p.start()
+        merged_img, self.full_extend, self.rend_dt = q.get()
+        p.join()
+        
+        #merged_img, self.full_extend = MergeImg(cats_order, scatts, styles, None)
+        self.axes.clear()
+        self.axes.axis('equal')
+
+        if self.transpose:
+            merged_img = np.transpose(merged_img, (1, 0, 2))
+
+        img = imshow(self.axes, merged_img,
+                     extent= [int(ceil(x)) for x in self.full_extend],
+                     origin='lower',
+                     interpolation='nearest',
+                     aspect="equal")
+
+        callafter_list.append([self.axes.draw_artist, [img]])
+        callafter_list.append([grass.try_remove, [merged_img.filename]])
+
+        for cat_id in cats_order:
+            if cat_id == 0:
+                continue
+            if not ellipses.has_key(cat_id):
+                continue
+                
+            e = ellipses[cat_id]
+            if not e:
+                continue
+
+            colors = styles[cat_id]['color'].split(":")
+            if self.transpose:
+                e['theta'] = 360 - e['theta'] + 90
+                if e['theta'] >= 360:
+                    e['theta'] = abs(360 - e['theta']) 
+                
+                e['pos'] = [e['pos'][1], e['pos'][0]]
+
+            ellip = Ellipse(xy=e['pos'], 
+                            width=e['width'], 
+                            height=e['height'], 
+                            angle=e['theta'], 
+                            edgecolor="w",
+                            linewidth=1.5, 
+                            facecolor='None')
+            self.axes.add_artist(ellip)
+            callafter_list.append([self.axes.draw_artist, [ellip]])
+
+            color = map(lambda v : int(v)/255.0, styles[cat_id]['color'].split(":"))
+
+            ellip = Ellipse(xy=e['pos'], 
+                            width=e['width'], 
+                            height=e['height'], 
+                            angle=e['theta'], 
+                            edgecolor=color,
+                            linewidth=1, 
+                            facecolor='None')
+
+            self.axes.add_artist(ellip)
+            callafter_list.append([self.axes.draw_artist, [ellip]])
+            
+            center = Line2D([e['pos'][0]], [e['pos'][1]], 
+                            marker='x',
+                            markeredgecolor='w',
+                            #markerfacecolor=color,
+                            markersize=2)
+            self.axes.add_artist(center)
+            callafter_list.append([self.axes.draw_artist, [center]])
+
+        callafter_list.append([self.fig.canvas.blit, []])
+
+        if c:
+            self.axes.axis(c)
+        wx.CallAfter(lambda : self.CallAfter(callafter_list))
+    
+    def CallAfter(self, funcs_list):
+        while funcs_list: 
+            fcn, args = funcs_list.pop(0) 
+            fcn(*args) 
+
+        self.canvas.draw()
+
+    def CleanUp(self):
+        self.plotClosed.emit(scatt_id = self.scatt_id)
+        self.Destroy()
+
+    def ZoomWheel(self, event):
+        # get the current x and y limits
+        if not event.inaxes:
+            return
+        # tcaswell
+        # http://stackoverflow.com/questions/11551049/matplotlib-plot-zooming-with-scroll-wheel
+        cur_xlim = self.axes.get_xlim()
+        cur_ylim = self.axes.get_ylim()
+        
+        xdata = event.xdata
+        ydata = event.ydata 
+        if event.button == 'up':
+            scale_factor = 1/self.base_scale
+        elif event.button == 'down':
+            scale_factor = self.base_scale
+        else:
+            scale_factor = 1
+
+        extend = (xdata - (xdata - cur_xlim[0]) * scale_factor,
+                  xdata + (cur_xlim[1] - xdata) * scale_factor, 
+                  ydata - (ydata - cur_ylim[0]) * scale_factor,
+                  ydata + (cur_ylim[1] - ydata) * scale_factor)
+
+        self.axes.axis(extend)
+        
+        self.canvas.draw()
+
+    def ZoomRectangle(self, event):
+        # get the current x and y limits
+        if not self.mode == "zoom": return
+        if event.inaxes is None: return
+        if event.button != 1: return
+
+        cur_xlim = self.axes.get_xlim()
+        cur_ylim = self.axes.get_ylim()
+        
+        x1, y1 = event.xdata, event.ydata
+        x2 = deepcopy(self.zoom_rect_coords['x'])
+        y2 = deepcopy(self.zoom_rect_coords['y'])
+
+        if x1 == x2 or y1 == y2:
+            return
+
+        self.axes.axis((x1, x2, y1, y2))
+        #self.axes.set_xlim(x1, x2)#, auto = True)
+        #self.axes.set_ylim(y1, y2)#, auto = True)
+        self.canvas.draw()
+
+    def Motion(self, event):
+        self.PanMotion(event)
+        self.ZoomRectMotion(event)
+        
+        if event.inaxes is None: 
+            return
+        
+        self.cursorMove.emit(x=event.xdata, y=event.ydata, scatt_id=self.scatt_id)
+
+    def OnCanvasLeave(self, event):
+        self.cursorMove.emit(x=None, y=None, scatt_id=self.scatt_id)
+
+    def PanMotion(self, event):
+        'on mouse movement'
+        if not self.mode == "pan": 
+            return
+        if event.inaxes is None: 
+            return
+        if event.button != 1: 
+            return
+
+        cur_xlim = self.axes.get_xlim()
+        cur_ylim = self.axes.get_ylim()
+
+        x,y = event.xdata, event.ydata
+        
+        mx = (x - self.zoom_wheel_coords['x']) * 0.6
+        my = (y - self.zoom_wheel_coords['y']) * 0.6
+
+        extend = (cur_xlim[0] - mx, cur_xlim[1] - mx, cur_ylim[0] - my, cur_ylim[1] - my)
+
+        self.zoom_wheel_coords['x'] = x
+        self.zoom_wheel_coords['y'] = y
+
+        self.axes.axis(extend)
+
+        #self.canvas.copy_from_bbox(self.axes.bbox)
+        #self.canvas.restore_region(self.background)
+        self.canvas.draw()
+        
+    def ZoomRectMotion(self, event):
+        if not self.mode == "zoom": return
+        if event.inaxes is None: return
+        if event.button != 1: return
+
+        x1, y1 = event.xdata, event.ydata
+        self.zoom_rect.set_visible(True)
+        x2 = self.zoom_rect_coords['x']
+        y2 = self.zoom_rect_coords['y']
+
+        self.zoom_rect.xy = ((x1, y1), (x1, y2), (x2, y2), (x2, y1), (x1, y1))
+
+        #self.axes.draw_artist(self.zoom_rect)
+        self.canvas.draw()
+
+def MergeImg(cats_order, scatts, styles, rend_dt, output_queue):
+
+        init = True
+        merged_img = None
+        merge_tmp = grass.tempfile()
+        for cat_id in cats_order:
+            if not scatts.has_key(cat_id):
+                continue
+            scatt = scatts[cat_id]
+            #print "color map %d" % cat_id
+            #TODO make more general
+            if cat_id != 0 and (styles[cat_id]['opacity'] == 0.0 or \
+               not styles[cat_id]['show']):
+                if rend_dt.has_key(cat_id) and not rend_dt[cat_id]:
+                    del rend_dt[cat_id]
+                continue
+            if init:
+
+                b2_i = scatt['bands_info']['b1']
+                b1_i = scatt['bands_info']['b2']
+
+                full_extend = (b1_i['min'] - 0.5, b1_i['max'] + 0.5, b2_i['min'] - 0.5, b2_i['max'] + 0.5) 
+            
+            # if it does not need to be updated and was already rendered 
+            if not _renderCat(cat_id, rend_dt, scatt, styles):
+                # is empty - has only zeros
+                if rend_dt[cat_id] is None:
+                    continue
+            else:
+                masked_cat = np.ma.masked_less_equal(scatt['np_vals'], 0)
+                vmax = np.amax(masked_cat)
+                # totally empty -> no need to render
+                if vmax == 0:
+                    render_cat_ids[cat_id] = None
+                    continue
+
+                cmap = _getColorMap(cat_id, styles)
+                masked_cat = np.uint8(masked_cat * (255.0 / float(vmax)))
+
+                cmap = np.uint8(cmap._lut * 255)
+                sh =masked_cat.shape
+
+                rend_dt[cat_id] = {}
+                if cat_id != 0:
+                    rend_dt[cat_id]['color'] = styles[cat_id]['color']
+                rend_dt[cat_id]['dt'] = np.memmap(grass.tempfile(), dtype='uint8', mode='w+', 
+                                                                    shape=(sh[0], sh[1], 4))
+                #colored_cat = np.zeros(dtype='uint8', )
+                ApplyColormap(masked_cat, masked_cat.mask, cmap, rend_dt[cat_id]['dt'])
+
+                #colored_cat = np.uint8(cmap(masked_cat) * 255)
+                del masked_cat
+                del cmap
+            
+            #colored_cat[...,3] = np.choose(masked_cat.mask, (255, 0))
+            if init:
+                merged_img = np.memmap(merge_tmp, dtype='uint8', mode='w+', 
+                                       shape=rend_dt[cat_id]['dt'].shape)
+                merged_img[:] = rend_dt[cat_id]['dt']
+                init = False
+            else:
+                MergeArrays(merged_img, rend_dt[cat_id]['dt'], styles[cat_id]['opacity'])
+
+            """
+                #c_img_a = np.memmap(grass.tempfile(), dtype="uint16", mode='w+', shape = shape) 
+                c_img_a = colored_cat.astype('uint16')[:,:,3] * styles[cat_id]['opacity']
+
+                #TODO apply strides and there will be no need for loop
+                #b = as_strided(a, strides=(0, a.strides[3], a.strides[3], a.strides[3]), shape=(3, a.shape[0], a.shape[1]))
+                
+                for i in range(3):
+                    merged_img[:,:,i] = (merged_img[:,:,i] * (255 - c_img_a) + colored_cat[:,:,i] * c_img_a) / 255;
+                merged_img[:,:,3] = (merged_img[:,:,3] * (255 - c_img_a) + 255 * c_img_a) / 255;
+                
+                del c_img_a
+            """
+
+        output_queue.put((merged_img, full_extend, rend_dt))
+
+def _renderCat(cat_id, rend_dt, scatt, styles):
+    return True
+
+    if not rend_dt.has_key(cat_id):
+        return True
+    if not rend_dt[cat_id]:
+        return False
+    if scatt['render']:
+        return True
+    if cat_id != 0 and \
+       rend_dt[cat_id]['color'] != styles[cat_id]['color']:
+       return True
+    
+    return False
+
+def _getColorMap(cat_id, styles):
+    cmap = matplotlib.cm.jet
+    if cat_id == 0:
+        cmap.set_bad('w',1.)
+        cmap._init()
+        cmap._lut[len(cmap._lut) - 1, -1] = 0
+    else:
+        colors = styles[cat_id]['color'].split(":")
+
+        cmap.set_bad('w',1.)
+        cmap._init()
+        cmap._lut[len(cmap._lut) - 1, -1] = 0
+        cmap._lut[:, 0] = int(colors[0])/255.0
+        cmap._lut[:, 1] = int(colors[1])/255.0
+        cmap._lut[:, 2] = int(colors[2])/255.0
+
+    return cmap
+
+class ScatterPlotContextMenu:
+    def __init__(self, plot):
+
+        self.plot = plot
+        self.canvas = plot.canvas
+        self.cidpress = self.canvas.mpl_connect(
+            'button_press_event', self.ContexMenu)
+   
+    def ContexMenu(self, event):
+        if not event.inaxes:
+            return
+
+        if event.button == 3:
+            menu = wx.Menu()       
+            menu_items = [["zoom_to_extend", _("Zoom to scatter plot extend"), 
+                            lambda event : self.plot.ZoomToExtend()]]
+
+            for item in menu_items:
+                item_id = wx.ID_ANY
+                menu.Append(item_id, text = item[1])
+                menu.Bind(wx.EVT_MENU, item[2], id = item_id)
+
+            wx.CallAfter(self.ShowMenu, menu) 
+   
+    def ShowMenu(self, menu):
+        self.plot.PopupMenu(menu)
+        menu.Destroy()
+        self.plot.ReleaseMouse() 
+
+class PolygonDrawer:
+    """
+    An polygon editor.
+    """
+    def __init__(self, ax, pol, empty_pol):
+        if pol.figure is None:
+            raise RuntimeError('You must first add the polygon to a figure or canvas before defining the interactor')
+        self.ax = ax
+        self.canvas = pol.figure.canvas
+
+        self.showverts = True
+
+        self.pol = pol
+        self.empty_pol = empty_pol
+
+        x, y = zip(*self.pol.xy)
+
+        style = self._getPolygonStyle()
+
+        self.line = Line2D(x, y, marker='o', markerfacecolor='r', animated=True)
+        self.ax.add_line(self.line)
+        #self._update_line(pol)
+
+        cid = self.pol.add_callback(self.poly_changed)
+        self.moving_ver_idx = None # the active vert
+
+        self.mode = None
+
+        if self.empty_pol:
+            self._show(False)
+
+        #self.canvas.mpl_connect('draw_event', self.DrawCallback)
+        self.canvas.mpl_connect('button_press_event', self.OnButtonPressed)
+        self.canvas.mpl_connect('button_release_event', self.ButtonReleaseCallback)
+        self.canvas.mpl_connect('motion_notify_event', self.motion_notify_callback)
+    
+        self.it = 0
+
+    def _getPolygonStyle(self):
+        style = {}
+        style['sel_pol'] = UserSettings.Get(group='scatt', 
+                                            key='selection', 
+                                            subkey='sel_pol')
+        style['sel_pol_vertex'] = UserSettings.Get(group='scatt', 
+                                                   key='selection', 
+                                                   subkey='sel_pol_vertex')
+
+        style['sel_pol'] = [i / 255.0 for i in style['sel_pol']] 
+        style['sel_pol_vertex'] = [i / 255.0 for i in style['sel_pol_vertex']] 
+
+        return style
+
+    def _getSnapTresh(self):
+        return UserSettings.Get(group='scatt', 
+                                key='selection', 
+                                subkey='snap_tresh')
+
+    def SetMode(self, mode):
+        self.mode = mode
+
+    def SetSelectionPolygonMode(self, activate):
+        
+        self.Show(activate)
+        if not activate and self.mode:
+            self.SetMode(None) 
+
+    def Show(self, show):
+        if show:
+            if not self.empty_pol:
+                self._show(True)
+        else:
+            self._show(False)
+
+    def GetCoords(self):
+        if self.empty_pol:
+            return None
+
+        coords = deepcopy(self.pol.xy)
+        return coords
+
+    def SetEmpty(self):
+        self._setEmptyPol(True)
+
+    def _setEmptyPol(self, empty_pol):
+        self.empty_pol = empty_pol
+        if self.empty_pol:
+            #TODO
+            self.pol.xy = np.array([[0, 0]])
+        self._show(not empty_pol)
+
+    def _show(self, show):
+
+        self.show = show
+
+        self.line.set_visible(self.show)
+        self.pol.set_visible(self.show)
+
+        self.Redraw()
+
+    def Redraw(self):
+        if self.show:
+            self.ax.draw_artist(self.pol)
+            self.ax.draw_artist(self.line)
+        self.canvas.blit(self.ax.bbox)
+        self.canvas.draw()
+
+    def DrawCallback(self, event):
+
+        style=self._getPolygonStyle()
+        self.pol.set_facecolor(style['sel_pol'])
+        self.line.set_markerfacecolor(style['sel_pol_vertex'])
+
+        self.background = self.canvas.copy_from_bbox(self.ax.bbox)
+        self.ax.draw_artist(self.pol)
+        self.ax.draw_artist(self.line)
+    
+    def poly_changed(self, pol):
+        'this method is called whenever the polygon object is called'
+        # only copy the artist props to the line (except visibility)
+        vis = self.line.get_visible()
+        Artist.update_from(self.line, pol)
+        self.line.set_visible(vis)  # don't use the pol visibility state
+
+    def get_ind_under_point(self, event):
+        'get the index of the vertex under point if within treshold'
+
+        # display coords
+        xy = np.asarray(self.pol.xy)
+        xyt = self.pol.get_transform().transform(xy)
+        xt, yt = xyt[:, 0], xyt[:, 1]
+        d = np.sqrt((xt-event.x)**2 + (yt-event.y)**2)
+        indseq = np.nonzero(np.equal(d, np.amin(d)))[0]
+        ind = indseq[0]
+
+        if d[ind]>=self._getSnapTresh():
+            ind = None
+
+        return ind
+
+    def OnButtonPressed(self, event):
+        if not event.inaxes:
+            return
+
+        if event.button in [2, 3]: 
+            return
+
+        if self.mode == "delete_vertex":
+            self._deleteVertex(event)
+        elif self.mode == "add_boundary_vertex":
+            self._addVertexOnBoundary(event)
+        elif self.mode == "add_vertex":
+            self._addVertex(event)
+        elif self.mode == "remove_polygon":
+            self.SetEmpty()
+        self.moving_ver_idx = self.get_ind_under_point(event)
+
+    def ButtonReleaseCallback(self, event):
+        'whenever a mouse button is released'
+        if not self.showverts: return
+        if event.button != 1: return
+        self.moving_ver_idx = None
+
+    def ShowVertices(self, show):
+        self.showverts = show
+        self.line.set_visible(self.showverts)
+        if not self.showverts: self.moving_ver_idx = None
+
+    def _deleteVertex(self, event):
+        ind = self.get_ind_under_point(event)
+
+        if ind  is None or self.empty_pol:
+            return
+
+        if len(self.pol.xy) <= 2:
+            self.empty_pol = True
+            self._show(False)
+            return
+
+        coords = []
+        for i,tup in enumerate(self.pol.xy): 
+            if i == ind:
+                continue
+            elif i == 0 and ind == len(self.pol.xy) - 1:
+                continue
+            elif i == len(self.pol.xy) - 1 and ind == 0: 
+                continue
+
+            coords.append(tup)
+
+        self.pol.xy = coords
+        self.line.set_data(zip(*self.pol.xy))
+
+        self.Redraw()
+
+    def _addVertexOnBoundary(self, event):
+        if self.empty_pol:
+            return
+
+        xys = self.pol.get_transform().transform(self.pol.xy)
+        p = event.x, event.y # display coords
+        for i in range(len(xys)-1):
+            s0 = xys[i]
+            s1 = xys[i+1]
+            d = dist_point_to_segment(p, s0, s1)
+
+            if d<=self._getSnapTresh():
+                self.pol.xy = np.array(
+                    list(self.pol.xy[:i + 1]) +
+                    [(event.xdata, event.ydata)] +
+                    list(self.pol.xy[i + 1:]))
+                self.line.set_data(zip(*self.pol.xy))
+                break
+
+        self.Redraw()
+
+    def _addVertex(self, event):
+
+        if self.empty_pol:
+            pt = (event.xdata, event.ydata)
+            self.pol.xy = np.array([pt, pt])
+            self._show(True)
+            self.empty_pol = False
+        else:
+            self.pol.xy = np.array(
+                        [(event.xdata, event.ydata)] +
+                        list(self.pol.xy[1:]) +
+                        [(event.xdata, event.ydata)])
+
+        self.line.set_data(zip(*self.pol.xy))
+        
+        self.Redraw()
+
+    def motion_notify_callback(self, event):
+        'on mouse movement'
+        if not self.mode == "move_vertex": return
+        if not self.showverts: return
+        if self.empty_pol: return
+        if self.moving_ver_idx is None: return
+        if event.inaxes is None: return
+        if event.button != 1: return
+
+        self.it += 1
+
+        x,y = event.xdata, event.ydata
+
+        self.pol.xy[self.moving_ver_idx] = x,y
+        if self.moving_ver_idx == 0:
+            self.pol.xy[len(self.pol.xy) - 1] = x,y
+        elif self.moving_ver_idx == len(self.pol.xy) - 1:
+            self.pol.xy[0] = x,y
+
+        self.line.set_data(zip(*self.pol.xy))
+
+        self.canvas.restore_region(self.background)
+
+        self.Redraw()
+
+class ModestImage(mi.AxesImage):
+    """
+    Computationally modest image class.
+
+    ModestImage is an extension of the Matplotlib AxesImage class
+    better suited for the interactive display of larger images. Before
+    drawing, ModestImage resamples the data array based on the screen
+    resolution and view window. This has very little affect on the
+    appearance of the image, but can substantially cut down on
+    computation since calculations of unresolved or clipped pixels
+    are skipped.
+
+    The interface of ModestImage is the same as AxesImage. However, it
+    does not currently support setting the 'extent' property. There
+    may also be weird coordinate warping operations for images that
+    I'm not aware of. Don't expect those to work either.
+
+    Author: Chris Beaumont <beaumont at hawaii.edu>
+    """
+    def __init__(self, minx=0.0, miny=0.0, *args, **kwargs):
+        if 'extent' in kwargs and kwargs['extent'] is not None:
+            raise NotImplementedError("ModestImage does not support extents")
+
+        self._full_res = None
+        self._sx, self._sy = None, None
+        self._bounds = (None, None, None, None)
+        self.minx = minx
+        self.miny = miny
+
+        super(ModestImage, self).__init__(*args, **kwargs)
+
+    def set_data(self, A):
+        """
+        Set the image array
+
+        ACCEPTS: numpy/PIL Image A
+        """
+        self._full_res = A
+        self._A = A
+
+        if self._A.dtype != np.uint8 and not np.can_cast(self._A.dtype,
+                                                         np.float):
+            raise TypeError("Image data can not convert to float")
+
+        if (self._A.ndim not in (2, 3) or
+            (self._A.ndim == 3 and self._A.shape[-1] not in (3, 4))):
+            raise TypeError("Invalid dimensions for image data")
+
+        self._imcache =None
+        self._rgbacache = None
+        self._oldxslice = None
+        self._oldyslice = None
+        self._sx, self._sy = None, None
+
+    def get_array(self):
+        """Override to return the full-resolution array"""
+        return self._full_res
+
+    def _scale_to_res(self):
+        """ Change self._A and _extent to render an image whose
+        resolution is matched to the eventual rendering."""
+
+        ax = self.axes
+        ext = ax.transAxes.transform([1, 1]) - ax.transAxes.transform([0, 0])
+        xlim, ylim = ax.get_xlim(), ax.get_ylim()
+        dx, dy = xlim[1] - xlim[0], ylim[1] - ylim[0]
+
+        y0 = max(self.miny, ylim[0] - 5)
+        y1 = min(self._full_res.shape[0] + self.miny, ylim[1] + 5)
+        x0 = max(self.minx, xlim[0] - 5)
+        x1 = min(self._full_res.shape[1] + self.minx, xlim[1] + 5)
+        y0, y1, x0, x1 = map(int, [y0, y1, x0, x1])
+
+        sy = int(max(1, min((y1 - y0) / 5., np.ceil(dy / ext[1]))))
+        sx = int(max(1, min((x1 - x0) / 5., np.ceil(dx / ext[0]))))
+
+        # have we already calculated what we need?
+        if sx == self._sx and sy == self._sy and \
+            x0 == self._bounds[0] and x1 == self._bounds[1] and \
+            y0 == self._bounds[2] and y1 == self._bounds[3]:
+            return
+
+        self._A = self._full_res[y0 - self.miny:y1 - self.miny:sy, 
+                                 x0 - self.minx:x1 - self.minx:sx]
+
+        x1 = x0 + self._A.shape[1] * sx
+        y1 = y0 + self._A.shape[0] * sy
+
+        self.set_extent([x0 - .5, x1 - .5, y0 - .5, y1 - .5])
+        self._sx = sx
+        self._sy = sy
+        self._bounds = (x0, x1, y0, y1)
+        self.changed()
+
+    def draw(self, renderer, *args, **kwargs):
+        self._scale_to_res()
+        super(ModestImage, self).draw(renderer, *args, **kwargs)
+
+def imshow(axes, X, cmap=None, norm=None, aspect=None,
+           interpolation=None, alpha=None, vmin=None, vmax=None,
+           origin=None, extent=None, shape=None, filternorm=1,
+           filterrad=4.0, imlim=None, resample=None, url=None, **kwargs):
+    """Similar to matplotlib's imshow command, but produces a ModestImage
+
+    Unlike matplotlib version, must explicitly specify axes
+    Author: Chris Beaumont <beaumont at hawaii.edu>
+    """
+
+    if not axes._hold:
+        axes.cla()
+    if norm is not None:
+        assert(isinstance(norm, mcolors.Normalize))
+    if aspect is None:
+        aspect = rcParams['image.aspect']
+    axes.set_aspect(aspect)
+
+    if extent:
+        minx=extent[0]
+        miny=extent[2]
+    else:
+        minx=0.0
+        miny=0.0
+
+    im = ModestImage(minx, miny, axes, cmap, norm, interpolation, origin, extent,
+                     filternorm=filternorm,
+                     filterrad=filterrad, resample=resample, **kwargs)
+
+    im.set_data(X)
+    im.set_alpha(alpha)
+    axes._set_artist_props(im)
+
+    if im.get_clip_path() is None:
+        # image does not already have clipping set, clip to axes patch
+        im.set_clip_path(axes.patch)
+
+    #if norm is None and shape is None:
+    #    im.set_clim(vmin, vmax)
+    if vmin is not None or vmax is not None:
+        im.set_clim(vmin, vmax)
+    else:
+        im.autoscale_None()
+    im.set_url(url)
+
+    # update ax.dataLim, and, if autoscaling, set viewLim
+    # to tightly fit the image, regardless of dataLim.
+    im.set_extent(im.get_extent())
+
+    axes.images.append(im)
+    im._remove_method = lambda h: axes.images.remove(h)
+
+    return im


Property changes on: grass/trunk/gui/wxpython/iscatt/plots.py
___________________________________________________________________
Added: svn:mime-type
   + text/x-python
Added: svn:eol-style
   + native

Added: grass/trunk/gui/wxpython/iscatt/toolbars.py
===================================================================
--- grass/trunk/gui/wxpython/iscatt/toolbars.py	                        (rev 0)
+++ grass/trunk/gui/wxpython/iscatt/toolbars.py	2013-09-29 20:54:52 UTC (rev 57861)
@@ -0,0 +1,267 @@
+"""!
+ at package iscatt.toolbars
+
+ at brief Scatter plot - toolbars
+
+Classes:
+ - toolbars::MainToolbar
+
+(C) 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.
+
+ at author Stepan Turek <stepan.turek seznam.cz> (mentor: Martin Landa)
+"""
+import wx
+
+from icons.icon import MetaIcon
+from gui_core.toolbars import BaseToolbar, BaseIcons
+from core.gcmd import RunCommand
+from core.gcmd import GException, GError, RunCommand
+from iscatt.iscatt_core import idBandsToidScatt
+from iscatt.dialogs import SettingsDialog
+
+class MainToolbar(BaseToolbar):
+    """!Main toolbar
+    """
+    def __init__(self, parent, scatt_mgr, opt_tools=None):
+        BaseToolbar.__init__(self, parent)
+        self.scatt_mgr = scatt_mgr
+        self.opt_tools = opt_tools
+
+        self.InitToolbar(self._toolbarData())
+        
+        # realize the toolbar
+        self.Realize()
+        self.scatt_mgr.modeSet.connect(self.ModeSet)
+
+    def _toolbarData(self):
+
+        icons = {
+                'selectGroup' : MetaIcon(img = 'layer-group-add',
+                                 label = _('Select imagery group')),
+                'settings'   : BaseIcons['settings'].SetLabel( _('Settings')),
+                'help'       : MetaIcon(img = 'help',
+                                         label = _('Show manual')),
+                'add_scatt_pl'  : MetaIcon(img = 'layer-raster-analyze',
+                                            label = _('Add scatter plot')),
+                'selCatPol'  : MetaIcon(img = 'polygon',
+                                      label = _('Select area with polygon')),
+                'pan'        : MetaIcon(img = 'pan',
+                                         label = _('Pan mode for scatter plots')),
+                'zoomIn'     : MetaIcon(img = 'zoom-in',
+                                        label = _('Zoom mode for scatter plots (left mouse button, wheel)')),
+                'zoomExtent' : MetaIcon(img = 'zoom-extent',
+                                       label = _('Zoom to scatter plot data extend mode (click on scatter plot for zooming to extend)')),
+                'cats_mgr' : MetaIcon(img = 'table-manager',
+                                          label = _('Show/hide class manager'))
+                }
+            
+        tools = [
+                    ('add_scatt', icons["add_scatt_pl"],
+                    lambda event : self.scatt_mgr.AddScattPlot()),
+                    (None, ),
+                    ("cats_mgr", icons['cats_mgr'],
+                    lambda event: self.parent.ShowCategoryPanel(event.Checked()), wx.ITEM_CHECK),
+                    (None, ),
+                    ("pan", icons["pan"],
+                    lambda event: self.SetPloltsMode(event, 'pan'),
+                    wx.ITEM_CHECK),
+                    ("zoom", icons["zoomIn"],
+                    lambda event: self.SetPloltsMode(event, 'zoom'),
+                    wx.ITEM_CHECK),
+                    ("zoom_extend", icons["zoomExtent"],
+                    lambda event: self.SetPloltsMode(event, 'zoom_extend'),
+                    wx.ITEM_CHECK),
+                    (None, ),
+                    ('sel_pol_mode', icons['selCatPol'],
+                    self.ActivateSelectionPolygonMode,
+                    wx.ITEM_CHECK),
+                    (None, ),
+                    ('settings', icons["settings"],
+                    self.OnSettings),
+                    ('help', icons["help"],
+                     self.OnHelp)                    
+                ]
+
+        if self.opt_tools and "add_group" in self.opt_tools:
+            tools.insert(0, ("selectGroup", icons['selectGroup'],
+                             lambda event : self.scatt_mgr.SetData()))
+
+        return self._getToolbarData(tools)
+
+    def GetToolId(self, toolName): #TODO can be useful in base
+        return vars(self)[toolName]            
+
+    def SetPloltsMode(self, event, tool_name):
+        self.scatt_mgr.modeSet.disconnect(self.ModeSet)
+        if event.Checked()  == True:
+            for i_tool_data in  self._data:
+                i_tool_name = i_tool_data[0]
+                if not i_tool_name or i_tool_name in ["cats_mgr", "sel_pol_mode"]:
+                    continue
+                if i_tool_name == tool_name:
+                    continue
+                i_tool_id = vars(self)[i_tool_name]
+                self.ToggleTool(i_tool_id, False)
+
+            self.scatt_mgr.SetPlotsMode(mode = tool_name)
+        else:
+            self.scatt_mgr.SetPlotsMode(mode = None)
+        self.scatt_mgr.modeSet.connect(self.ModeSet)
+
+    def ActivateSelectionPolygonMode(self, event):
+
+        activated = self.scatt_mgr.ActivateSelectionPolygonMode(event.Checked())
+        self.parent.ShowPlotEditingToolbar(activated)
+
+        i_tool_id = vars(self)['sel_pol_mode']
+        self.ToggleTool(i_tool_id, activated)
+
+    def ModeSet(self, mode):
+        self.UnsetMode()
+
+    def UnsetMode(self):
+        for i_tool_data in  self._data:
+                i_tool_name = i_tool_data[0]
+                if not i_tool_name or i_tool_name in ["cats_mgr", "sel_pol_mode"]:
+                    continue
+                i_tool_id = vars(self)[i_tool_name]
+                self.ToggleTool(i_tool_id, False)
+
+    def OnSettings(self, event):
+        dlg = SettingsDialog(parent=self, id=wx.ID_ANY, 
+                             title=_('Settings'), scatt_mgr = self.scatt_mgr)
+        
+        dlg.ShowModal()
+        dlg.Destroy()
+
+    def OnHelp(self, event) :
+            RunCommand('g.manual',
+                       entry = 'wxGUI.iscatt')
+
+class EditingToolbar(BaseToolbar):
+    """!Main toolbar
+    """
+    def __init__(self, parent, scatt_mgr):
+        BaseToolbar.__init__(self, parent)
+        self.scatt_mgr = scatt_mgr
+
+        self.InitToolbar(self._toolbarData())
+        
+        # realize the toolbar
+        self.Realize()
+        self.scatt_mgr.modeSet.connect(self.ModeSet)
+
+    def _toolbarData(self):
+        """!Toolbar data
+        """
+        self.icons = {
+            'sel_add'         : MetaIcon(img = 'layer-add',
+                                         label = _('Include selected area to class'),
+                                         desc = _('Include selected area to class')),
+            'sel_remove'      : MetaIcon(img = 'layer-remove',
+                                         label = _('Exclude selected area from class'),
+                                         desc = _('Exclude selected area from class')),
+            'addVertex'       : MetaIcon(img = 'vertex-create',
+                                         label = _('Add new vertex'),
+                                         desc = _('Add new vertex to polygon boundary scatter plot')),
+            'editLine'        : MetaIcon(img = 'polygon-create',
+                                         label = _('Create selection polygon'),
+                                         desc = _('Add new vertex between last and first points of the boundary')),
+            'moveVertex'      : MetaIcon(img = 'vertex-move',
+                                         label = _('Move vertex'),
+                                         desc = _('Move boundary vertex')),
+            'removeVertex'    : MetaIcon(img = 'vertex-delete',
+                                         label = _('Remove vertex'),
+                                         desc = _('Remove boundary vertex')),
+            'delete'        : MetaIcon(img = 'polygon-delete',
+                                       label = _("Remove polygon (click on scatter plot for removing it's polygon)")),
+            }
+
+        return self._getToolbarData((
+                                    ("sel_add", self.icons["sel_add"],
+                                     lambda event: self.scatt_mgr.ProcessSelectionPolygons('add')),
+                                     ("sel_remove", self.icons['sel_remove'],
+                                     lambda event: self.scatt_mgr.ProcessSelectionPolygons('remove')),
+                                     (None, ),
+                                     ("add_vertex", self.icons["editLine"],
+                                     lambda event: self.SetMode(event, 'add_vertex'),
+                                     wx.ITEM_CHECK),
+                                     ("add_boundary_vertex", self.icons['addVertex'],
+                                     lambda event: self.SetMode(event, 'add_boundary_vertex'),
+                                     wx.ITEM_CHECK),
+                                     ("move_vertex", self.icons["moveVertex"],
+                                     lambda event: self.SetMode(event, 'move_vertex'),
+                                     wx.ITEM_CHECK),
+                                     ('delete_vertex', self.icons['removeVertex'],
+                                     lambda event: self.SetMode(event, 'delete_vertex'),
+                                     wx.ITEM_CHECK),
+                                     ('remove_polygon', self.icons['delete'],
+                                     lambda event: self.SetMode(event, 'remove_polygon'),
+                                     wx.ITEM_CHECK)
+                                    ))
+
+    def SetMode(self, event, tool_name):
+        self.scatt_mgr.modeSet.disconnect(self.ModeSet)
+        if event.Checked() == True:
+            for i_tool_data in  self._data:
+                i_tool_name = i_tool_data[0]
+                if not i_tool_name:
+                    continue
+                if i_tool_name == tool_name:
+                    continue
+                i_tool_id = vars(self)[i_tool_name]
+                self.ToggleTool(i_tool_id, False)
+            self.scatt_mgr.SetPlotsMode(tool_name)
+        else:
+            self.scatt_mgr.SetPlotsMode(None)
+        self.scatt_mgr.modeSet.connect(self.ModeSet)
+
+    def ModeSet(self, mode):
+
+        if mode in ['zoom', 'pan', 'zoom_extend', None]:
+            self.UnsetMode()
+
+    def UnsetMode(self):
+        for i_tool_data in  self._data:
+                i_tool_name = i_tool_data[0]
+                if not i_tool_name:
+                    continue
+                i_tool_id = vars(self)[i_tool_name]
+                self.ToggleTool(i_tool_id, False)
+
+    def GetToolId(self, toolName):
+        return vars(self)[toolName]
+
+class CategoryToolbar(BaseToolbar):
+    """!Main toolbar
+    """
+    def __init__(self, parent, scatt_mgr, cats_list):
+        BaseToolbar.__init__(self, parent)
+        self.scatt_mgr = scatt_mgr
+        self.cats_mgr = self.scatt_mgr.GetCategoriesManager()
+        self.cats_list = cats_list
+
+        self.InitToolbar(self._toolbarData())
+        
+        # realize the toolbar
+        self.Realize()
+
+    def _toolbarData(self):
+        """!Toolbar data
+        """
+        self.icons = {
+            'add_class'     : MetaIcon(img = 'layer-add',
+                                       label = _('Add class')),
+            'remove_class'  : MetaIcon(img = 'layer-remove',
+                                       label = _('Remove selected class'))
+            }
+
+        return self._getToolbarData((
+                                    ("add_class", self.icons["add_class"],
+                                     lambda event: self.cats_mgr.AddCategory()),
+                                     ("remove_class", self.icons['remove_class'],
+                                     lambda event: self.cats_list.DeleteCategory()),
+                                    ))
\ No newline at end of file


Property changes on: grass/trunk/gui/wxpython/iscatt/toolbars.py
___________________________________________________________________
Added: svn:mime-type
   + text/x-python
Added: svn:eol-style
   + native



More information about the grass-commit mailing list