GRASS Programmer's Manual  6.4.3(2013)-r
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Macros Pages
histogram.py
Go to the documentation of this file.
1 """!
2 @package modules.histogram
3 
4 Plotting histogram based on d.histogram
5 
6 Classes:
7  - histogram::BufferedWindow
8  - histogram::HistogramFrame
9  - histogram::HistogramToolbar
10 
11 (C) 2007, 2010-2011 by the GRASS Development Team
12 
13 This program is free software under the GNU General Public License
14 (>=v2). Read the file COPYING that comes with GRASS for details.
15 
16 @author Michael Barton
17 @author Various updates by Martin Landa <landa.martin gmail.com>
18 """
19 
20 import os
21 
22 import wx
23 
24 from core import globalvar
25 from core.render import Map
26 from gui_core.forms import GUI
27 from mapdisp.gprint import PrintOptions
28 from core.utils import GetLayerNameFromCmd
29 from gui_core.dialogs import GetImageHandlers, ImageSizeDialog
30 from gui_core.preferences import DefaultFontDialog
31 from core.debug import Debug
32 from core.gcmd import GError
33 from gui_core.toolbars import BaseToolbar, BaseIcons
34 
35 class BufferedWindow(wx.Window):
36  """!A Buffered window class.
37 
38  When the drawing needs to change, you app needs to call the
39  UpdateHist() method. Since the drawing is stored in a bitmap, you
40  can also save the drawing to file by calling the
41  SaveToFile(self,file_name,file_type) method.
42  """
43  def __init__(self, parent, id = wx.ID_ANY,
44  style = wx.NO_FULL_REPAINT_ON_RESIZE,
45  Map = None, **kwargs):
46 
47  wx.Window.__init__(self, parent, id = id, style = style, **kwargs)
48 
49  self.parent = parent
50  self.Map = Map
51  self.mapname = self.parent.mapname
52 
53  #
54  # Flags
55  #
56  self.render = True # re-render the map from GRASS or just redraw image
57  self.resize = False # indicates whether or not a resize event has taken place
58  self.dragimg = None # initialize variable for map panning
59  self.pen = None # pen for drawing zoom boxes, etc.
60 
61  #
62  # Event bindings
63  #
64  self.Bind(wx.EVT_PAINT, self.OnPaint)
65  self.Bind(wx.EVT_SIZE, self.OnSize)
66  self.Bind(wx.EVT_IDLE, self.OnIdle)
67 
68  #
69  # Render output objects
70  #
71  self.mapfile = None # image file to be rendered
72  self.img = "" # wx.Image object (self.mapfile)
73 
74  self.imagedict = {} # images and their PseudoDC ID's for painting and dragging
75 
76  self.pdc = wx.PseudoDC()
77  self._buffer = '' # will store an off screen empty bitmap for saving to file
78 
79  # make sure that extents are updated at init
80  self.Map.region = self.Map.GetRegion()
81  self.Map.SetRegion()
82 
83  self.Bind(wx.EVT_ERASE_BACKGROUND, lambda x:None)
84 
85  def Draw(self, pdc, img = None, drawid = None, pdctype = 'image', coords = [0,0,0,0]):
86  """!Draws histogram or clears window
87  """
88  if drawid == None:
89  if pdctype == 'image' :
90  drawid = imagedict[img]
91  elif pdctype == 'clear':
92  drawid == None
93  else:
94  drawid = wx.NewId()
95  else:
96  pdc.SetId(drawid)
97 
98  pdc.BeginDrawing()
99 
100  Debug.msg (3, "BufferedWindow.Draw(): id=%s, pdctype=%s, coord=%s" % (drawid, pdctype, coords))
101 
102  if pdctype == 'clear': # erase the display
103  bg = wx.WHITE_BRUSH
104  pdc.SetBackground(bg)
105  pdc.Clear()
106  self.Refresh()
107  pdc.EndDrawing()
108  return
109 
110  if pdctype == 'image':
111  bg = wx.TRANSPARENT_BRUSH
112  pdc.SetBackground(bg)
113  bitmap = wx.BitmapFromImage(img)
114  w,h = bitmap.GetSize()
115  pdc.DrawBitmap(bitmap, coords[0], coords[1], True) # draw the composite map
116  pdc.SetIdBounds(drawid, (coords[0],coords[1],w,h))
117 
118  pdc.EndDrawing()
119  self.Refresh()
120 
121  def OnPaint(self, event):
122  """!Draw psuedo DC to buffer
123  """
124  dc = wx.BufferedPaintDC(self, self._buffer)
125 
126  # use PrepareDC to set position correctly
127  self.PrepareDC(dc)
128  # we need to clear the dc BEFORE calling PrepareDC
129  bg = wx.Brush(self.GetBackgroundColour())
130  dc.SetBackground(bg)
131  dc.Clear()
132  # create a clipping rect from our position and size
133  # and the Update Region
134  rgn = self.GetUpdateRegion()
135  r = rgn.GetBox()
136  # draw to the dc using the calculated clipping rect
137  self.pdc.DrawToDCClipped(dc,r)
138 
139  def OnSize(self, event):
140  """!Init image size to match window size
141  """
142  # set size of the input image
143  self.Map.width, self.Map.height = self.GetClientSize()
144 
145  # Make new off screen bitmap: this bitmap will always have the
146  # current drawing in it, so it can be used to save the image to
147  # a file, or whatever.
148  self._buffer = wx.EmptyBitmap(self.Map.width, self.Map.height)
149 
150  # get the image to be rendered
151  self.img = self.GetImage()
152 
153  # update map display
154  if self.img and self.Map.width + self.Map.height > 0: # scale image during resize
155  self.img = self.img.Scale(self.Map.width, self.Map.height)
156  self.render = False
157  self.UpdateHist()
158 
159  # re-render image on idle
160  self.resize = True
161 
162  def OnIdle(self, event):
163  """!Only re-render a histogram image from GRASS during idle
164  time instead of multiple times during resizing.
165  """
166  if self.resize:
167  self.render = True
168  self.UpdateHist()
169  event.Skip()
170 
171  def SaveToFile(self, FileName, FileType, width, height):
172  """!This will save the contents of the buffer to the specified
173  file. See the wx.Windows docs for wx.Bitmap::SaveFile for the
174  details
175  """
176  busy = wx.BusyInfo(message=_("Please wait, exporting image..."),
177  parent=self)
178  wx.Yield()
179 
180  self.Map.ChangeMapSize((width, height))
181  ibuffer = wx.EmptyBitmap(max(1, width), max(1, height))
182  self.Map.Render(force=True, windres = True)
183  img = self.GetImage()
184  self.Draw(self.pdc, img, drawid = 99)
185  dc = wx.BufferedPaintDC(self, ibuffer)
186  dc.Clear()
187  self.PrepareDC(dc)
188  self.pdc.DrawToDC(dc)
189  ibuffer.SaveFile(FileName, FileType)
190 
191  busy.Destroy()
192 
193  def GetImage(self):
194  """!Converts files to wx.Image
195  """
196  if self.Map.mapfile and os.path.isfile(self.Map.mapfile) and \
197  os.path.getsize(self.Map.mapfile):
198  img = wx.Image(self.Map.mapfile, wx.BITMAP_TYPE_ANY)
199  else:
200  img = None
201 
202  self.imagedict[img] = 99 # set image PeudoDC ID
203  return img
204 
205  def UpdateHist(self, img = None):
206  """!Update canvas if histogram options changes or window
207  changes geometry
208  """
209  Debug.msg (2, "BufferedWindow.UpdateHist(%s): render=%s" % (img, self.render))
210  oldfont = ""
211  oldencoding = ""
212 
213  if self.render:
214  # render new map images
215  # set default font and encoding environmental variables
216  if "GRASS_FONT" in os.environ:
217  oldfont = os.environ["GRASS_FONT"]
218  if self.parent.font != "": os.environ["GRASS_FONT"] = self.parent.font
219  if "GRASS_ENCODING" in os.environ:
220  oldencoding = os.environ["GRASS_ENCODING"]
221  if self.parent.encoding != None and self.parent.encoding != "ISO-8859-1":
222  os.environ[GRASS_ENCODING] = self.parent.encoding
223 
224  # using active comp region
225  self.Map.GetRegion(update = True)
226 
227  self.Map.width, self.Map.height = self.GetClientSize()
228  self.mapfile = self.Map.Render(force = self.render)
229  self.img = self.GetImage()
230  self.resize = False
231 
232  if not self.img: return
233  try:
234  id = self.imagedict[self.img]
235  except:
236  return
237 
238  # paint images to PseudoDC
239  self.pdc.Clear()
240  self.pdc.RemoveAll()
241  self.Draw(self.pdc, self.img, drawid = id) # draw map image background
242 
243  self.resize = False
244 
245  # update statusbar
246  # Debug.msg (3, "BufferedWindow.UpdateHist(%s): region=%s" % self.Map.region)
247  self.Map.SetRegion()
248  self.parent.statusbar.SetStatusText("Image/Raster map <%s>" % self.parent.mapname)
249 
250  # set default font and encoding environmental variables
251  if oldfont != "":
252  os.environ["GRASS_FONT"] = oldfont
253  if oldencoding != "":
254  os.environ["GRASS_ENCODING"] = oldencoding
255 
256  def EraseMap(self):
257  """!Erase the map display
258  """
259  self.Draw(self.pdc, pdctype = 'clear')
260 
261 class HistogramFrame(wx.Frame):
262  """!Main frame for hisgram display window. Uses d.histogram
263  rendered onto canvas
264  """
265  def __init__(self, parent = None, id = wx.ID_ANY,
266  title = _("GRASS GIS Histogramming Tool (d.histogram)"),
267  size = wx.Size(500, 350),
268  style = wx.DEFAULT_FRAME_STYLE, **kwargs):
269  wx.Frame.__init__(self, parent, id, title, size = size, style = style, **kwargs)
270  self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
271 
272  self.Map = Map() # instance of render.Map to be associated with display
273  self.layer = None # reference to layer with histogram
274 
275  # Init variables
276  self.params = {} # previously set histogram parameters
277  self.propwin = '' # ID of properties dialog
278 
279  self.font = ""
280  self.encoding = 'ISO-8859-1' # default encoding for display fonts
281 
282  self.toolbar = HistogramToolbar(parent = self)
283  self.SetToolBar(self.toolbar)
284 
285  # find selected map
286  self.mapname = None
287  if parent.GetName() == "MapWindow" and not parent.IsStandalone():
288  tree = parent.GetLayerManager().GetLayerTree()
289 
290  if tree.layer_selected and tree.GetPyData(tree.layer_selected)[0]['type'] == 'raster':
291  self.mapname = tree.GetPyData(tree.layer_selected)[0]['maplayer'].name
292 
293  # Add statusbar
294  self.statusbar = self.CreateStatusBar(number = 1, style = 0)
295  # self.statusbar.SetStatusWidths([-2, -1])
296  hist_frame_statusbar_fields = ["Histogramming %s" % self.mapname]
297  for i in range(len(hist_frame_statusbar_fields)):
298  self.statusbar.SetStatusText(hist_frame_statusbar_fields[i], i)
299 
300  # Init map display
301  self.InitDisplay() # initialize region values
302 
303  # initialize buffered DC
304  self.HistWindow = BufferedWindow(self, id = wx.ID_ANY, Map = self.Map) # initialize buffered DC
305 
306  # Bind various events
307  self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
308 
309  # Init print module and classes
310  self.printopt = PrintOptions(self, self.HistWindow)
311 
312  # Add layer to the map
313  self.layer = self.Map.AddLayer(type = "command", name = 'histogram', command = [['d.histogram']],
314  l_active = False, l_hidden = False, l_opacity = 1, l_render = False)
315  if self.mapname:
316  self.SetHistLayer(self.mapname, None)
317  else:
318  self.OnErase(None)
319 
320  def InitDisplay(self):
321  """!Initialize histogram display, set dimensions and region
322  """
323  self.width, self.height = self.GetClientSize()
324  self.Map.geom = self.width, self.height
325 
326  def OnOptions(self, event):
327  """!Change histogram settings"""
328  cmd = ['d.histogram']
329  if self.mapname != '':
330  cmd.append('map=%s' % self.mapname)
331  module = GUI(parent = self)
332  module.ParseCommand(cmd, completed = (self.GetOptData, None, self.params))
333 
334  def GetOptData(self, dcmd, layer, params, propwin):
335  """!Callback method for histogram command generated by dialog
336  created in menuform.py
337  """
338  if dcmd:
339  name, found = GetLayerNameFromCmd(dcmd, fullyQualified = True,
340  layerType = 'raster')
341  if not found:
342  GError(parent = propwin,
343  message = _("Raster map <%s> not found") % name)
344  return
345 
346  self.SetHistLayer(name, dcmd)
347  self.params = params
348  self.propwin = propwin
349  self.HistWindow.UpdateHist()
350 
351  def SetHistLayer(self, name, cmd = None):
352  """!Set histogram layer
353  """
354  self.mapname = name
355  if not cmd:
356  cmd = ['d.histogram',('map=%s' % self.mapname)]
357  self.layer = self.Map.ChangeLayer(layer = self.layer,
358  command = [cmd],
359  active = True)
360 
361  return self.layer
362 
363  def SetHistFont(self, event):
364  """!Set font for histogram. If not set, font will be default
365  display font.
366  """
367  dlg = DefaultFontDialog(parent = self, id = wx.ID_ANY,
368  title = _('Select font for histogram text'))
369  dlg.fontlb.SetStringSelection(self.font, True)
370 
371  if dlg.ShowModal() == wx.ID_CANCEL:
372  dlg.Destroy()
373  return
374 
375  # set default font type, font, and encoding to whatever selected in dialog
376  if dlg.font != None:
377  self.font = dlg.font
378  if dlg.encoding != None:
379  self.encoding = dlg.encoding
380 
381  dlg.Destroy()
382  self.HistWindow.UpdateHist()
383 
384  def OnErase(self, event):
385  """!Erase the histogram display
386  """
387  self.HistWindow.Draw(self.HistWindow.pdc, pdctype = 'clear')
388 
389  def OnRender(self, event):
390  """!Re-render histogram
391  """
392  self.HistWindow.UpdateHist()
393 
394  def GetWindow(self):
395  """!Get buffered window"""
396  return self.HistWindow
397 
398  def SaveToFile(self, event):
399  """!Save to file
400  """
401  filetype, ltype = GetImageHandlers(self.HistWindow.img)
402 
403  # get size
404  dlg = ImageSizeDialog(self)
405  dlg.CentreOnParent()
406  if dlg.ShowModal() != wx.ID_OK:
407  dlg.Destroy()
408  return
409  width, height = dlg.GetValues()
410  dlg.Destroy()
411 
412  # get filename
413  dlg = wx.FileDialog(parent = self,
414  message = _("Choose a file name to save the image "
415  "(no need to add extension)"),
416  wildcard = filetype,
417  style=wx.SAVE | wx.FD_OVERWRITE_PROMPT)
418 
419  if dlg.ShowModal() == wx.ID_OK:
420  path = dlg.GetPath()
421  if not path:
422  dlg.Destroy()
423  return
424 
425  base, ext = os.path.splitext(path)
426  fileType = ltype[dlg.GetFilterIndex()]['type']
427  extType = ltype[dlg.GetFilterIndex()]['ext']
428  if ext != extType:
429  path = base + '.' + extType
430 
431  self.HistWindow.SaveToFile(path, fileType,
432  width, height)
433 
434  self.HistWindow.UpdateHist()
435  dlg.Destroy()
436 
437  def PrintMenu(self, event):
438  """!Print options and output menu
439  """
440  point = wx.GetMousePosition()
441  printmenu = wx.Menu()
442  # Add items to the menu
443  setup = wx.MenuItem(printmenu, id = wx.ID_ANY, text = _('Page setup'))
444  printmenu.AppendItem(setup)
445  self.Bind(wx.EVT_MENU, self.printopt.OnPageSetup, setup)
446 
447  preview = wx.MenuItem(printmenu, id = wx.ID_ANY, text = _('Print preview'))
448  printmenu.AppendItem(preview)
449  self.Bind(wx.EVT_MENU, self.printopt.OnPrintPreview, preview)
450 
451  doprint = wx.MenuItem(printmenu, id = wx.ID_ANY, text = _('Print display'))
452  printmenu.AppendItem(doprint)
453  self.Bind(wx.EVT_MENU, self.printopt.OnDoPrint, doprint)
454 
455  # Popup the menu. If an item is selected then its handler
456  # will be called before PopupMenu returns.
457  self.PopupMenu(printmenu)
458  printmenu.Destroy()
459 
460  def OnQuit(self, event):
461  self.Close(True)
462 
463  def OnCloseWindow(self, event):
464  """!Window closed
465  Also remove associated rendered images
466  """
467  try:
468  self.propwin.Close(True)
469  except:
470  pass
471  self.Map.Clean()
472  self.Destroy()
473 
474 class HistogramToolbar(BaseToolbar):
475  """!Histogram toolbar (see histogram.py)
476  """
477  def __init__(self, parent):
478  BaseToolbar.__init__(self, parent)
479 
480  self.InitToolbar(self._toolbarData())
481 
482  # realize the toolbar
483  self.Realize()
484 
485  def _toolbarData(self):
486  """!Toolbar data"""
487  return self._getToolbarData((('histogram', BaseIcons["histogramD"],
488  self.parent.OnOptions),
489  ('render', BaseIcons["display"],
490  self.parent.OnRender),
491  ('erase', BaseIcons["erase"],
492  self.parent.OnErase),
493  ('font', BaseIcons["font"],
494  self.parent.SetHistFont),
495  (None, ),
496  ('save', BaseIcons["saveFile"],
497  self.parent.SaveToFile),
498  ('hprint', BaseIcons["print"],
499  self.parent.PrintMenu),
500  (None, ),
501  ('quit', BaseIcons["quit"],
502  self.parent.OnQuit))
503  )
def _toolbarData
Toolbar data.
Definition: histogram.py:485
wxGUI command interface
def GetOptData
Callback method for histogram command generated by dialog created in menuform.py. ...
Definition: histogram.py:334
wxGUI debugging
def OnPaint
Draw psuedo DC to buffer.
Definition: histogram.py:121
A Buffered window class.
Definition: histogram.py:35
def OnSize
Init image size to match window size.
Definition: histogram.py:139
def SetHistLayer
Set histogram layer.
Definition: histogram.py:351
def EraseMap
Erase the map display.
Definition: histogram.py:256
Histogram toolbar (see histogram.py)
Definition: histogram.py:474
def GetWindow
Get buffered window.
Definition: histogram.py:394
def PrintMenu
Print options and output menu.
Definition: histogram.py:437
def Draw
Draws histogram or clears window.
Definition: histogram.py:85
Main frame for hisgram display window.
Definition: histogram.py:261
#define max(x, y)
Definition: draw2.c:69
Various dialogs used in wxGUI.
Print context and utility functions for printing contents of map display window.
Rendering map layers and overlays into map composition image.
def OnCloseWindow
Window closed Also remove associated rendered images.
Definition: histogram.py:463
def SetHistFont
Set font for histogram.
Definition: histogram.py:363
def OnRender
Re-render histogram.
Definition: histogram.py:389
def OnIdle
Only re-render a histogram image from GRASS during idle time instead of multiple times during resizin...
Definition: histogram.py:162
def GetLayerNameFromCmd
Get map name from GRASS command.
Definition: core/utils.py:73
def GetImageHandlers
Get list of supported image handlers.
Base classes toolbar widgets.
Misc utilities for wxGUI.
def InitDisplay
Initialize histogram display, set dimensions and region.
Definition: histogram.py:320
User preferences dialog.
def SaveToFile
This will save the contents of the buffer to the specified file.
Definition: histogram.py:171
def SaveToFile
Save to file.
Definition: histogram.py:398
def OnErase
Erase the histogram display.
Definition: histogram.py:384
tuple range
Definition: tools.py:1402
def GetImage
Converts files to wx.Image.
Definition: histogram.py:193
def OnOptions
Change histogram settings.
Definition: histogram.py:326
def UpdateHist
Update canvas if histogram options changes or window changes geometry.
Definition: histogram.py:205