GRASS Programmer's Manual  6.4.2(2012)
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
histogram.py
Go to the documentation of this file.
1 """!
2 @package histogram.py
3 
4 Plotting histogram
5 
6 Classes:
7  - BufferedWindow
8  - HistFrame
9 
10 COPYRIGHT: (C) 2007, 2010-2011 by the GRASS Development Team
11 This program is free software under the GNU General Public License
12 (>=v2). Read the file COPYING that comes with GRASS for details.
13 
14 @author Michael Barton
15 @author Various updates by Martin Landa <landa.martin gmail.com>
16 """
17 
18 import os
19 import sys
20 
21 import wx
22 
23 import render
24 import menuform
25 import disp_print
26 import utils
27 import gdialogs
28 import globalvar
29 from toolbars import HistogramToolbar
30 from preferences import DefaultFontDialog
31 from debug import Debug
32 from icon import Icons
33 from gcmd import GError
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 HistFrame(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 Histogram of raster map"),
267  style = wx.DEFAULT_FRAME_STYLE, **kwargs):
268  wx.Frame.__init__(self, parent, id, title, style = style, **kwargs)
269  self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
270 
271  self.Map = render.Map() # instance of render.Map to be associated with display
272  self.layer = None # reference to layer with histogram
273 
274  # Init variables
275  self.params = {} # previously set histogram parameters
276  self.propwin = '' # ID of properties dialog
277 
278  self.font = ""
279  self.encoding = 'ISO-8859-1' # default encoding for display fonts
280 
281  self.toolbar = HistogramToolbar(parent = self)
282  self.SetToolBar(self.toolbar)
283 
284  # Add statusbar
285  self.mapname = ''
286  self.statusbar = self.CreateStatusBar(number = 1, style = 0)
287  # self.statusbar.SetStatusWidths([-2, -1])
288  hist_frame_statusbar_fields = ["Histogramming %s" % self.mapname]
289  for i in range(len(hist_frame_statusbar_fields)):
290  self.statusbar.SetStatusText(hist_frame_statusbar_fields[i], i)
291 
292  # Init map display
293  self.InitDisplay() # initialize region values
294 
295  # initialize buffered DC
296  self.HistWindow = BufferedWindow(self, id = wx.ID_ANY, Map = self.Map) # initialize buffered DC
297 
298  # Bind various events
299  self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
300 
301  # Init print module and classes
303 
304  # Add layer to the map
305  self.layer = self.Map.AddLayer(type = "command", name = 'histogram', command = ['d.histogram'],
306  l_active = False, l_hidden = False, l_opacity = 1, l_render = False)
307 
308  def InitDisplay(self):
309  """!Initialize histogram display, set dimensions and region
310  """
311  self.width, self.height = self.GetClientSize()
312  self.Map.geom = self.width, self.height
313 
314  def OnOptions(self, event):
315  """!Change histogram settings"""
316  cmd = ['d.histogram']
317  if self.mapname != '':
318  cmd.append('map=%s' % self.mapname)
319 
320  menuform.GUI(parent = self).ParseCommand(cmd,
321  completed = (self.GetOptData, None, self.params))
322 
323  def GetOptData(self, dcmd, layer, params, propwin):
324  """!Callback method for histogram command generated by dialog
325  created in menuform.py
326  """
327  if dcmd:
328  name, found = utils.GetLayerNameFromCmd(dcmd, fullyQualified = True,
329  layerType = 'raster')
330  if not found:
331  GError(parent = propwin,
332  message = _("Raster map <%s> not found") % name)
333  return
334 
335  self.SetHistLayer(name)
336  self.params = params
337  self.propwin = propwin
338 
339  self.HistWindow.UpdateHist()
340 
341  def SetHistLayer(self, name):
342  """!Set histogram layer
343  """
344  self.mapname = name
345 
346  self.layer = self.Map.ChangeLayer(layer = self.layer,
347  command = [['d.histogram', 'map=%s' % self.mapname],],
348  active = True)
349 
350  return self.layer
351 
352  def SetHistFont(self, event):
353  """!Set font for histogram. If not set, font will be default
354  display font.
355  """
356  dlg = DefaultFontDialog(parent = self, id = wx.ID_ANY,
357  title = _('Select font for histogram text'))
358  dlg.fontlb.SetStringSelection(self.font, True)
359 
360  if dlg.ShowModal() == wx.ID_CANCEL:
361  dlg.Destroy()
362  return
363 
364  # set default font type, font, and encoding to whatever selected in dialog
365  if dlg.font != None:
366  self.font = dlg.font
367  if dlg.encoding != None:
368  self.encoding = dlg.encoding
369 
370  dlg.Destroy()
371  self.HistWindow.UpdateHist()
372 
373  def OnErase(self, event):
374  """!Erase the histogram display
375  """
376  self.HistWindow.Draw(self.HistWindow.pdc, pdctype = 'clear')
377 
378  def OnRender(self, event):
379  """!Re-render histogram
380  """
381  self.HistWindow.UpdateHist()
382 
383  def GetWindow(self):
384  """!Get buffered window"""
385  return self.HistWindow
386 
387  def SaveToFile(self, event):
388  """!Save to file
389  """
390  filetype, ltype = gdialogs.GetImageHandlers(self.HistWindow.img)
391 
392  # get size
393  dlg = gdialogs.ImageSizeDialog(self)
394  dlg.CentreOnParent()
395  if dlg.ShowModal() != wx.ID_OK:
396  dlg.Destroy()
397  return
398  width, height = dlg.GetValues()
399  dlg.Destroy()
400 
401  # get filename
402  dlg = wx.FileDialog(parent = self,
403  message = _("Choose a file name to save the image "
404  "(no need to add extension)"),
405  wildcard = filetype,
406  style=wx.SAVE | wx.FD_OVERWRITE_PROMPT)
407 
408  if dlg.ShowModal() == wx.ID_OK:
409  path = dlg.GetPath()
410  if not path:
411  dlg.Destroy()
412  return
413 
414  base, ext = os.path.splitext(path)
415  fileType = ltype[dlg.GetFilterIndex()]['type']
416  extType = ltype[dlg.GetFilterIndex()]['ext']
417  if ext != extType:
418  path = base + '.' + extType
419 
420  self.HistWindow.SaveToFile(path, fileType,
421  width, height)
422 
423  self.HistWindow.UpdateHist()
424  dlg.Destroy()
425 
426  def PrintMenu(self, event):
427  """!Print options and output menu
428  """
429  point = wx.GetMousePosition()
430  printmenu = wx.Menu()
431  # Add items to the menu
432  setup = wx.MenuItem(printmenu, id = wx.ID_ANY, text = _('Page setup'))
433  printmenu.AppendItem(setup)
434  self.Bind(wx.EVT_MENU, self.printopt.OnPageSetup, setup)
435 
436  preview = wx.MenuItem(printmenu, id = wx.ID_ANY, text = _('Print preview'))
437  printmenu.AppendItem(preview)
438  self.Bind(wx.EVT_MENU, self.printopt.OnPrintPreview, preview)
439 
440  doprint = wx.MenuItem(printmenu, id = wx.ID_ANY, text = _('Print display'))
441  printmenu.AppendItem(doprint)
442  self.Bind(wx.EVT_MENU, self.printopt.OnDoPrint, doprint)
443 
444  # Popup the menu. If an item is selected then its handler
445  # will be called before PopupMenu returns.
446  self.PopupMenu(printmenu)
447  printmenu.Destroy()
448 
449  def OnQuit(self, event):
450  self.Close(True)
451 
452  def OnCloseWindow(self, event):
453  """!Window closed
454  Also remove associated rendered images
455  """
456  try:
457  self.propwin.Close(True)
458  except:
459  pass
460  self.Map.Clean()
461  self.Destroy()
462