histogram.py 16 KB

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