[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