Belle II Software  release-05-02-19
viewer.py
1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*-
3 
4 import numbers
5 import json
6 import time
7 from io import StringIO
8 from html import escape
9 
10 from dateutil.relativedelta import relativedelta
11 
12 
13 class IPythonWidget(object):
14  """
15  A base class for widgets in the hep ipython projects.
16  """
17 
18  def create(self):
19  """
20  Override this method!
21  """
22  return None
23 
24  def show(self):
25  """
26  Show the widget
27  """
28  from IPython.core.display import display
29 
30  a = self.create()
31  display(a)
32 
33 
35  """The css string for styling the notebook."""
36 
37 
38  css_string = """
39  #notebook-container {
40  width: 90%;
41  }
42 
43  #menubar-container {
44  width: 90%;
45  }
46 
47  #header-container {
48  width: 90%;
49  }
50  """
51 
52  def create(self):
53  """Create the styling widget."""
54  from IPython.core.display import HTML
55  html = HTML("<style>\n%s\n</style>" % self.css_string)
56  return html
57 
58 
60  """
61  Viewer Object used to print data to the IPython Notebook.
62  Do not use it on your own.
63  """
64 
65  def __init__(self):
66  """
67  Create a new progress bar viewer.
68  """
69  from IPython.core.display import display
70  from ipywidgets import FloatProgress, VBox, Layout, Label
71 
72 
73  self.last_time = time.time()
74 
75  self.last_percentage = 0
76 
77 
78  self.progress_bar = FloatProgress(value=0, min=0, max=1,
79  layout=Layout(width="100%", height="40px"))
80 
81  self.progress_label = Label()
82 
83  self.progress_box = VBox([self.progress_bar, self.progress_label])
84 
85  display(self.progress_box)
86 
87  def update(self, text_or_percentage):
88  """
89  Update the widget with a new event number
90  """
91 
92  if isinstance(text_or_percentage, numbers.Number):
93  # text_or_percentage is percentage fraction
94  current_percentage = float(text_or_percentage)
95  current_time = time.time()
96 
97  remaining_percentage = 1.0 - current_percentage
98 
99  time_delta = current_time - self.last_time
100  percentage_delta = current_percentage - self.last_percentage
101 
102  if percentage_delta > 0:
103  time_delta_per_percentage = 1.0 * time_delta / percentage_delta
104 
105  # creates a human-readable time delta like '3 minutes 34 seconds'
106  attrs = ['years', 'months', 'days', 'hours', 'minutes', 'seconds']
107 
108  def human_readable(delta): return ['%d %s' % (getattr(delta, attr), getattr(delta, attr) > 1 and attr or attr[:-1])
109  for attr in attrs if getattr(delta, attr)]
110 
111  times_list = human_readable(relativedelta(seconds=time_delta_per_percentage * remaining_percentage))
112  human_readable_str = " ".join(times_list)
113 
114  display_text = "%d %% Remaining time: %s" % (
115  100 * current_percentage, human_readable_str)
116 
117  self.progress_label.value = display_text
118  self.progress_bar.value = float(current_percentage)
119  else:
120  # text_or_percentage is status string
121  self.progress_label.value = "Status: {}".format(text_or_percentage)
122  if "finished" in str(text_or_percentage):
123  self.progress_bar.value = 1.0
124  self.progress_bar.bar_style = "success"
125  elif "failed" in str(text_or_percentage):
126  self.progress_bar.bar_style = "danger"
127  # color box red to see failure even when progress value is 0
128  self.progress_box.box_style = "danger"
129 
130  def show(self):
131  """
132  Display the widget
133  """
134  from IPython.core.display import display
135 
136  display(self.progress_box)
137 
138 
140  """
141  Viewer object for the store entries.
142  Do not use it on your own.
143  """
144 
145  def __init__(self, collections):
146  """
147  Create a new collections viewer with the collections object from the process.
148  Collections must be a StoreContentList with a list of StoreContents.
149  """
150 
151  self.collections = collections
152 
153 
154  self.table_row_html = """<tr><td style="padding: 10px 0;">{content.name}</td>
155  <td style="padding: 10px 0;">{content.number}</td></tr>"""
156 
157  def create(self):
158  """
159  Create the widget
160  """
161 
162  import ipywidgets as widgets
163 
164  if self.collections is None:
165  return widgets.HTML("")
166 
167  a = widgets.Tab()
168  children = []
169 
170  for i, event in enumerate(self.collections):
171  html = widgets.HTML()
172  html.value = """<table style="border-collapse: separate; border-spacing: 50px 0;">"""
173  for store_content in event.content:
174  html.value += self.table_row_html.format(content=store_content)
175  html.value += "</table>"
176  children.append(html)
177  a.set_title(i, "Event " + str(event.event_number))
178 
179  a.children = children
180 
181  return a
182 
183 
185  """
186  A viewer widget for displaying the
187  statistics in a nicer way in ipython
188  """
189 
190  def __init__(self, statistics):
191  """ Init the widget with the statistics from the process.
192  The statistics must be an instance of Statistics. """
193 
194  self.statistics = statistics
195 
196 
197  self.table_column_html = """<td style="padding: 10px;">{content}</td>"""
198 
199  self.table_column_3_html = """<td colspan="3" style="padding: 10px;">{content}</td>"""
200 
201  self.table_cell_html = """<td style="padding: 10px; text-align: left">{content}</td>"""
202 
203  self.table_cell_3_html = """<td style=\"text-align: right\">{content[0]}</td><td>{content[1]}</td><td>{content[2]}</td>"""
204 
205  def create(self):
206  """
207  Create the widget
208  """
209  import ipywidgets as widgets
210 
211  if self.statistics is None:
212  return widgets.HTML("")
213 
214  html = widgets.HTML()
215  html.value = """<table style="border-collapse: collapsed; border: 1px solid black;">
216  <thead><tr style="background: #AAA; font-weight: bold">"""
217 
218  for column in self.statistics.columns:
219  if column.three_column_format:
220  html.value += self.table_column_3_html.format(content=column.display_name)
221  else:
222  html.value += self.table_column_html.format(content=column.display_name)
223  html.value += "</tr></thead><tbody>"
224 
225  for n, module in enumerate(self.statistics.modules):
226  if n % 2 == 1:
227  html.value += """<tr style="background: #EEE;">"""
228  else:
229  html.value += """<tr>"""
230 
231  for column in self.statistics.columns:
232  if column.three_column_format:
233  html.value += self.table_cell_3_html.format(content=module[column.name])
234  else:
235  html.value += self.table_cell_html.format(content=module[column.name])
236 
237  html.value += "</tr>"
238 
239  # SUMMARY html.value += """<tr style="border: 1px solid black; font-weight: bold">"""
240 
241  html.value += "</tbody></table>"
242  html.margin = "10px"
243  return html
244 
245 
246 class ProcessViewer(object):
247  """
248  A widget to summarize all the infromation from different processes.
249  Must be filled with the widgets of the single processes
250  """
251 
252  def __init__(self, children):
253  """
254  Create a process viewer
255  """
256 
257 
258  self.children = children
259 
260  def create(self):
261  """
262  Create the widget
263  """
264  import ipywidgets as widgets
265 
266  a = widgets.Tab()
267  for i in range(len(self.children)):
268  a.set_title(i, "Process " + str(i))
269  a.children = [children.create() for children in self.children if children is not None]
270  return a
271 
272  def show(self):
273  """
274  Show the widget
275  """
276 
277  import ipywidgets as widgets
278  from IPython.core.display import display
279 
280  if len(self.children) > 0:
281  a = self.create()
282 
283  else:
284  a = widgets.HTML("<strong>Calculation list empty. Nothing to show.</strong>")
285 
286  display(a)
287 
288 
290  """
291  A widget to show the log of a calculation.
292  """
293 
294  def __init__(self, log_content):
295  """
296  Initialize the log viewer.
297  """
298 
299 
300  self.log_content = log_content
301 
302 
303  self.log_levels = ["DEBUG", "INFO", "RESULT", "WARNING", "ERROR", "FATAL", "DEFAULT"]
304 
305 
306  self.log_color_codes = {"DEBUG": "gray", "ERROR": "red", "FATAL": "red", "INFO": "black", "RESULT": "green",
307  "WARNING": "orange", "DEFAULT": "black"}
308 
309 
310  self.log_message = """<pre class="log-line-{type_lower}" title="{info}">[{level}] {message}{var_output}</pre>"""
311 
312 
313  self.toggle_button_line = """<a onclick="$('.log-line-{type_lower}').hide();
314  $('.log-line-{type_lower}-hide-button').hide();
315  $('.log-line-{type_lower}-show-button').show();"
316  style="cursor: pointer; margin: 0px 10px;"
317  class="log-line-{type_lower}-hide-button">Hide {type_upper}</a>
318  <a onclick="$('.log-line-{type_lower}').show();
319  $('.log-line-{type_lower}-hide-button').show();
320  $('.log-line-{type_lower}-show-button').hide();"
321  style="cursor: pointer; margin: 0px 10px; display: none;"
322  class="log-line-{type_lower}-show-button">Show {type_upper}</a>"""
323 
324  def format_logmessage(self, buf, message, indent=4, base=0):
325  """
326  Format the json object of a logmessage as key: value list with recursion and indentation
327  Funnily this is faster than json.dumps() and looks a bit better in the title attribute
328  """
329  for key, val in message.items():
330  if not val:
331  continue
332  if isinstance(val, dict):
333  buf.write(f"{key}:\n")
334  self.format_logmessage(buf, val, indent=indent, base=base + indent)
335  else:
336  buf.write(base * " ")
337  buf.write(f"{key}: {val!r}\n")
338 
339  def create(self):
340  """
341  Create the log viewer.
342  """
343  from ipywidgets import HTML, HBox, VBox
344 
345  output = StringIO()
346  output.write("<style scoped>\n")
347  for level, color in self.log_color_codes.items():
348  level = level.lower()
349  output.write(f".log-line-{level} {{margin:0; padding:0; line-height:normal; color: {color} !important;}}\n")
350 
351  output.write("""</style><div style="max-height: 400px; overflow-y: auto; width: 100%";>""")
352 
353  for line in self.log_content.split("\n"):
354  if line.startswith('{"level"'):
355  try:
356  message = json.loads(line)
357  # ok, message is parsed. Prepare some info string which
358  # contains the info about the message in a indented
359  # format but don't completely json or pprint because it
360  # takes to much time
361  buf = StringIO()
362  self.format_logmessage(buf, message)
363  info = escape(buf.getvalue())
364  # add variables if necessary
365  variables = message.get("variables", "")
366  if variables:
367  variables = "\n".join([""] + [f"\t{k} = {v}" for k, v in variables.items()])
368  # and write out
369  level = message["level"].lower()
370  output.write(self.log_message.format(info=info, type_lower=level, var_output=variables, **message))
371  continue
372  except json.JSONDecodeError as e:
373  # any error: treat as default output, not a log line
374  pass
375 
376  output.write('<pre class="log-line-default">')
377  output.write(line)
378  output.write('</pre>')
379 
380  output.write("</div>")
381 
382  html = HTML()
383  html.value = output.getvalue()
384  html.width = "100%"
385  html.margin = "5px"
386 
387  buttons = []
388  for type in self.log_levels:
389  buttons.append(HTML(self.toggle_button_line.format(type_lower=type.lower(), type_upper=type.upper())))
390 
391  buttons_view = HBox(buttons)
392  buttons_view.margin = "10px 0px"
393  result_vbox = VBox((buttons_view, html))
394 
395  return result_vbox
hep_ipython_tools.viewer.LogViewer.log_levels
log_levels
The log levels of the framework.
Definition: viewer.py:303
hep_ipython_tools.viewer.IPythonWidget.create
def create(self)
Definition: viewer.py:18
hep_ipython_tools.viewer.IPythonWidget
Definition: viewer.py:13
hep_ipython_tools.viewer.ProgressBarViewer.update
def update(self, text_or_percentage)
Definition: viewer.py:87
hep_ipython_tools.viewer.LogViewer
Definition: viewer.py:289
hep_ipython_tools.viewer.StylingWidget.create
def create(self)
Definition: viewer.py:52
hep_ipython_tools.viewer.StatisticsViewer.statistics
statistics
The statistics we want to show.
Definition: viewer.py:194
hep_ipython_tools.viewer.CollectionsViewer.create
def create(self)
Definition: viewer.py:157
hep_ipython_tools.viewer.IPythonWidget.show
def show(self)
Definition: viewer.py:24
hep_ipython_tools.viewer.ProgressBarViewer.progress_box
progress_box
Box widget that will be displayed, contains progress bar and status label.
Definition: viewer.py:83
hep_ipython_tools.viewer.ProgressBarViewer.last_time
last_time
The starting time of the process.
Definition: viewer.py:73
hep_ipython_tools.viewer.StatisticsViewer.table_cell_3_html
table_cell_3_html
Template for a table cell with 3 columns.
Definition: viewer.py:203
hep_ipython_tools.viewer.LogViewer.log_content
log_content
The log content to show.
Definition: viewer.py:300
hep_ipython_tools.viewer.StatisticsViewer.table_column_3_html
table_column_3_html
Template for a table cell spanning 3 columns.
Definition: viewer.py:199
hep_ipython_tools.viewer.LogViewer.log_color_codes
log_color_codes
The color codes for the log messages.
Definition: viewer.py:306
hep_ipython_tools.viewer.ProgressBarViewer.last_percentage
last_percentage
The starting percentage (obviously 0)
Definition: viewer.py:75
hep_ipython_tools.viewer.StatisticsViewer.table_cell_html
table_cell_html
Template for a table cell with left alignment.
Definition: viewer.py:201
hep_ipython_tools.viewer.ProcessViewer.__init__
def __init__(self, children)
Definition: viewer.py:252
hep_ipython_tools.viewer.LogViewer.__init__
def __init__(self, log_content)
Definition: viewer.py:294
hep_ipython_tools.viewer.LogViewer.log_message
log_message
A templated line in the log.
Definition: viewer.py:310
hep_ipython_tools.viewer.ProgressBarViewer.progress_bar
progress_bar
Widget of the progress bar itself.
Definition: viewer.py:78
hep_ipython_tools.viewer.StatisticsViewer.table_column_html
table_column_html
Template for a table cell.
Definition: viewer.py:197
hep_ipython_tools.viewer.LogViewer.toggle_button_line
toggle_button_line
The toggle button.
Definition: viewer.py:313
hep_ipython_tools.viewer.CollectionsViewer.__init__
def __init__(self, collections)
Definition: viewer.py:145
hep_ipython_tools.viewer.LogViewer.create
def create(self)
Definition: viewer.py:339
hep_ipython_tools.viewer.StatisticsViewer.__init__
def __init__(self, statistics)
Definition: viewer.py:190
hep_ipython_tools.viewer.LogViewer.format_logmessage
def format_logmessage(self, buf, message, indent=4, base=0)
Definition: viewer.py:324
display
Definition: display.py:1
hep_ipython_tools.viewer.CollectionsViewer.collections
collections
The collections to show.
Definition: viewer.py:151
hep_ipython_tools.viewer.ProcessViewer.children
children
The children for each process.
Definition: viewer.py:258
hep_ipython_tools.viewer.StatisticsViewer.create
def create(self)
Definition: viewer.py:205
hep_ipython_tools.viewer.ProgressBarViewer.show
def show(self)
Definition: viewer.py:130
hep_ipython_tools.viewer.ProgressBarViewer.__init__
def __init__(self)
Definition: viewer.py:65
hep_ipython_tools.viewer.ProcessViewer.create
def create(self)
Definition: viewer.py:260
hep_ipython_tools.viewer.ProgressBarViewer.progress_label
progress_label
Label for the progress bar, shows progress in percent or status.
Definition: viewer.py:81
hep_ipython_tools.viewer.ProgressBarViewer
Definition: viewer.py:59
hep_ipython_tools.viewer.StylingWidget.css_string
string css_string
The css string for styling the notebook.
Definition: viewer.py:38
hep_ipython_tools.viewer.CollectionsViewer.table_row_html
table_row_html
Template for a table row.
Definition: viewer.py:154
hep_ipython_tools.viewer.CollectionsViewer
Definition: viewer.py:139
hep_ipython_tools.viewer.StatisticsViewer
Definition: viewer.py:184
hep_ipython_tools.viewer.ProcessViewer.show
def show(self)
Definition: viewer.py:272
hep_ipython_tools.viewer.StylingWidget
Definition: viewer.py:34
hep_ipython_tools.viewer.ProcessViewer
Definition: viewer.py:246