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  )