GRASS Programmer's Manual  6.4.2(2012)
histogram.py
Go to the documentation of this file.
00001 """!
00002 @package histogram.py
00003 
00004 Plotting histogram
00005 
00006 Classes:
00007  - BufferedWindow
00008  - HistFrame
00009 
00010 COPYRIGHT: (C) 2007, 2010-2011 by the GRASS Development Team
00011 This program is free software under the GNU General Public License
00012 (>=v2). Read the file COPYING that comes with GRASS for details.
00013 
00014 @author Michael Barton
00015 @author Various updates by Martin Landa <landa.martin gmail.com>
00016 """
00017 
00018 import os
00019 import sys
00020 
00021 import wx
00022 
00023 import render
00024 import menuform
00025 import disp_print
00026 import utils
00027 import gdialogs
00028 import globalvar
00029 from toolbars import HistogramToolbar
00030 from preferences import DefaultFontDialog
00031 from debug import Debug
00032 from icon import Icons
00033 from gcmd import GError
00034 
00035 class BufferedWindow(wx.Window):
00036     """!A Buffered window class.
00037 
00038     When the drawing needs to change, you app needs to call the
00039     UpdateHist() method. Since the drawing is stored in a bitmap, you
00040     can also save the drawing to file by calling the
00041     SaveToFile(self,file_name,file_type) method.
00042     """
00043     def __init__(self, parent, id =  wx.ID_ANY,
00044                  style = wx.NO_FULL_REPAINT_ON_RESIZE,
00045                  Map = None, **kwargs):
00046         
00047         wx.Window.__init__(self, parent, id = id, style = style, **kwargs)
00048         
00049         self.parent = parent
00050         self.Map = Map
00051         self.mapname = self.parent.mapname
00052         
00053         #
00054         # Flags
00055         #
00056         self.render = True  # re-render the map from GRASS or just redraw image
00057         self.resize = False # indicates whether or not a resize event has taken place
00058         self.dragimg = None # initialize variable for map panning
00059         self.pen = None     # pen for drawing zoom boxes, etc.
00060         
00061         #
00062         # Event bindings
00063         #
00064         self.Bind(wx.EVT_PAINT,        self.OnPaint)
00065         self.Bind(wx.EVT_SIZE,         self.OnSize)
00066         self.Bind(wx.EVT_IDLE,         self.OnIdle)
00067         
00068         #
00069         # Render output objects
00070         #
00071         self.mapfile = None # image file to be rendered
00072         self.img = ""       # wx.Image object (self.mapfile)
00073         
00074         self.imagedict = {} # images and their PseudoDC ID's for painting and dragging
00075         
00076         self.pdc = wx.PseudoDC()
00077         self._buffer = '' # will store an off screen empty bitmap for saving to file
00078         
00079         # make sure that extents are updated at init
00080         self.Map.region = self.Map.GetRegion()
00081         self.Map.SetRegion() 
00082         
00083         self.Bind(wx.EVT_ERASE_BACKGROUND, lambda x:None)
00084         
00085     def Draw(self, pdc, img = None, drawid = None, pdctype = 'image', coords = [0,0,0,0]):
00086         """!Draws histogram or clears window
00087         """
00088         if drawid == None:
00089             if pdctype == 'image' :
00090                 drawid = imagedict[img]
00091             elif pdctype == 'clear':
00092                 drawid == None
00093             else:
00094                 drawid = wx.NewId()
00095         else:
00096             pdc.SetId(drawid)
00097         
00098         pdc.BeginDrawing()
00099         
00100         Debug.msg (3, "BufferedWindow.Draw(): id=%s, pdctype=%s, coord=%s" % (drawid, pdctype, coords))
00101         
00102         if pdctype == 'clear': # erase the display
00103             bg = wx.WHITE_BRUSH
00104             pdc.SetBackground(bg)
00105             pdc.Clear()
00106             self.Refresh()
00107             pdc.EndDrawing()
00108             return
00109         
00110         if pdctype == 'image':
00111             bg = wx.TRANSPARENT_BRUSH
00112             pdc.SetBackground(bg)
00113             bitmap = wx.BitmapFromImage(img)
00114             w,h = bitmap.GetSize()
00115             pdc.DrawBitmap(bitmap, coords[0], coords[1], True) # draw the composite map
00116             pdc.SetIdBounds(drawid, (coords[0],coords[1],w,h))
00117         
00118         pdc.EndDrawing()
00119         self.Refresh()
00120         
00121     def OnPaint(self, event):
00122         """!Draw psuedo DC to buffer
00123         """
00124         dc = wx.BufferedPaintDC(self, self._buffer)
00125         
00126         # use PrepareDC to set position correctly
00127         self.PrepareDC(dc)
00128         # we need to clear the dc BEFORE calling PrepareDC
00129         bg = wx.Brush(self.GetBackgroundColour())
00130         dc.SetBackground(bg)
00131         dc.Clear()
00132         # create a clipping rect from our position and size
00133         # and the Update Region
00134         rgn = self.GetUpdateRegion()
00135         r = rgn.GetBox()
00136         # draw to the dc using the calculated clipping rect
00137         self.pdc.DrawToDCClipped(dc,r)
00138         
00139     def OnSize(self, event):
00140         """!Init image size to match window size
00141         """
00142         # set size of the input image
00143         self.Map.width, self.Map.height = self.GetClientSize()
00144         
00145         # Make new off screen bitmap: this bitmap will always have the
00146         # current drawing in it, so it can be used to save the image to
00147         # a file, or whatever.
00148         self._buffer = wx.EmptyBitmap(self.Map.width, self.Map.height)
00149         
00150         # get the image to be rendered
00151         self.img = self.GetImage()
00152         
00153         # update map display
00154         if self.img and self.Map.width + self.Map.height > 0: # scale image during resize
00155             self.img = self.img.Scale(self.Map.width, self.Map.height)
00156             self.render = False
00157             self.UpdateHist()
00158         
00159         # re-render image on idle
00160         self.resize = True
00161         
00162     def OnIdle(self, event):
00163         """!Only re-render a histogram image from GRASS during idle
00164         time instead of multiple times during resizing.
00165         """
00166         if self.resize:
00167             self.render = True
00168             self.UpdateHist()
00169         event.Skip()
00170         
00171     def SaveToFile(self, FileName, FileType, width, height):
00172         """!This will save the contents of the buffer to the specified
00173         file. See the wx.Windows docs for wx.Bitmap::SaveFile for the
00174         details
00175         """
00176         busy = wx.BusyInfo(message=_("Please wait, exporting image..."),
00177                            parent=self)
00178         wx.Yield()
00179         
00180         self.Map.ChangeMapSize((width, height))
00181         ibuffer = wx.EmptyBitmap(max(1, width), max(1, height))
00182         self.Map.Render(force=True, windres = True)
00183         img = self.GetImage()
00184         self.Draw(self.pdc, img, drawid = 99)
00185         dc = wx.BufferedPaintDC(self, ibuffer)
00186         dc.Clear()
00187         self.PrepareDC(dc)
00188         self.pdc.DrawToDC(dc)
00189         ibuffer.SaveFile(FileName, FileType)
00190         
00191         busy.Destroy()
00192         
00193     def GetImage(self):
00194         """!Converts files to wx.Image
00195         """
00196         if self.Map.mapfile and os.path.isfile(self.Map.mapfile) and \
00197                 os.path.getsize(self.Map.mapfile):
00198             img = wx.Image(self.Map.mapfile, wx.BITMAP_TYPE_ANY)
00199         else:
00200             img = None
00201         
00202         self.imagedict[img] = 99 # set image PeudoDC ID
00203         return img
00204     
00205     def UpdateHist(self, img = None):
00206         """!Update canvas if histogram options changes or window
00207         changes geometry
00208         """
00209         Debug.msg (2, "BufferedWindow.UpdateHist(%s): render=%s" % (img, self.render))
00210         oldfont = ""
00211         oldencoding = ""
00212         
00213         if self.render:
00214             # render new map images
00215             # set default font and encoding environmental variables
00216             if "GRASS_FONT" in os.environ:
00217                 oldfont = os.environ["GRASS_FONT"]
00218             if self.parent.font != "": os.environ["GRASS_FONT"] = self.parent.font
00219             if "GRASS_ENCODING" in os.environ:
00220                 oldencoding = os.environ["GRASS_ENCODING"]
00221             if self.parent.encoding != None and self.parent.encoding != "ISO-8859-1":
00222                 os.environ[GRASS_ENCODING] = self.parent.encoding
00223             
00224             # using active comp region
00225             self.Map.GetRegion(update = True)
00226             
00227             self.Map.width, self.Map.height = self.GetClientSize()
00228             self.mapfile = self.Map.Render(force = self.render)
00229             self.img = self.GetImage()
00230             self.resize = False
00231         
00232         if not self.img: return
00233         try:
00234             id = self.imagedict[self.img]
00235         except:
00236             return
00237         
00238         # paint images to PseudoDC
00239         self.pdc.Clear()
00240         self.pdc.RemoveAll()
00241         self.Draw(self.pdc, self.img, drawid = id) # draw map image background
00242         
00243         self.resize = False
00244         
00245         # update statusbar
00246         # Debug.msg (3, "BufferedWindow.UpdateHist(%s): region=%s" % self.Map.region)
00247         self.Map.SetRegion()
00248         self.parent.statusbar.SetStatusText("Image/Raster map <%s>" % self.parent.mapname)
00249         
00250         # set default font and encoding environmental variables
00251         if oldfont != "":
00252             os.environ["GRASS_FONT"] = oldfont
00253         if oldencoding != "":
00254             os.environ["GRASS_ENCODING"] = oldencoding
00255         
00256     def EraseMap(self):
00257         """!Erase the map display
00258         """
00259         self.Draw(self.pdc, pdctype = 'clear')
00260         
00261 class HistFrame(wx.Frame):
00262     """!Main frame for hisgram display window. Uses d.histogram
00263     rendered onto canvas
00264     """
00265     def __init__(self, parent = None, id = wx.ID_ANY,
00266                  title = _("GRASS GIS Histogram of raster map"),
00267                  style = wx.DEFAULT_FRAME_STYLE, **kwargs):
00268         wx.Frame.__init__(self, parent, id, title, style = style, **kwargs)
00269         self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
00270         
00271         self.Map   = render.Map()  # instance of render.Map to be associated with display
00272         self.layer = None          # reference to layer with histogram
00273         
00274         # Init variables
00275         self.params = {}  # previously set histogram parameters
00276         self.propwin = '' # ID of properties dialog
00277         
00278         self.font = ""
00279         self.encoding = 'ISO-8859-1' # default encoding for display fonts
00280         
00281         self.toolbar = HistogramToolbar(parent = self)
00282         self.SetToolBar(self.toolbar)
00283 
00284         # Add statusbar
00285         self.mapname = ''
00286         self.statusbar = self.CreateStatusBar(number = 1, style = 0)
00287         # self.statusbar.SetStatusWidths([-2, -1])
00288         hist_frame_statusbar_fields = ["Histogramming %s" % self.mapname]
00289         for i in range(len(hist_frame_statusbar_fields)):
00290             self.statusbar.SetStatusText(hist_frame_statusbar_fields[i], i)
00291         
00292         # Init map display
00293         self.InitDisplay() # initialize region values
00294         
00295         # initialize buffered DC
00296         self.HistWindow = BufferedWindow(self, id = wx.ID_ANY, Map = self.Map) # initialize buffered DC
00297         
00298         # Bind various events
00299         self.Bind(wx.EVT_CLOSE,    self.OnCloseWindow)
00300         
00301         # Init print module and classes
00302         self.printopt = disp_print.PrintOptions(self, self.HistWindow)
00303         
00304         # Add layer to the map
00305         self.layer = self.Map.AddLayer(type = "command", name = 'histogram', command = ['d.histogram'],
00306                                        l_active = False, l_hidden = False, l_opacity = 1, l_render = False)
00307         
00308     def InitDisplay(self):
00309         """!Initialize histogram display, set dimensions and region
00310         """
00311         self.width, self.height = self.GetClientSize()
00312         self.Map.geom = self.width, self.height
00313         
00314     def OnOptions(self, event):
00315         """!Change histogram settings"""
00316         cmd = ['d.histogram']
00317         if self.mapname != '':
00318             cmd.append('map=%s' % self.mapname)
00319         
00320         menuform.GUI(parent = self).ParseCommand(cmd,
00321                                                  completed = (self.GetOptData, None, self.params))
00322         
00323     def GetOptData(self, dcmd, layer, params, propwin):
00324         """!Callback method for histogram command generated by dialog
00325         created in menuform.py
00326         """
00327         if dcmd:
00328             name, found = utils.GetLayerNameFromCmd(dcmd, fullyQualified = True,
00329                                                     layerType = 'raster')
00330             if not found:
00331                 GError(parent = propwin,
00332                        message = _("Raster map <%s> not found") % name)
00333                 return
00334             
00335             self.SetHistLayer(name)
00336         self.params = params
00337         self.propwin = propwin
00338         
00339         self.HistWindow.UpdateHist()
00340         
00341     def SetHistLayer(self, name):
00342         """!Set histogram layer
00343         """
00344         self.mapname = name
00345         
00346         self.layer = self.Map.ChangeLayer(layer = self.layer,
00347                                           command = [['d.histogram', 'map=%s' % self.mapname],],
00348                                           active = True)
00349         
00350         return self.layer
00351 
00352     def SetHistFont(self, event):
00353         """!Set font for histogram. If not set, font will be default
00354         display font.
00355         """
00356         dlg = DefaultFontDialog(parent = self, id = wx.ID_ANY,
00357                                 title = _('Select font for histogram text'))        
00358         dlg.fontlb.SetStringSelection(self.font, True)
00359         
00360         if dlg.ShowModal() == wx.ID_CANCEL:
00361             dlg.Destroy()
00362             return
00363         
00364         # set default font type, font, and encoding to whatever selected in dialog
00365         if dlg.font != None:
00366             self.font = dlg.font
00367         if dlg.encoding != None:
00368             self.encoding = dlg.encoding
00369         
00370         dlg.Destroy()
00371         self.HistWindow.UpdateHist()
00372 
00373     def OnErase(self, event):
00374         """!Erase the histogram display
00375         """
00376         self.HistWindow.Draw(self.HistWindow.pdc, pdctype = 'clear')
00377         
00378     def OnRender(self, event):
00379         """!Re-render histogram
00380         """
00381         self.HistWindow.UpdateHist()
00382         
00383     def GetWindow(self):
00384         """!Get buffered window"""
00385         return self.HistWindow
00386     
00387     def SaveToFile(self, event):
00388         """!Save to file
00389         """
00390         filetype, ltype = gdialogs.GetImageHandlers(self.HistWindow.img)
00391         
00392         # get size
00393         dlg = gdialogs.ImageSizeDialog(self)
00394         dlg.CentreOnParent()
00395         if dlg.ShowModal() != wx.ID_OK:
00396             dlg.Destroy()
00397             return
00398         width, height = dlg.GetValues()
00399         dlg.Destroy()
00400         
00401         # get filename
00402         dlg = wx.FileDialog(parent = self,
00403                             message = _("Choose a file name to save the image "
00404                                         "(no need to add extension)"),
00405                             wildcard = filetype,
00406                             style=wx.SAVE | wx.FD_OVERWRITE_PROMPT)
00407         
00408         if dlg.ShowModal() == wx.ID_OK:
00409             path = dlg.GetPath()
00410             if not path:
00411                 dlg.Destroy()
00412                 return
00413             
00414             base, ext = os.path.splitext(path)
00415             fileType = ltype[dlg.GetFilterIndex()]['type']
00416             extType  = ltype[dlg.GetFilterIndex()]['ext']
00417             if ext != extType:
00418                 path = base + '.' + extType
00419             
00420             self.HistWindow.SaveToFile(path, fileType,
00421                                        width, height)
00422         
00423         self.HistWindow.UpdateHist()
00424         dlg.Destroy()
00425         
00426     def PrintMenu(self, event):
00427         """!Print options and output menu
00428         """
00429         point = wx.GetMousePosition()
00430         printmenu = wx.Menu()
00431         # Add items to the menu
00432         setup = wx.MenuItem(printmenu, id = wx.ID_ANY, text = _('Page setup'))
00433         printmenu.AppendItem(setup)
00434         self.Bind(wx.EVT_MENU, self.printopt.OnPageSetup, setup)
00435         
00436         preview = wx.MenuItem(printmenu, id = wx.ID_ANY, text = _('Print preview'))
00437         printmenu.AppendItem(preview)
00438         self.Bind(wx.EVT_MENU, self.printopt.OnPrintPreview, preview)
00439         
00440         doprint = wx.MenuItem(printmenu, id = wx.ID_ANY, text = _('Print display'))
00441         printmenu.AppendItem(doprint)
00442         self.Bind(wx.EVT_MENU, self.printopt.OnDoPrint, doprint)
00443         
00444         # Popup the menu.  If an item is selected then its handler
00445         # will be called before PopupMenu returns.
00446         self.PopupMenu(printmenu)
00447         printmenu.Destroy()
00448         
00449     def OnQuit(self, event):
00450         self.Close(True)
00451         
00452     def OnCloseWindow(self, event):
00453         """!Window closed
00454         Also remove associated rendered images
00455         """
00456         try:
00457             self.propwin.Close(True)
00458         except:
00459             pass
00460         self.Map.Clean()
00461         self.Destroy()
00462         
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines