histogram.py 16 KB

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