Module curlew.utils.datascreen
A lightweight visualisation tool for jupyter notebooks using ipywidgets and pythreejs.
This will likely be depricated when we find a better option… (that is lighter than VTK).
Classes
class DataScreen (scale_factor=None, mode='JUPYTER', **kwds)-
Expand source code
class DataScreen: def __init__( self, scale_factor=None, mode = "JUPYTER", **kwds ): self.sf = scale_factor self.center = None self.mode = mode self.__update_settings(kwds) self._light = p3s.DirectionalLight(color='white', position=[0, 0, 1], intensity=0.6) self._light2 = p3s.AmbientLight(intensity=0.5) self._cam = p3s.PerspectiveCamera(position=[0, 0, 10], lookAt=[0, 0, 0], fov=self.__s["fov"], aspect=self.__s["width"]/self.__s["height"], children=[self._light]) self._cam.up = (0,0,1) # set up direction to z-axis (important for controller) self._orbit = p3s.OrbitControls(controlling=self._cam) #self._orbit = p3s.TrackballControls(controlling=self._cam) self._scene = p3s.Scene(children=[self._cam, self._light2], background=self.__s["background"])#"#4c4c80" self._renderer = p3s.Renderer(camera=self._cam, scene = self._scene, controls=[self._orbit], width=self.__s["width"], height=self.__s["height"], antialias=self.__s["antialias"]) self._objects = {} self._cnt = 0 self._centroid = None # init GUI variables (these will be added if the corresponding geometry type is added) self._size_slider = None self._clip_slider = {} self._picker = None self.pickedPoints = [] self.pickedIDs = [] def __update_settings(self, settings={}): sett = {"width": 600, "height": 300, "antialias": True, "scale": 1.5, "background": "#ffffff", "fov": 45} for k in settings: sett[k] = settings[k] self.__s = sett def __add_object(self, name, obj): self._objects[name] = obj self._cnt += 1 self._scene.add(obj["mesh"]) self.__update_view() if self.mode == "JUPYTER": return self._cnt - 1 elif self.mode == "WEBSITE": return self def __update_view(self): if len(self._objects) == 0: return ma = np.zeros((len(self._objects), 3)) mi = np.zeros((len(self._objects), 3)) for r, obj in enumerate(self._objects): ma[r] = self._objects[obj]["max"] mi[r] = self._objects[obj]["min"] ma = np.max(ma, axis=0) mi = np.min(mi, axis=0) diag = np.linalg.norm(ma-mi) mean = ((ma - mi) / 2 + mi).tolist() self._centroid = mean # store as this is quite useful scale = self.__s["scale"] * (diag) self._orbit.target = mean #self._cam.lookAt(mean) self._cam.position = [mean[0], mean[1], mean[2]+scale] self._light.position = [mean[0], mean[1], mean[2]+scale] self._orbit.exec_three_obj_method('update') self._cam.exec_three_obj_method('updateProjectionMatrix') def __get_bbox(self, v): m = np.min(v, axis=0) M = np.max(v, axis=0) # Corners of the bounding box v_box = np.array([[m[0], m[1], m[2]], [M[0], m[1], m[2]], [M[0], M[1], m[2]], [m[0], M[1], m[2]], [m[0], m[1], M[2]], [M[0], m[1], M[2]], [M[0], M[1], M[2]], [m[0], M[1], M[2]]]) f_box = np.array([[0, 1], [1, 2], [2, 3], [3, 0], [4, 5], [5, 6], [6, 7], [7, 4], [0, 4], [1, 5], [2, 6], [7, 3]], dtype=np.uint32) return v_box, f_box def __centerScale( self, xyz ): # get and apply center if self.center is None: self.center = np.mean(xyz, axis=0) xyz = xyz - self.center # remove center # get and apply scale factor if self.sf is None: self.sf = 10 / np.max( np.max(xyz, axis=0) - np.min(xyz, axis=0) ) xyz = xyz * self.sf return xyz def addMesh(self, name, verts, faces, rgb='green'): verts = self.__centerScale( verts ) geometry = p3s.BufferGeometry(attributes=dict(index=p3s.BufferAttribute(faces.astype(np.uint32).ravel(), normalized=False), position=p3s.BufferAttribute(verts.astype(np.float32), normalized=False))) #geometry.exec_three_obj_method('computeFaceNormals') material = p3s.MeshStandardMaterial(color=rgb, side='DoubleSide', flat=True, roughness=0.5, metalness=0.25, reflectivity=1.0,) mesh = p3s.Mesh(geometry=geometry, material=material) obj = { 'mesh' : mesh, 'geometry' : geometry, 'material' : material, 'type' : "Mesh", 'min' : np.min(verts, axis=0), 'max' : np.max(verts, axis=0), 'bounds' : self.__get_bbox(verts), } self.__add_object(name, obj) def addPoints(self, name, xyz, rgb=None): xyz = self.__centerScale( xyz ) # get bounds bounds = self.__get_bbox(xyz) ma = np.max(xyz, axis=0) mi = np.min(xyz, axis=0) # build slider if needed if self._size_slider is None: self._size_slider = ipywidgets.FloatSlider(value=0.02, min=0.0, max=0.1, description='Point Size', step=0.01) ps = self._size_slider.value # build buffer attribute pts = p3s.BufferAttribute( array=(xyz).astype(np.float32) ) if rgb is not None: if isinstance(rgb, str): # constant colour geometry = p3s.BufferGeometry( attributes={'position': pts }) material = p3s.PointsMaterial(color = rgb, size=ps) else: # variable colour rgb = p3s.BufferAttribute( array=rgb ) geometry = p3s.BufferGeometry( attributes={'position': pts, 'color' : rgb }) material = p3s.PointsMaterial(vertexColors = 'VertexColors', size=ps) else: geometry = p3s.BufferGeometry( attributes={'position': pts }) material = p3s.PointsMaterial(color='red', size=ps) # build point cloud object cloud = p3s.Points( geometry=geometry, material=material ) # link to GUI elements ipywidgets.jslink((self._size_slider, 'value'), (material, 'size')) # add picker if True: picker = p3s.Picker(controlling = cloud, all=True, event='dblclick', pointThreshold = 0.01) self._renderer.controls = self._renderer.controls + [picker] hover_point = p3s.Mesh(geometry=p3s.SphereGeometry(radius=2*ps), material=p3s.MeshBasicMaterial(color='red')) self._scene.add(hover_point) ipywidgets.jslink((hover_point, 'position'), (picker, 'point')) def pick(evt): self._picker = picker # todo; do something useful here picker.observe(pick) point_obj = {"geometry": geometry, "mesh": cloud, "material": material, "bounds":bounds, "max":ma, "min":mi, "type": "Points", "wireframe": None} return self.__add_object(name, point_obj) def clip( self, name, direction=[1,0,0], width=None, visible=False, pos=0 ): self._renderer.localClippingEnabled = True; # get min and max direction = np.array(direction) / np.linalg.norm(direction) # ensure this has a unit length mi = np.min( [ o['min'] for o in self._objects.values() ], axis=0 ) ma = np.max( [ o['max'] for o in self._objects.values() ], axis=0 ) bounds = self.__get_bbox([mi,ma]) diag = ma - mi length = np.abs( np.dot(diag, direction) ) # travel length # create geometry geometry = p3s.PlaneGeometry( 1, 1 ) material = p3s.MeshPhongMaterial(color = "red", side="DoubleSide", diffuse="blue", opacity=0.2, transparent=True) plane = p3s.Mesh( geometry, material ) plane.lookAt(direction) plane.position = self._centroid plane.visible = visible #self.plane = plane #self._scene.add(plane) # add slider slider = ipywidgets.FloatSlider(value=pos, min=-length/2, max=length/2, description='%s pos'%name) self._clip_slider[name] = slider wslider = None if width is not None: wslider = ipywidgets.FloatSlider(value=width, min=0, max=length/2, description='%s width'%name) self._clip_slider[name+'_width'] = wslider # add object clip_obj = {"geometry": geometry, "mesh": plane, "material": material, "bounds":bounds, "max":ma, "min":mi, "type": "Clip", "direction" : direction, "width" : width, "wslider" : wslider, "slider" : slider, "wireframe": None} out = self.__add_object(name, clip_obj) # setup interaction def update_clip(change): clips = [] for k,o in self._objects.items(): if o['type'] == 'Clip': s = o['slider'] i = s.value plane = o['mesh'] direction = o['direction'] width = o['width'] if width is not None: width = o['wslider'].value plane.position = [c+o for c,o in zip(self._centroid, i*direction) ] clips.append(p3s.Plane(tuple([v for v in direction]),0.05-np.dot(plane.position, direction))) if width is not None: clips.append(p3s.Plane(tuple([-v for v in direction]),width-np.dot(plane.position, -direction))) #self._renderer.clippingPlanes = [p3s.Plane(tuple([v for v in direction]),0.05-np.dot(plane.position, direction))] self._renderer.clippingPlanes = clips # update clipping planes update_clip(None) slider.observe(update_clip) if width is not None: wslider.observe(update_clip) return out def show(self): from ipywidgets import AppLayout from ipywidgets import HTML left = [ self._size_slider]+list(self._clip_slider.values()) left += [HTML('<hr/>')] for k,v in self._objects.items(): left.append( ipywidgets.Checkbox( value=v['mesh'].visible, description=k, disabled=False, indent=True ) ) ipywidgets.jslink((left[-1], 'value'), (v['mesh'], 'visible')) store = ipywidgets.Button(description="Store point") def clk(e): if self._picker is not None: if self._picker.index is not None: self.pickedPoints.append( self._picker.point ) self.pickedIDs.append( self._picker.index ) # add mesh ps = 0.1 if self._size_slider is not None: ps = self._size_slider.value hover_point = p3s.Mesh(geometry=p3s.SphereGeometry(radius=2*ps), material=p3s.MeshBasicMaterial(color='yellow')) self._scene.add(hover_point) store.on_click(clk) right = [store] return AppLayout(header=None, left_sidebar=ipywidgets.VBox([i for i in left if i is not None]), center=self._renderer, right_sidebar=ipywidgets.VBox([i for i in right if i is not None]), footer=None)Methods
def addMesh(self, name, verts, faces, rgb='green')-
Expand source code
def addMesh(self, name, verts, faces, rgb='green'): verts = self.__centerScale( verts ) geometry = p3s.BufferGeometry(attributes=dict(index=p3s.BufferAttribute(faces.astype(np.uint32).ravel(), normalized=False), position=p3s.BufferAttribute(verts.astype(np.float32), normalized=False))) #geometry.exec_three_obj_method('computeFaceNormals') material = p3s.MeshStandardMaterial(color=rgb, side='DoubleSide', flat=True, roughness=0.5, metalness=0.25, reflectivity=1.0,) mesh = p3s.Mesh(geometry=geometry, material=material) obj = { 'mesh' : mesh, 'geometry' : geometry, 'material' : material, 'type' : "Mesh", 'min' : np.min(verts, axis=0), 'max' : np.max(verts, axis=0), 'bounds' : self.__get_bbox(verts), } self.__add_object(name, obj) def addPoints(self, name, xyz, rgb=None)-
Expand source code
def addPoints(self, name, xyz, rgb=None): xyz = self.__centerScale( xyz ) # get bounds bounds = self.__get_bbox(xyz) ma = np.max(xyz, axis=0) mi = np.min(xyz, axis=0) # build slider if needed if self._size_slider is None: self._size_slider = ipywidgets.FloatSlider(value=0.02, min=0.0, max=0.1, description='Point Size', step=0.01) ps = self._size_slider.value # build buffer attribute pts = p3s.BufferAttribute( array=(xyz).astype(np.float32) ) if rgb is not None: if isinstance(rgb, str): # constant colour geometry = p3s.BufferGeometry( attributes={'position': pts }) material = p3s.PointsMaterial(color = rgb, size=ps) else: # variable colour rgb = p3s.BufferAttribute( array=rgb ) geometry = p3s.BufferGeometry( attributes={'position': pts, 'color' : rgb }) material = p3s.PointsMaterial(vertexColors = 'VertexColors', size=ps) else: geometry = p3s.BufferGeometry( attributes={'position': pts }) material = p3s.PointsMaterial(color='red', size=ps) # build point cloud object cloud = p3s.Points( geometry=geometry, material=material ) # link to GUI elements ipywidgets.jslink((self._size_slider, 'value'), (material, 'size')) # add picker if True: picker = p3s.Picker(controlling = cloud, all=True, event='dblclick', pointThreshold = 0.01) self._renderer.controls = self._renderer.controls + [picker] hover_point = p3s.Mesh(geometry=p3s.SphereGeometry(radius=2*ps), material=p3s.MeshBasicMaterial(color='red')) self._scene.add(hover_point) ipywidgets.jslink((hover_point, 'position'), (picker, 'point')) def pick(evt): self._picker = picker # todo; do something useful here picker.observe(pick) point_obj = {"geometry": geometry, "mesh": cloud, "material": material, "bounds":bounds, "max":ma, "min":mi, "type": "Points", "wireframe": None} return self.__add_object(name, point_obj) def clip(self, name, direction=[1, 0, 0], width=None, visible=False, pos=0)-
Expand source code
def clip( self, name, direction=[1,0,0], width=None, visible=False, pos=0 ): self._renderer.localClippingEnabled = True; # get min and max direction = np.array(direction) / np.linalg.norm(direction) # ensure this has a unit length mi = np.min( [ o['min'] for o in self._objects.values() ], axis=0 ) ma = np.max( [ o['max'] for o in self._objects.values() ], axis=0 ) bounds = self.__get_bbox([mi,ma]) diag = ma - mi length = np.abs( np.dot(diag, direction) ) # travel length # create geometry geometry = p3s.PlaneGeometry( 1, 1 ) material = p3s.MeshPhongMaterial(color = "red", side="DoubleSide", diffuse="blue", opacity=0.2, transparent=True) plane = p3s.Mesh( geometry, material ) plane.lookAt(direction) plane.position = self._centroid plane.visible = visible #self.plane = plane #self._scene.add(plane) # add slider slider = ipywidgets.FloatSlider(value=pos, min=-length/2, max=length/2, description='%s pos'%name) self._clip_slider[name] = slider wslider = None if width is not None: wslider = ipywidgets.FloatSlider(value=width, min=0, max=length/2, description='%s width'%name) self._clip_slider[name+'_width'] = wslider # add object clip_obj = {"geometry": geometry, "mesh": plane, "material": material, "bounds":bounds, "max":ma, "min":mi, "type": "Clip", "direction" : direction, "width" : width, "wslider" : wslider, "slider" : slider, "wireframe": None} out = self.__add_object(name, clip_obj) # setup interaction def update_clip(change): clips = [] for k,o in self._objects.items(): if o['type'] == 'Clip': s = o['slider'] i = s.value plane = o['mesh'] direction = o['direction'] width = o['width'] if width is not None: width = o['wslider'].value plane.position = [c+o for c,o in zip(self._centroid, i*direction) ] clips.append(p3s.Plane(tuple([v for v in direction]),0.05-np.dot(plane.position, direction))) if width is not None: clips.append(p3s.Plane(tuple([-v for v in direction]),width-np.dot(plane.position, -direction))) #self._renderer.clippingPlanes = [p3s.Plane(tuple([v for v in direction]),0.05-np.dot(plane.position, direction))] self._renderer.clippingPlanes = clips # update clipping planes update_clip(None) slider.observe(update_clip) if width is not None: wslider.observe(update_clip) return out def show(self)-
Expand source code
def show(self): from ipywidgets import AppLayout from ipywidgets import HTML left = [ self._size_slider]+list(self._clip_slider.values()) left += [HTML('<hr/>')] for k,v in self._objects.items(): left.append( ipywidgets.Checkbox( value=v['mesh'].visible, description=k, disabled=False, indent=True ) ) ipywidgets.jslink((left[-1], 'value'), (v['mesh'], 'visible')) store = ipywidgets.Button(description="Store point") def clk(e): if self._picker is not None: if self._picker.index is not None: self.pickedPoints.append( self._picker.point ) self.pickedIDs.append( self._picker.index ) # add mesh ps = 0.1 if self._size_slider is not None: ps = self._size_slider.value hover_point = p3s.Mesh(geometry=p3s.SphereGeometry(radius=2*ps), material=p3s.MeshBasicMaterial(color='yellow')) self._scene.add(hover_point) store.on_click(clk) right = [store] return AppLayout(header=None, left_sidebar=ipywidgets.VBox([i for i in left if i is not None]), center=self._renderer, right_sidebar=ipywidgets.VBox([i for i in right if i is not None]), footer=None)