[GRASS-SVN] r55827 - in grass/trunk/gui/wxpython: animation core

svn_grass at osgeo.org svn_grass at osgeo.org
Tue Apr 16 00:51:09 PDT 2013


Author: annakrat
Date: 2013-04-16 00:51:09 -0700 (Tue, 16 Apr 2013)
New Revision: 55827

Modified:
   grass/trunk/gui/wxpython/animation/controller.py
   grass/trunk/gui/wxpython/animation/frame.py
   grass/trunk/gui/wxpython/animation/mapwindow.py
   grass/trunk/gui/wxpython/core/gconsole.py
Log:
wxGUI/animation: apply patch by Soeren Gebbert (#1926 parallel rendering)

Modified: grass/trunk/gui/wxpython/animation/controller.py
===================================================================
--- grass/trunk/gui/wxpython/animation/controller.py	2013-04-16 07:36:07 UTC (rev 55826)
+++ grass/trunk/gui/wxpython/animation/controller.py	2013-04-16 07:51:09 UTC (rev 55827)
@@ -138,7 +138,6 @@
         win.DrawBitmap(bitmap, dataId)
         # self.frame.SetStatusText(dataId)
         self.slider.UpdateFrame(index)
-        self.UpdateReloadStatus()
 
     def SliderChanging(self, index):
         if self.runAfterReleasingSlider is None:
@@ -302,7 +301,6 @@
 
         self._updateSlider(timeLabels = timeLabels)
         self._updateAnimations(activeIndices = indices, mapNamesDict = mapNamesDict)
-        self._updateRegion()
         self._updateBitmapData()
         # if running:
         #     self.PauseAnimation(False)
@@ -310,8 +308,6 @@
         # else:
         self.EndAnimation()
 
-        self.UpdateReloadStatus()
-
     def _updateSlider(self, timeLabels = None):
         if self.temporalMode == TemporalMode.NONTEMPORAL:
             self.frame.SetSlider('nontemporal')
@@ -433,35 +429,12 @@
     def Reload(self):
         self.EndAnimation()
 
-        self._updateRegion()
         activeIndices = [anim.windowIndex for anim in self.animationData]
         for index in activeIndices:
             self.bitmapProviders[index].Load(force = True)
 
         self.EndAnimation()
 
-        self.UpdateReloadStatus()
-
-    def UpdateReloadStatus(self):
-        activeIndices = [anim.windowIndex for anim in self.animationData]
-        for i, win in enumerate(self.mapwindows):
-            if i in activeIndices and win.IsRescaled():
-                self.frame.SetStatusText(_("Reload recommended"), 1)
-                return
-        self.frame.SetStatusText('', 1)
-
-    def _updateRegion(self):
-        grassRegion = grass.region()
-        for anim in self.animationData:
-            if anim.viewMode == '2d':
-                self.mapwindows[anim.windowIndex].SetRegion(grassRegion)
-            else:
-                loadSize = self.bitmapProviders[anim.windowIndex].GetLoadSize() or \
-                           self.mapwindows[anim.windowIndex].GetClientSize()
-                region = {}
-                region['cols'], region['rows'] = loadSize
-                self.mapwindows[anim.windowIndex].SetRegion(region)
-
     def Export(self):
         if not self.animationData:
             GMessage(parent = self.frame, message = _("No animation to export."))

Modified: grass/trunk/gui/wxpython/animation/frame.py
===================================================================
--- grass/trunk/gui/wxpython/animation/frame.py	2013-04-16 07:36:07 UTC (rev 55826)
+++ grass/trunk/gui/wxpython/animation/frame.py	2013-04-16 07:51:09 UTC (rev 55827)
@@ -64,8 +64,9 @@
                                               providers = self.providers,
                                               bitmapPool = bitmapPool)
         for win, provider in zip(self.windows, self.providers):
-            win.Bind(wx.EVT_SIZE, lambda event, prov = provider,
-                     sizeMethod = win.GetClientSize: prov.WindowSizeChanged(event, sizeMethod))
+            win.Bind(wx.EVT_SIZE, lambda event, provider=provider,
+                     sizeMethod=win.GetClientSize: self.FrameSizeChanged(event, provider, sizeMethod))
+            provider.mapsLoaded.connect(lambda: self.SetStatusText(''))
 
         self.InitStatusbar()
         self._mgr = wx.aui.AuiManager(self)
@@ -88,8 +89,7 @@
 
     def InitStatusbar(self):
         """!Init statusbar."""
-        statusbar = self.CreateStatusBar(number = 2, style = 0)
-        statusbar.SetStatusWidths([-3, -2])
+        self.CreateStatusBar(number = 1, style = 0)
 
     def _addPanes(self):
         self._mgr.AddPane(self.animationPanel, wx.aui.AuiPaneInfo().CentrePane().
@@ -229,6 +229,13 @@
     def OnExportAnimation(self, event):
         self.controller.Export()
 
+    def FrameSizeChanged(self, event, provider, sizeMethod):
+        provider.WindowSizeChanged(*sizeMethod())
+        if self.animationPanel.shown:
+            self.SetStatusText(_("Window size has changed, rerender maps if needed"))
+        event.Skip()
+                     
+                     
     def OnHelp(self, event):
         RunCommand('g.manual',
                    quiet = True,

Modified: grass/trunk/gui/wxpython/animation/mapwindow.py
===================================================================
--- grass/trunk/gui/wxpython/animation/mapwindow.py	2013-04-16 07:36:07 UTC (rev 55826)
+++ grass/trunk/gui/wxpython/animation/mapwindow.py	2013-04-16 07:51:09 UTC (rev 55827)
@@ -18,12 +18,14 @@
 """
 import os
 import wx
-
+from multiprocessing import Process, Queue
+import tempfile
 import grass.script as grass
 from core.gcmd import RunCommand
 from core.debug import Debug
-from utils import ComputeScaledRect
 
+from grass.pydispatch.signal import Signal
+
 class BufferedWindow(wx.Window):
     """
     A Buffered window class (http://wiki.wxpython.org/DoubleBufferedDrawing).
@@ -106,11 +108,7 @@
         Debug.msg(2, "AnimationWindow.__init__()")
 
         self.bitmap = wx.EmptyBitmap(1, 1)
-        self.x = self.y = 0
         self.text = ''
-        self.size = wx.Size()
-        self.rescaleNeeded = False
-        self.region = None
         self.parent = parent
 
         BufferedWindow.__init__(self, parent = parent, id = id, style = style)
@@ -124,88 +122,46 @@
         Debug.msg(5, "AnimationWindow.Draw()")
 
         dc.Clear() # make sure you clear the bitmap!
-        dc.DrawBitmap(self.bitmap, x = self.x, y = self.y)
+        dc.DrawBitmap(self.bitmap, x=0, y=0)
         dc.DrawText(self.text, 0, 0)
 
     def OnSize(self, event):
         Debug.msg(5, "AnimationWindow.OnSize()")
-        self._computeBitmapCoordinates()
 
         self.DrawBitmap(self.bitmap, self.text)
         
         BufferedWindow.OnSize(self, event)
         if event:
             event.Skip()
-        
-    def IsRescaled(self):
-        return self.rescaleNeeded
 
-    def _rescaleIfNeeded(self, bitmap):
-        """!If the bitmap has different size than the window, rescale it."""
-        bW, bH = bitmap.GetSize()
-        wW, wH = self.size
-        if abs(bW - wW) > 5 and abs(bH - wH) > 5:
-            self.rescaleNeeded = True
-            im = wx.ImageFromBitmap(bitmap)
-            im.Rescale(*self.size)
-            bitmap = wx.BitmapFromImage(im)
-        else:
-            self.rescaleNeeded = False
-        return bitmap
-        
     def DrawBitmap(self, bitmap, text):
         """!Draws bitmap.
         Does not draw the bitmap if it is the same one as last time.
         """
-        bmp = self._rescaleIfNeeded(bitmap)
-        if self.bitmap == bmp:
+        if self.bitmap == bitmap:
             return
 
-        self.bitmap = bmp
+        self.bitmap = bitmap
         self.text = text
         self.UpdateDrawing()
 
-    def _computeBitmapCoordinates(self):
-        """!Computes where to place the bitmap
-        to be in the center of the window."""
-        if not self.region:
-            return
-
-        cols = self.region['cols']
-        rows = self.region['rows']
-        params = ComputeScaledRect((cols, rows), self.GetClientSize())
-        self.x = params['x']
-        self.y = params['y']
-        self.size = (params['width'], params['height'])
-
-    def SetRegion(self, region):
-        """!Sets region for size computations.
-        Region is set from outside to avoid calling g.region multiple times.
-        """
-        self.region = region
-        self._computeBitmapCoordinates()
-
-    def GetAdjustedSize(self):
-        return self.size
-
-    def GetAdjustedPosition(self):
-        return self.x, self.y
-
 class BitmapProvider(object):
     """!Class responsible for loading data and providing bitmaps"""
-    def __init__(self, frame, bitmapPool):
+    def __init__(self, frame, bitmapPool, imageWidth=640, imageHeight=480, nprocs=4):
 
         self.datasource = None
         self.dataNames = None
         self.dataType = None
-        self.region = None
         self.bitmapPool = bitmapPool
         self.frame = frame
-        self.size = wx.Size()
-        self.loadSize = wx.Size()
+        self.imageWidth = imageWidth # width of the image to render with d.rast or d.vect
+        self.imageHeight = imageHeight # height of the image to render with d.rast or d.vect
+        self.nprocs = nprocs # Number of procs to be used for rendering
 
         self.suffix = ''
         self.nvizRegion = None
+        
+        self.mapsLoaded = Signal('mapsLoaded')
 
     def GetDataNames(self):
         return self.dataNames
@@ -245,22 +201,17 @@
             bitmap = self.bitmapPool[None]
         return bitmap
 
-    def GetLoadSize(self):
-        return self.loadSize
-
-    def WindowSizeChanged(self, event, sizeMethod):
+    def WindowSizeChanged(self, width, height):
         """!Sets size when size of related window changes."""
-        # sizeMethod is GetClientSize, must be used instead of GetSize
-        self.size = sizeMethod()
-        event.Skip()
+        self.imageWidth, self.imageHeight = width, height
 
-    def _createNoDataBitmap(self, ncols, nrows):
+    def _createNoDataBitmap(self, width, height):
         """!Creates 'no data' bitmap.
 
         Used when requested bitmap is not available (loading data was not successful) or 
         we want to show 'no data' bitmap.
         """
-        bitmap = wx.EmptyBitmap(ncols, nrows)
+        bitmap = wx.EmptyBitmap(width, height)
         dc = wx.MemoryDC()
         dc.SelectObject(bitmap)
         dc.Clear()
@@ -268,7 +219,7 @@
         dc.SetFont(wx.Font(pointSize = 40, family = wx.FONTFAMILY_SCRIPT,
                            style = wx.FONTSTYLE_NORMAL, weight = wx.FONTWEIGHT_BOLD))
         tw, th = dc.GetTextExtent(text)
-        dc.DrawText(text, (ncols-tw)/2,  (nrows-th)/2)
+        dc.DrawText(text, (width-tw)/2,  (height-th)/2)
         dc.SelectObject(wx.NullBitmap)
         return bitmap
 
@@ -294,30 +245,22 @@
         else:
             updateFunction = None
 
-        if self.dataType == 'rast':
-            size, scale = self._computeScale()
-            # loading ...
-            self._loadRasters(rasters = self.datasource, names = self.dataNames,
-                             size = size, scale = scale, force = force, updateFunction = updateFunction)
+        if self.dataType == 'rast' or self.dataType == 'vect':
+            self._loadMaps(mapType=self.dataType, maps = self.datasource, names = self.dataNames,
+                             force = force, updateFunction = updateFunction)
         elif self.dataType == 'nviz':
             self._load3D(commands = self.datasource, region = self.nvizRegion, names = self.dataNames,
                          force = force, updateFunction = updateFunction)
         if progress:
             progress.Destroy()
 
+        self.mapsLoaded.emit()
+
     def Unload(self):
         self.datasource = None
         self.dataNames = None
         self.dataType = None
 
-    def _computeScale(self):
-        """!Computes parameters for creating bitmaps."""
-        region = grass.region()
-        ncols, nrows = region['cols'], region['rows']
-        params = ComputeScaledRect((ncols, nrows), self.size)
-
-        return ((params['width'], params['height']), params['scale'])
-
     def _dryLoad(self, rasters, names, force):
         """!Tries how many bitmaps will be loaded.
         Used for progress dialog.
@@ -336,54 +279,75 @@
 
         return count, maxLength
 
-    def _loadRasters(self, rasters, names, size, scale, force, updateFunction):
-        """!Loads rasters (also rasters from temporal dataset).
+    
+    def _loadMaps(self, mapType, maps, names, force, updateFunction):
+        """!Loads rasters/vectors (also from temporal dataset).
 
-        Uses r.out.ppm.
+        Uses d.rast/d.vect and multiprocessing for parallel rendering
 
-        @param rasters raster maps to be loaded
+        @param mapType Must be "rast" or "vect"
+        @param maps raster or vector maps to be loaded
         @param names names used as keys for bitmaps
-        @param size size of new bitmaps
-        @param scale used for adjustment of region resolution for r.out.ppm
         @param force load everything even though it is already there
         @param updateFunction function called for updating progress dialog
         """
-        region = grass.region()
-        for key in ('rows', 'cols', 'cells'):
-            region.pop(key)
-        # sometimes it renderes nonsense - depends on resolution
-        # should we set the resolution of the raster?
-        region['nsres'] /= scale
-        region['ewres'] /= scale
-        os.environ['GRASS_REGION'] = grass.region_env(**region)
-        ncols, nrows = size
-        self.loadSize = size
+
         count = 0
 
+        # Variables for parallel rendering
+        proc_count = 0
+        proc_list = []
+        queue_list = []
+        name_list = []
+
+        mapNum = len(maps)
+
         # create no data bitmap
         if None not in self.bitmapPool or force:
-            self.bitmapPool[None] = self._createNoDataBitmap(ncols, nrows)
-        for raster, name in zip(rasters, names):
+            self.bitmapPool[None] = self._createNoDataBitmap(self.imageWidth, self.imageHeight)
+
+        for mapname, name in zip(maps, names):
+            count += 1
+
             if name in self.bitmapPool and force is False:
                 continue
-            count += 1
-            # RunCommand has problem with DecodeString
-            returncode, stdout, messages = read2_command('r.out.ppm', input = raster,
-                                                         flags = 'h', output = '-', quiet = True)
-            if returncode != 0:
-                self.bitmapPool[name] = wx.EmptyBitmap(ncols, nrows)
-                continue
-                
-            bitmap = wx.BitmapFromBuffer(ncols, nrows, stdout)
-            self.bitmapPool[name] = bitmap
 
+            # Queue object for interprocess communication
+            q = Queue()
+            # The separate render process
+            p = Process(target=mapRenderProcess, args=(mapType, mapname, self.imageWidth, self.imageHeight, q))
+            p.start()
+
+            queue_list.append(q)
+            proc_list.append(p)
+            name_list.append(name)
+
+            proc_count += 1
+
+            # Wait for all running processes and read/store the created images
+            if proc_count == self.nprocs or count == mapNum:
+                for i in range(len(name_list)):
+                    proc_list[i].join()
+                    filename = queue_list[i].get()
+
+                    # Unfortunately the png files must be read here, 
+                    # since the swig wx objects can not be serialized by the Queue object :(
+                    if filename == None:
+                        self.bitmapPool[name_list[i]] = wx.EmptyBitmap(self.imageWidth, self.imageHeight)
+                    else:
+                        self.bitmapPool[name_list[i]] = wx.BitmapFromImage(wx.Image(filename))
+                        os.remove(filename)
+
+                proc_count = 0
+                proc_list = []
+                queue_list = []
+                name_list = []
+
             if updateFunction:
-                keepGoing, skip = updateFunction(count, raster)
+                keepGoing, skip = updateFunction(count, mapname)
                 if not keepGoing:
                     break
 
-        os.environ.pop('GRASS_REGION')
-
     def _load3D(self, commands, region, names, force, updateFunction):
         """!Load 3D view images using m.nviz.image.
 
@@ -393,8 +357,7 @@
         @param force load everything even though it is already there
         @param updateFunction function called for updating progress dialog
         """
-        ncols, nrows = self.size
-        self.loadSize = ncols, nrows
+        ncols, nrows = self.imageWidth, self.imageHeight
         count = 0
         format = 'ppm'
         tempFile = grass.tempfile(False)
@@ -419,7 +382,6 @@
             if returncode != 0:
                 self.bitmapPool[name] = wx.EmptyBitmap(ncols, nrows)
                 continue
-                
 
             self.bitmapPool[name] = wx.Bitmap(tempFileFormat)
 
@@ -430,6 +392,43 @@
         grass.try_remove(tempFileFormat)
         os.environ.pop('GRASS_REGION')
 
+def mapRenderProcess(mapType, mapname, width, height, fileQueue):
+    """!Render raster or vector files as png image and write the 
+       resulting png filename in the provided file queue
+    
+        @param mapType Must be "rast" or "vect"
+        @param mapname raster or vector map name to be rendered
+        @param width Width of the resulting image
+        @param height Height of the resulting image
+        @param fileQueue The inter process communication queue storing the file name of the image
+    """
+    
+    # temporary file, we use python here to avoid calling g.tempfile for each render process
+    fileHandler, filename = tempfile.mkstemp(suffix=".png")
+    os.close(fileHandler)
+    
+    # Set the environment variables for this process
+    os.environ['GRASS_WIDTH'] = str(width) 
+    os.environ['GRASS_HEIGHT'] = str(height)
+    os.environ['GRASS_RENDER_IMMEDIATE'] = "png"
+    os.environ['GRASS_TRUECOLOR'] = "1" 
+    os.environ['GRASS_TRANSPARENT'] = "1"
+    os.environ['GRASS_PNGFILE'] = str(filename)
+    
+    if mapType == "rast":
+        Debug.msg(1, "Render raster image " + str(filename))
+        returncode, stdout, messages = read2_command('d.rast', map = mapname)
+    else:
+        Debug.msg(1, "Render vector image " + str(filename))
+        returncode, stdout, messages = read2_command('d.vect', map = mapname)
+
+    if returncode != 0:
+        fileQueue.put(None)
+        os.remove(filename)
+        return
+
+    fileQueue.put(filename)
+    
 class BitmapPool():
     """!Class storing bitmaps (emulates dictionary)"""
     def __init__(self):
@@ -459,4 +458,4 @@
     kwargs['stderr'] = grass.PIPE
     ps = grass.start_command(*args, **kwargs)
     stdout, stderr = ps.communicate()
-    return ps.returncode, stdout, stderr
\ No newline at end of file
+    return ps.returncode, stdout, stderr

Modified: grass/trunk/gui/wxpython/core/gconsole.py
===================================================================
--- grass/trunk/gui/wxpython/core/gconsole.py	2013-04-16 07:36:07 UTC (rev 55826)
+++ grass/trunk/gui/wxpython/core/gconsole.py	2013-04-16 07:51:09 UTC (rev 55827)
@@ -229,6 +229,9 @@
         """
         self.receiver = receiver
 
+    def flush(self):
+        pass
+
     def write(self, s):
         if len(s) == 0 or s == '\n':
             return



More information about the grass-commit mailing list