histogram.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546
  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. # using active comp region
  194. self.Map.GetRegion(update=True)
  195. self.Map.width, self.Map.height = self.GetClientSize()
  196. self.mapfile = self.Map.Render(force=self.render)
  197. self.img = self.GetImage()
  198. self.resize = False
  199. if not self.img: return
  200. try:
  201. id = self.imagedict[self.img]
  202. except:
  203. return
  204. # paint images to PseudoDC
  205. self.pdc.Clear()
  206. self.pdc.RemoveAll()
  207. self.Draw(self.pdc, self.img, drawid=id) # draw map image background
  208. self.resize = False
  209. # update statusbar
  210. # Debug.msg (3, "BufferedWindow.UpdateHist(%s): region=%s" % self.Map.region)
  211. self.Map.SetRegion()
  212. self.parent.statusbar.SetStatusText("Raster/Image map layer <%s>" % self.parent.mapname)
  213. # set default font and encoding environmental variables
  214. if oldfont != "":
  215. os.environ["GRASS_FONT"] = oldfont
  216. if oldencoding != "":
  217. os.environ["GRASS_ENCODING"] = oldencoding
  218. def EraseMap(self):
  219. """
  220. Erase the map display
  221. """
  222. self.Draw(self.pdc, pdctype='clear')
  223. class HistFrame(wx.Frame):
  224. """
  225. Main frame for hisgram display window.
  226. Uses d.histogram rendered onto canvas
  227. """
  228. def __init__(self, parent=None, id = wx.ID_ANY, title="Histogram of image or raster map",
  229. pos=wx.DefaultPosition, size=wx.DefaultSize,
  230. style=wx.DEFAULT_FRAME_STYLE):
  231. wx.Frame.__init__(self, parent, id, title, pos, size, style)
  232. toolbar = self.__createToolBar()
  233. self.Map = render.Map() # instance of render.Map to be associated with display
  234. self.layer = None # reference to layer with histogram
  235. #
  236. # Set the size & cursor
  237. #
  238. self.SetClientSize(size)
  239. self.iconsize = (16, 16)
  240. # Init variables
  241. self.params = {} # previously set histogram parameters
  242. self.propwin = '' # ID of properties dialog
  243. self.font = ""
  244. self.encoding = 'ISO-8859-1' # default encoding for display fonts
  245. #
  246. # Add statusbar
  247. #
  248. self.mapname = ''
  249. self.statusbar = self.CreateStatusBar(number=1, style=0)
  250. # self.statusbar.SetStatusWidths([-2, -1])
  251. hist_frame_statusbar_fields = ["Histogramming %s" % self.mapname]
  252. for i in range(len(hist_frame_statusbar_fields)):
  253. self.statusbar.SetStatusText(hist_frame_statusbar_fields[i], i)
  254. #
  255. # Init map display
  256. #
  257. self.InitDisplay() # initialize region values
  258. # initialize buffered DC
  259. self.HistWindow = BufferedWindow(self, id = wx.ID_ANY, Map=self.Map) # initialize buffered DC
  260. #
  261. # Bind various events
  262. #
  263. self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
  264. #
  265. # Init print module and classes
  266. #
  267. self.printopt = disp_print.PrintOptions(self, self.HistWindow)
  268. #
  269. # Add layer to the map
  270. #
  271. self.layer = self.Map.AddLayer(type="command", name='histogram', command=['d.histogram'],
  272. l_active=False, l_hidden=False, l_opacity=1, l_render=False)
  273. def __createToolBar(self):
  274. """Creates toolbar"""
  275. toolbar = self.CreateToolBar()
  276. for each in self.toolbarData():
  277. self.AddToolbarButton(toolbar, *each)
  278. toolbar.Realize()
  279. def AddToolbarButton(self, toolbar, label, icon, help, handler):
  280. """Adds buttons to the toolbar"""
  281. if not label:
  282. toolbar.AddSeparator()
  283. return
  284. tool = toolbar.AddLabelTool(id=wx.ID_ANY, label=label, bitmap=icon, shortHelp=help)
  285. self.Bind(wx.EVT_TOOL, handler, tool)
  286. def toolbarData(self):
  287. return (
  288. ('histogram',
  289. Icons["histogram"].GetBitmap(),
  290. Icons["histogram"].GetLabel(),
  291. self.OnOptions),
  292. ('rendermap',
  293. Icons["rendermap"].GetBitmap(),
  294. Icons["rendermap"].GetLabel(),
  295. self.OnRender),
  296. ('erase',
  297. Icons["erase"].GetBitmap(),
  298. Icons["erase"].GetLabel(),
  299. self.OnErase),
  300. ('font',
  301. Icons["font"].GetBitmap(),
  302. Icons["font"].GetLabel(),
  303. self.SetHistFont),
  304. ('', '', '', ''),
  305. ('save',
  306. Icons["savefile"].GetBitmap(),
  307. Icons["savefile"].GetLabel(),
  308. self.SaveToFile),
  309. ('print',
  310. Icons["printmap"].GetBitmap(),
  311. Icons["printmap"].GetLabel(),
  312. self.PrintMenu),
  313. ('quit',
  314. Icons["quit"].GetBitmap(),
  315. Icons["quit"].GetLabel(),
  316. self.OnQuit))
  317. def InitDisplay(self):
  318. """
  319. Initialize histogram display, set dimensions and region
  320. """
  321. self.width, self.height = self.GetClientSize()
  322. self.Map.geom = self.width, self.height
  323. def OnOptions(self, event):
  324. """Change histogram settings"""
  325. cmd = ['d.histogram']
  326. if self.mapname != '':
  327. cmd.append('map=%s' % self.mapname)
  328. menuform.GUI().ParseCommand(cmd,
  329. completed=(self.GetOptData, None, self.params),
  330. parentframe=self)
  331. def GetOptData(self, dcmd, layer, params, propwin):
  332. """
  333. Callback method for histogram command generated by
  334. dialog created in menuform.py
  335. """
  336. if dcmd:
  337. name = utils.GetLayerNameFromCmd(dcmd, fullyQualified=True)
  338. self.SetHistLayer(name)
  339. self.params = params
  340. self.propwin = propwin
  341. self.HistWindow.UpdateHist()
  342. def SetHistLayer(self, name):
  343. """
  344. Set histogram layer
  345. """
  346. self.mapname = name
  347. self.layer = self.Map.ChangeLayer(layer=self.layer,
  348. command=[['d.histogram', 'map=%s' % self.mapname],],
  349. active=True)
  350. return self.layer
  351. def SetHistFont(self, event):
  352. """
  353. Set font for histogram. If not
  354. set, font will be default 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. if dlg.ShowModal() == wx.ID_CANCEL:
  360. dlg.Destroy()
  361. return
  362. # set default font type, font, and encoding to whatever selected in dialog
  363. if dlg.font != None:
  364. self.font = dlg.font
  365. if dlg.encoding != None:
  366. self.encoding = dlg.encoding
  367. dlg.Destroy()
  368. self.HistWindow.UpdateHist()
  369. def OnErase(self, event):
  370. """
  371. Erase the histogram display
  372. """
  373. self.HistWindow.Draw(self.HistWindow.pdc, pdctype='clear')
  374. def OnRender(self, event):
  375. """
  376. Re-render histogram
  377. """
  378. self.HistWindow.UpdateHist()
  379. def SaveToFile(self, event):
  380. """
  381. Save to file
  382. """
  383. filetype = "PNG file (*.png)|*.png|"\
  384. "TIF file (*.tif)|*.tif|"\
  385. "GIF file (*.gif)|*.gif"
  386. dlg = wx.FileDialog(self, "Choose a file name to save the image as a PNG to",
  387. defaultDir = "",
  388. defaultFile = "",
  389. wildcard = filetype,
  390. style=wx.SAVE|wx.FD_OVERWRITE_PROMPT)
  391. if dlg.ShowModal() == wx.ID_OK:
  392. base = os.path.splitext(dlg.GetPath())[0]
  393. ext = os.path.splitext(dlg.GetPath())[1]
  394. if dlg.GetFilterIndex() == 0:
  395. type = wx.BITMAP_TYPE_PNG
  396. path = dlg.GetPath()
  397. if ext != '.png': path = base+'.png'
  398. elif dlg.GetFilterIndex() == 1:
  399. type = wx.BITMAP_TYPE_TIF
  400. if ext != '.tif': path = base+'.tif'
  401. elif dlg.GetFilterIndex() == 2:
  402. type = wx.BITMAP_TYPE_TIF
  403. if ext != '.gif': path = base+'.gif'
  404. self.HistWindow.SaveToFile(path, type)
  405. dlg.Destroy()
  406. def PrintMenu(self, event):
  407. """
  408. Print options and output menu
  409. """
  410. point = wx.GetMousePosition()
  411. printmenu = wx.Menu()
  412. # Add items to the menu
  413. setup = wx.MenuItem(printmenu, -1,'Page setup')
  414. printmenu.AppendItem(setup)
  415. self.Bind(wx.EVT_MENU, self.printopt.OnPageSetup, setup)
  416. preview = wx.MenuItem(printmenu, -1,'Print preview')
  417. printmenu.AppendItem(preview)
  418. self.Bind(wx.EVT_MENU, self.printopt.OnPrintPreview, preview)
  419. doprint = wx.MenuItem(printmenu, -1,'Print display')
  420. printmenu.AppendItem(doprint)
  421. self.Bind(wx.EVT_MENU, self.printopt.OnDoPrint, doprint)
  422. reset = wx.MenuItem(printmenu, -1,'Reset')
  423. printmenu.AppendItem(reset)
  424. self.Bind(wx.EVT_MENU, self.printopt.OnReset, reset)
  425. # Popup the menu. If an item is selected then its handler
  426. # will be called before PopupMenu returns.
  427. self.PopupMenu(printmenu)
  428. printmenu.Destroy()
  429. def OnQuit(self, event):
  430. self.Close(True)
  431. def OnCloseWindow(self, event):
  432. """
  433. Window closed
  434. Also remove associated rendered images
  435. """
  436. try:
  437. self.propwin.Close(True)
  438. except:
  439. pass
  440. self.Map.Clean()
  441. self.Destroy()