GRASS Programmer's Manual
6.4.2(2012)
|
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