gui.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. """
  2. GUI progressbar decorator for iterators.
  3. Includes a default `range` iterator printing to `stderr`.
  4. Usage:
  5. >>> from tqdm.gui import trange, tqdm
  6. >>> for i in trange(10):
  7. ... ...
  8. """
  9. # future division is important to divide integers and get as
  10. # a result precise floating numbers (instead of truncated int)
  11. from __future__ import division, absolute_import
  12. # import compatibility functions and utilities
  13. from .utils import _range
  14. # to inherit from the tqdm class
  15. from .std import tqdm as std_tqdm
  16. from .std import TqdmExperimentalWarning
  17. from warnings import warn
  18. __author__ = {"github.com/": ["casperdcl", "lrq3000"]}
  19. __all__ = ['tqdm_gui', 'tgrange', 'tqdm', 'trange']
  20. class tqdm_gui(std_tqdm): # pragma: no cover
  21. """
  22. Experimental GUI version of tqdm!
  23. """
  24. # TODO: @classmethod: write() on GUI?
  25. def __init__(self, *args, **kwargs):
  26. import matplotlib as mpl
  27. import matplotlib.pyplot as plt
  28. from collections import deque
  29. kwargs['gui'] = True
  30. super(tqdm_gui, self).__init__(*args, **kwargs)
  31. # Initialize the GUI display
  32. if self.disable or not kwargs['gui']:
  33. return
  34. warn('GUI is experimental/alpha', TqdmExperimentalWarning, stacklevel=2)
  35. self.mpl = mpl
  36. self.plt = plt
  37. self.sp = None
  38. # Remember if external environment uses toolbars
  39. self.toolbar = self.mpl.rcParams['toolbar']
  40. self.mpl.rcParams['toolbar'] = 'None'
  41. self.mininterval = max(self.mininterval, 0.5)
  42. self.fig, ax = plt.subplots(figsize=(9, 2.2))
  43. # self.fig.subplots_adjust(bottom=0.2)
  44. total = self.__len__() # avoids TypeError on None #971
  45. if total is not None:
  46. self.xdata = []
  47. self.ydata = []
  48. self.zdata = []
  49. else:
  50. self.xdata = deque([])
  51. self.ydata = deque([])
  52. self.zdata = deque([])
  53. self.line1, = ax.plot(self.xdata, self.ydata, color='b')
  54. self.line2, = ax.plot(self.xdata, self.zdata, color='k')
  55. ax.set_ylim(0, 0.001)
  56. if total is not None:
  57. ax.set_xlim(0, 100)
  58. ax.set_xlabel('percent')
  59. self.fig.legend((self.line1, self.line2), ('cur', 'est'),
  60. loc='center right')
  61. # progressbar
  62. self.hspan = plt.axhspan(0, 0.001,
  63. xmin=0, xmax=0, color='g')
  64. else:
  65. # ax.set_xlim(-60, 0)
  66. ax.set_xlim(0, 60)
  67. ax.invert_xaxis()
  68. ax.set_xlabel('seconds')
  69. ax.legend(('cur', 'est'), loc='lower left')
  70. ax.grid()
  71. # ax.set_xlabel('seconds')
  72. ax.set_ylabel((self.unit if self.unit else 'it') + '/s')
  73. if self.unit_scale:
  74. plt.ticklabel_format(style='sci', axis='y',
  75. scilimits=(0, 0))
  76. ax.yaxis.get_offset_text().set_x(-0.15)
  77. # Remember if external environment is interactive
  78. self.wasion = plt.isinteractive()
  79. plt.ion()
  80. self.ax = ax
  81. def __iter__(self):
  82. # TODO: somehow allow the following:
  83. # if not self.gui:
  84. # return super(tqdm_gui, self).__iter__()
  85. iterable = self.iterable
  86. if self.disable:
  87. for obj in iterable:
  88. yield obj
  89. return
  90. # ncols = self.ncols
  91. mininterval = self.mininterval
  92. maxinterval = self.maxinterval
  93. miniters = self.miniters
  94. dynamic_miniters = self.dynamic_miniters
  95. last_print_t = self.last_print_t
  96. last_print_n = self.last_print_n
  97. n = self.n
  98. # dynamic_ncols = self.dynamic_ncols
  99. smoothing = self.smoothing
  100. avg_time = self.avg_time
  101. time = self._time
  102. for obj in iterable:
  103. yield obj
  104. # Update and possibly print the progressbar.
  105. # Note: does not call self.update(1) for speed optimisation.
  106. n += 1
  107. # check counter first to avoid calls to time()
  108. if n - last_print_n >= self.miniters:
  109. miniters = self.miniters # watch monitoring thread changes
  110. delta_t = time() - last_print_t
  111. if delta_t >= mininterval:
  112. cur_t = time()
  113. delta_it = n - last_print_n
  114. # EMA (not just overall average)
  115. if smoothing and delta_t and delta_it:
  116. rate = delta_t / delta_it
  117. avg_time = self.ema(rate, avg_time, smoothing)
  118. self.avg_time = avg_time
  119. self.n = n
  120. self.display()
  121. # If no `miniters` was specified, adjust automatically
  122. # to the max iteration rate seen so far between 2 prints
  123. if dynamic_miniters:
  124. if maxinterval and delta_t >= maxinterval:
  125. # Adjust miniters to time interval by rule of 3
  126. if mininterval:
  127. # Set miniters to correspond to mininterval
  128. miniters = delta_it * mininterval / delta_t
  129. else:
  130. # Set miniters to correspond to maxinterval
  131. miniters = delta_it * maxinterval / delta_t
  132. elif smoothing:
  133. # EMA-weight miniters to converge
  134. # towards the timeframe of mininterval
  135. rate = delta_it
  136. if mininterval and delta_t:
  137. rate *= mininterval / delta_t
  138. miniters = self.ema(rate, miniters, smoothing)
  139. else:
  140. # Maximum nb of iterations between 2 prints
  141. miniters = max(miniters, delta_it)
  142. # Store old values for next call
  143. self.n = self.last_print_n = last_print_n = n
  144. self.last_print_t = last_print_t = cur_t
  145. self.miniters = miniters
  146. # Closing the progress bar.
  147. # Update some internal variables for close().
  148. self.last_print_n = last_print_n
  149. self.n = n
  150. self.miniters = miniters
  151. self.close()
  152. def update(self, n=1):
  153. # if not self.gui:
  154. # return super(tqdm_gui, self).close()
  155. if self.disable:
  156. return
  157. if n < 0:
  158. self.last_print_n += n # for auto-refresh logic to work
  159. self.n += n
  160. # check counter first to reduce calls to time()
  161. if self.n - self.last_print_n >= self.miniters:
  162. delta_t = self._time() - self.last_print_t
  163. if delta_t >= self.mininterval:
  164. cur_t = self._time()
  165. delta_it = self.n - self.last_print_n # >= n
  166. # elapsed = cur_t - self.start_t
  167. # EMA (not just overall average)
  168. if self.smoothing and delta_t and delta_it:
  169. rate = delta_t / delta_it
  170. self.avg_time = self.ema(
  171. rate, self.avg_time, self.smoothing)
  172. self.display()
  173. # If no `miniters` was specified, adjust automatically to the
  174. # maximum iteration rate seen so far between two prints.
  175. # e.g.: After running `tqdm.update(5)`, subsequent
  176. # calls to `tqdm.update()` will only cause an update after
  177. # at least 5 more iterations.
  178. if self.dynamic_miniters:
  179. if self.maxinterval and delta_t >= self.maxinterval:
  180. if self.mininterval:
  181. self.miniters = delta_it * self.mininterval \
  182. / delta_t
  183. else:
  184. self.miniters = delta_it * self.maxinterval \
  185. / delta_t
  186. elif self.smoothing:
  187. self.miniters = self.smoothing * delta_it * \
  188. (self.mininterval / delta_t
  189. if self.mininterval and delta_t
  190. else 1) + \
  191. (1 - self.smoothing) * self.miniters
  192. else:
  193. self.miniters = max(self.miniters, delta_it)
  194. # Store old values for next call
  195. self.last_print_n = self.n
  196. self.last_print_t = cur_t
  197. def close(self):
  198. # if not self.gui:
  199. # return super(tqdm_gui, self).close()
  200. if self.disable:
  201. return
  202. self.disable = True
  203. with self.get_lock():
  204. self._instances.remove(self)
  205. # Restore toolbars
  206. self.mpl.rcParams['toolbar'] = self.toolbar
  207. # Return to non-interactive mode
  208. if not self.wasion:
  209. self.plt.ioff()
  210. if not self.leave:
  211. self.plt.close(self.fig)
  212. def display(self):
  213. n = self.n
  214. cur_t = self._time()
  215. elapsed = cur_t - self.start_t
  216. delta_it = n - self.last_print_n
  217. delta_t = cur_t - self.last_print_t
  218. # Inline due to multiple calls
  219. total = self.total
  220. xdata = self.xdata
  221. ydata = self.ydata
  222. zdata = self.zdata
  223. ax = self.ax
  224. line1 = self.line1
  225. line2 = self.line2
  226. # instantaneous rate
  227. y = delta_it / delta_t
  228. # overall rate
  229. z = n / elapsed
  230. # update line data
  231. xdata.append(n * 100.0 / total if total else cur_t)
  232. ydata.append(y)
  233. zdata.append(z)
  234. # Discard old values
  235. # xmin, xmax = ax.get_xlim()
  236. # if (not total) and elapsed > xmin * 1.1:
  237. if (not total) and elapsed > 66:
  238. xdata.popleft()
  239. ydata.popleft()
  240. zdata.popleft()
  241. ymin, ymax = ax.get_ylim()
  242. if y > ymax or z > ymax:
  243. ymax = 1.1 * y
  244. ax.set_ylim(ymin, ymax)
  245. ax.figure.canvas.draw()
  246. if total:
  247. line1.set_data(xdata, ydata)
  248. line2.set_data(xdata, zdata)
  249. try:
  250. poly_lims = self.hspan.get_xy()
  251. except AttributeError:
  252. self.hspan = self.plt.axhspan(
  253. 0, 0.001, xmin=0, xmax=0, color='g')
  254. poly_lims = self.hspan.get_xy()
  255. poly_lims[0, 1] = ymin
  256. poly_lims[1, 1] = ymax
  257. poly_lims[2] = [n / total, ymax]
  258. poly_lims[3] = [poly_lims[2, 0], ymin]
  259. if len(poly_lims) > 4:
  260. poly_lims[4, 1] = ymin
  261. self.hspan.set_xy(poly_lims)
  262. else:
  263. t_ago = [cur_t - i for i in xdata]
  264. line1.set_data(t_ago, ydata)
  265. line2.set_data(t_ago, zdata)
  266. ax.set_title(self.format_meter(
  267. n, total, elapsed, 0,
  268. self.desc, self.ascii, self.unit, self.unit_scale,
  269. 1 / self.avg_time if self.avg_time else None, self.bar_format,
  270. self.postfix, self.unit_divisor),
  271. fontname="DejaVu Sans Mono", fontsize=11)
  272. self.plt.pause(1e-9)
  273. def tgrange(*args, **kwargs):
  274. """
  275. A shortcut for `tqdm.gui.tqdm(xrange(*args), **kwargs)`.
  276. On Python3+, `range` is used instead of `xrange`.
  277. """
  278. return tqdm_gui(_range(*args), **kwargs)
  279. # Aliases
  280. tqdm = tqdm_gui
  281. trange = tgrange