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