histogram.py 16 KB

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