1############################################################################### 2# Copyright (c) Lawrence Livermore National Security, LLC and other Ascent 3# Project developers. See top-level LICENSE AND COPYRIGHT files for dates and 4# other details. No copyright assignment is required to contribute to Ascent. 5############################################################################### 6 7############################################################################### 8# file: jupyter.py 9# Purpose: Jupyter Ascent Widgets 10# 11############################################################################### 12 13import os 14import conduit 15 16try: 17 # we might not always have ipywidgets module (jupyter widgets) 18 # allow import to work even if not installed 19 import ipywidgets 20 ipywidgets_support = True 21except: 22 ipywidgets_support = False 23 24try: 25 # we might not always have the cinemasci module 26 # allow import to work even if not installed 27 import cinemasci.pynb 28 cinemasci_support = True 29except: 30 cinemasci_support = False 31 32class AscentImageSequenceViewer(object): 33 """ 34 Widget that shows a sequence of images with play controls. 35 """ 36 def __init__(self, image_fnames): 37 self.display_w = 400 38 self.display_h = 400 39 self.fnames = image_fnames 40 self.index = 0 41 self.data = [] 42 for fname in self.fnames: 43 with open(fname, "rb") as f: 44 self.data.append(f.read()) 45 self.label = ipywidgets.Label(value=self.fnames[self.index]) 46 self.image = ipywidgets.Image(value=self.data[self.index], 47 width=self.display_w, 48 height=self.display_h, 49 format="png") 50 self.check = ipywidgets.Checkbox(value=False, 51 description='Show absolute file path', 52 disabled=False, 53 indent=False) 54 self.slider = ipywidgets.IntSlider() 55 self.play = ipywidgets.Play(value=0, 56 min=0, 57 max=len(self.data)-1, 58 step=1, 59 interval=500) 60 ipywidgets.jslink((self.play, "min"), (self.slider, "min")) 61 ipywidgets.jslink((self.play, "max"), (self.slider, "max")) 62 ipywidgets.jslink((self.play, "value"), (self.slider, "value")) 63 64 def update_index(self,change): 65 self.index = change.owner.value 66 self.image.value = self.data[self.index] 67 self.label.value = self.image_path_txt() 68 69 def update_checkbox(self,change): 70 self.label.value = self.image_path_txt() 71 72 def image_path_txt(self): 73 fname = self.fnames[self.index] 74 if self.check.value is False: 75 return "File: " + fname 76 else: 77 return "File: " + os.path.abspath(fname) 78 79 def show(self): 80 if len(self.data) > 1: 81 # if we have more than one image 82 # we enabled the slider + play controls 83 v = ipywidgets.VBox([self.image, 84 self.label, 85 self.check, 86 self.slider,self.play]) 87 # setup connections for our controls 88 self.slider.observe(self.update_index) 89 self.play.observe(self.update_index) 90 else: 91 # if we have one image, we only show 92 # the label and abs checkbox 93 v = ipywidgets.VBox([self.image, 94 self.check, 95 self.label]) 96 # setup connections the check box (always in use) 97 self.check.observe(self.update_checkbox) 98 return v 99 100class AscentStatusWidget(object): 101 """ 102 Simple display of info["status"] 103 """ 104 def __init__(self, info): 105 style = self.detect_style(info) 106 status_title = ipywidgets.Button(description=str(info["status/message"]), 107 disabled=True, 108 layout=ipywidgets.Layout(width='100%'), 109 button_style=style) 110 self.main = status_title 111 112 def show(self): 113 return self.main 114 115 def detect_style(self,info): 116 msg = info["status/message"] 117 if "failed" in msg: 118 return 'danger' 119 elif "Ascent::close" in msg: 120 return 'warning' 121 else: 122 return 'success' 123 124class AscentNodeViewer(object): 125 """ 126 Shows YAML repr of a Conduit Node 127 """ 128 def __init__(self, node): 129 v = "<pre>" + node.to_yaml() + "</pre>" 130 self.main = ipywidgets.HTML(value=v, 131 placeholder='', 132 description='') 133 134 def show(self): 135 return self.main 136 137 138class AscentResultsViewer(object): 139 """ 140 Widget that display Ascent results from info. 141 """ 142 def __init__(self, info): 143 status = AscentStatusWidget(info) 144 widget_titles = [] 145 widgets = [] 146 147 # cinema databases 148 # (skipping until we have a handle on embedding cinema widgets) 149 # if cinema_support and info.has_child("extracts"): 150 # # collect any cinema extracts 151 # cinema_exts = conduit.Node() 152 # cinema_paths = [] 153 # for c in info["extracts"].children(): 154 # if c.node()["type"] == "cinema": 155 # cinema_exts.append().set(c.node()) 156 # if cinema_exts.number_of_children() > 0: 157 # w = AscentNodeViewer(cinema_exts) 158 # widget_titles.append("Cinema Databases") 159 # widgets.append(w.show()) 160 161 # rendered images 162 if info.has_child("images"): 163 # get fnames from images section of info 164 renders_fnames = [] 165 for c in info["images"].children(): 166 renders_fnames.append(c.node()["image_name"]) 167 # view using image viewer widget 168 w = AscentImageSequenceViewer(renders_fnames) 169 widget_titles.append("Renders") 170 widgets.append(w.show()) 171 172 # extracts 173 if info.has_child("extracts"): 174 # view results info as yaml repr 175 w = AscentNodeViewer(info["extracts"]) 176 widget_titles.append("Extracts") 177 widgets.append(w.show()) 178 179 # expressions 180 if info.has_child("expressions"): 181 # view results info as yaml repr 182 w = AscentNodeViewer(info["expressions"]) 183 widget_titles.append("Expressions") 184 widgets.append(w.show()) 185 186 # actions 187 if info.has_child("actions"): 188 # view results info as yaml repr 189 w = AscentNodeViewer(info["actions"]) 190 widget_titles.append("Actions") 191 widgets.append(w.show()) 192 193 # layout active widgets 194 if len(widgets) > 0: 195 # multiple widgets, group into tabs 196 tab = ipywidgets.Tab() 197 tab.children = widgets 198 for i,v in enumerate(widget_titles): 199 tab.set_title(i,v) 200 self.main = ipywidgets.VBox([tab, 201 status.show()], 202 layout=ipywidgets.Layout(overflow_x="hidden")) 203 else: 204 # only the status button 205 self.main = ipywidgets.VBox([status.show()], 206 layout=ipywidgets.Layout(overflow_x="hidden")) 207 208 def show(self): 209 return self.main 210 211 212class AscentViewer(object): 213 """ 214 A jupyter widget ui for Ascent results. 215 216 Thanks to Tom Stitt @ LLNL who provided a great starting 217 point with his image sequence viewer widget. 218 """ 219 def __init__(self,ascent): 220 self.ascent = ascent 221 222 def show(self): 223 info = conduit.Node() 224 self.ascent.info(info) 225 if not info.has_child("status"): 226 raise Exception("ERROR: AscentViewer.show() failed: info lacks `status`") 227 msg = info["status/message"] 228 if "failed" in msg and info.has_path("status/details"): 229 # print error details to standard notebook output, 230 # this looks pretty sensible vs trying to format in a widget 231 print("[Ascent Error]") 232 print(info["status/details"]) 233 if ipywidgets_support: 234 view = AscentResultsViewer(info) 235 return view.show() 236 else: 237 raise Exception("ERROR: AscentViewer.show() failed: ipywidgets missing") 238