Source code for nglview.widget

import base64
import json
import logging
import threading
import time
import uuid

import ipywidgets as widgets
import ipywidgets.embed
import numpy as np
from IPython.display import display
from ipywidgets import (Image, Box, DOMWidget, HBox, VBox, IntSlider, Output, Play, Widget,
                        jslink)
from ipywidgets import widget as _widget
from traitlets import (Bool, CaselessStrEnum, Dict, Instance, Int, Integer,
                       List, Unicode, observe, validate)
import traitlets

from . import color, interpolate
from .adaptor import Structure, Trajectory
from .component import ComponentViewer
from .config import BACKENDS
from .player import TrajectoryPlayer, _dry_run
from .remote_thread import RemoteCallThread
from .representation import RepresentationControl
from .shape import Shape
from .stage import Stage
from .utils import py_utils, widget_utils
from .utils.py_utils import (FileManager, _camelize_dict, _update_url,
                             encode_base64, get_repr_names_from_dict,
                             seq_to_string)
from .viewer_control import ViewerControl
from ._frontend import __frontend_version__
from .base import BaseWidget

widget_serialization = _widget.widget_serialization

__all__ = ['NGLWidget', 'ComponentViewer']
_EXCLUDED_CALLBACK_AFTER_FIRING = {
    'setUnSyncCamera',
    'setSelector',
    'setDelay',
    'autoView',
    '_downloadImage',
    '_exportImage',
    'set_representation_from_backend',
}


def _deprecated(msg):
    def wrap_1(func):
        def wrap_2(*args, **kwargs):
            logging.warn(msg)
            return func(*args, **kwargs)

        return wrap_2

    return wrap_1


[docs]def write_html(fp, views, frame_range=None): # type: (str, List[NGLWidget]) -> None """EXPERIMENTAL. Likely will be changed. Make html file to display a list of views. For further options, please check `ipywidgets.embed` module. Parameters ---------- fp : str or file handle views : a DOMWidget view or a list of views. frame_range : None or a tuple of int Examples -------- >>> import nglview >>> view = nglview.show_pdbid('1tsu') >>> view # doctest: +SKIP >>> nglview.write_html('index.html', [view]) # doctest: +SKIP >>> nglview.write_html('index.html', [view], frame_range=(0, 5)) # doctest: +SKIP """ views = isinstance(views, DOMWidget) and [views] or views embed = ipywidgets.embed color = None theme = None for k, v in views[0].widgets.items(): if v.__class__.__name__ == '_ColormakerRegistry': color = v if v.__class__.__name__ == 'ThemeManager': theme = v for v in [color, theme]: v and views.insert(0, v) def _set_serialization(views): for view in views: if hasattr(view, '_set_serialization'): view._set_serialization(frame_range=frame_range) elif isinstance(view, Box): _set_serialization(view.children) def _unset_serialization(views): for view in views: if hasattr(view, '_unset_serialization'): view._unset_serialization() elif isinstance(view, Box): _unset_serialization(view.children) _set_serialization(views) # FIXME: allow add jquery-ui link? snippet = '<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.0/jquery-ui.css">\n' snippet += embed.embed_snippet(views) html_code = embed.html_template.format(title='nglview-demo', snippet=snippet) # from ipywidgets # Check if fp is writable: if hasattr(fp, 'write'): fp.write(html_code) else: # Assume fp is a filename: with open(fp, "w") as f: f.write(html_code) _unset_serialization(views)
[docs]class NGLWidget(DOMWidget): _view_name = Unicode("NGLView").tag(sync=True) _view_module = Unicode("nglview-js-widgets").tag(sync=True) _view_module_version = Unicode(__frontend_version__).tag(sync=True) _model_name = Unicode("NGLModel").tag(sync=True) _model_module = Unicode("nglview-js-widgets").tag(sync=True) _model_module_version = Unicode(__frontend_version__).tag(sync=True) _ngl_version = Unicode().tag(sync=True) # _model_name = Unicode("NGLView").tag(sync=True) # _model_module = Unicode("nglview-js-widgets").tag(sync=True) _image_data = Unicode().tag(sync=False) # use Integer here, because mdtraj uses a long datatype here on Python-2.7 frame = Integer().tag(sync=True) max_frame = Int(0).tag(sync=True) background = Unicode('white').tag(sync=True) loaded = Bool(False).tag(sync=False) picked = Dict().tag(sync=True) n_components = Int(0).tag(sync=True) _view_width = Unicode().tag(sync=True) # px _view_height = Unicode().tag(sync=True) # px _scene_position = Dict().tag(sync=True) _scene_rotation = Dict().tag(sync=True) # hack to always display movie # TODO: remove _parameters? _parameters = Dict().tag(sync=False) _ngl_full_stage_parameters = Dict().tag(sync=True) _ngl_original_stage_parameters = Dict().tag(sync=True) _coordinates_dict = Dict().tag(sync=False) _camera_str = CaselessStrEnum(['perspective', 'orthographic'], default_value='orthographic').tag(sync=True) _camera_orientation = List().tag(sync=True) _synced_model_ids = List().tag(sync=True) _synced_repr_model_ids = List().tag(sync=True) _ngl_view_id = List().tag(sync=True) _ngl_repr_dict = Dict().tag(sync=True) _ngl_component_ids = List().tag(sync=False) _ngl_component_names = List().tag(sync=False) _ngl_msg = None _send_binary = Bool(True).tag(sync=False) _init_gui = Bool(False).tag(sync=False) gui_style = CaselessStrEnum(['ngl'], allow_none=True).tag(sync=True) _gui_theme = CaselessStrEnum(['dark', 'light'], allow_none=True).tag(sync=True) _widget_theme = None _ngl_serialize = Bool(False).tag(sync=True) _ngl_msg_archive = List().tag(sync=True) _ngl_coordinate_resource = Dict().tag(sync=True) _representations = List().tag(sync=False) _ngl_color_dict = Dict().tag(sync=True) _player_dict = Dict().tag(sync=True) # instance _iplayer = Instance(widgets.Box, allow_none=True).tag(sync=True, **widget_serialization) _igui = Instance(widgets.Tab, allow_none=True).tag(sync=True, **widget_serialization) _ibtn_fullscreen = Instance(widgets.Button, allow_none=True).tag(sync=True, **widget_serialization) def __init__(self, structure=None, representations=None, parameters=None, **kwargs): super().__init__(**kwargs) self._gui = None self._init_gui = kwargs.pop('gui', False) self._theme = kwargs.pop('theme', 'default') self._widget_image = Image() self._widget_image.width = 900. self._image_array = [] # do not use _displayed_callbacks since there is another Widget._display_callbacks self._event = threading.Event() self._ngl_displayed_callbacks_before_loaded = [] widget_utils._add_repr_method_shortcut(self, self) self.shape = Shape(view=self) self.stage = Stage(view=self) self.control = ViewerControl(view=self) self._handle_msg_thread = threading.Thread( target=self.on_msg, args=(self._ngl_handle_msg, )) # # register to get data from JS side self._handle_msg_thread.daemon = True self._handle_msg_thread.start() self._remote_call_thread = RemoteCallThread( self, registered_funcs=['loadFile', 'replaceStructure', '_exportImage']) self._remote_call_thread.start() self._trajlist = [] self._ngl_component_ids = [] if representations: # Must be set here before calling # add_trajectory or add_struture # After finish adding new Structure/Trajectory, # initial representations will be set. kwargs['default_representation'] = False else: if 'default' in kwargs: kwargs['default_representation'] = kwargs['default'] autoview = 'center' not in kwargs or ('center' in kwargs and kwargs.pop('center')) # NOTE: Using `pop` to avoid passing `center` to NGL. if parameters: self.parameters = parameters if isinstance(structure, Trajectory): name = py_utils.get_name(structure, kwargs) self.add_trajectory(structure, name=name, **kwargs) elif isinstance(structure, (list, tuple)): trajectories = structure for trajectory in trajectories: name = py_utils.get_name(trajectory, kwargs) self.add_trajectory(trajectory, name=name, **kwargs) else: if structure is not None: self.add_structure(structure, **kwargs) if representations: # If initial representations are provided, # we need to set defaultRepresentation to False self.representations = representations if autoview: self.center() self.player = TrajectoryPlayer(self) self._view_width = kwargs.get('width', '') self._view_height = kwargs.get('height', '') # Updating only self.layout.{width, height} don't handle # resizing NGL widget properly. self._sync_with_layout() # self.layout.width = 'auto' self._create_player() self._create_ibtn_fullscreen() def _create_ibtn_fullscreen(self): button = widgets.Button(icon='compress') button.layout.width = '34px' # onclick is implemented in frontend self._ibtn_fullscreen = button def _sync_with_layout(self): def on_change_layout(change): new = change['new'] if change['name'] == 'width': self._set_size(new, '') elif change['name'] == 'height': self._set_size('', new) self.layout.observe(on_change_layout, ['width', 'height']) def _set_serialization(self, frame_range=None): self._ngl_serialize = True resource = self._ngl_coordinate_resource if frame_range is not None: for t_index, traj in enumerate(self._trajlist): resource[t_index] = [] for f_index in range(*frame_range): if f_index < traj.n_frames: resource[t_index].append( encode_base64(traj.get_coordinates(f_index))) else: resource[t_index].append( encode_base64(np.empty((0), dtype='f4'))) resource['n_frames'] = len(resource[0]) self._ngl_coordinate_resource = resource self._ngl_color_dict = color._USER_COLOR_DICT.copy() def _create_player(self): player = Play(max=self.max_frame, interval=100) slider = IntSlider(max=self.max_frame) self._iplayer = HBox([player, slider]) self.player.widget_player = player self.player.widget_player_slider = slider jslink((player, 'value'), (slider, 'value')) jslink((player, 'value'), (self, 'frame')) jslink((player, 'max'), (self, 'max_frame')) jslink((slider, 'max'), (self, 'max_frame')) def _unset_serialization(self): self._ngl_serialize = False self._ngl_coordinate_resource = {} @property def parameters(self): return self._parameters @parameters.setter def parameters(self, params): params = _camelize_dict(params) self._parameters = params self._remote_call('setParameters', target='Widget', args=[ params, ]) @property def camera(self): return self._camera_str @camera.setter def camera(self, value): """ Parameters ---------- value : str, {'perspective', 'orthographic'} """ self._camera_str = value # use _remote_call so this function can be called right after # self is displayed self._remote_call("setParameters", target='Stage', kwargs=dict(cameraType=self._camera_str)) def _set_camera_orientation(self, arr): self._remote_call('set_camera_orientation', target='Widget', args=[ arr, ]) def _request_stage_parameters(self): self._remote_call('requestUpdateStageParameters', target='Widget') @validate('gui_style') def _validate_gui_style(self, proposal): val = proposal['value'] if val == 'ngl': if self._widget_theme is None: from .theme import ThemeManager self._widget_theme = ThemeManager() if self._widget_theme._theme is None: self._widget_theme.light() return val @observe("_gui_theme") def _on_theme_changed(self, change): # EXPERIMENTAL from nglview.theme import theme if change.new == 'dark': self._widget_theme.dark() elif change.new == 'light': self._widget_theme.light() @observe('picked') def _on_picked(self, change): picked = change['new'] if self.player.widget_picked is not None: self.player.widget_picked.value = json.dumps(picked) @observe('background') def _update_background_color(self, change): color = change['new'] self.stage.set_parameters(background_color=color)
[docs] def handle_resize(self): # self._remote_call("handleResize", target='Stage') self._remote_call("handleResize")
@observe('n_components') def _handle_n_components_changed(self, change): if self.player.widget_repr is not None: component_slider = widget_utils.get_widget_by_name( self.player.widget_repr, 'component_slider') if change['new'] - 1 >= component_slider.min: component_slider.max = change['new'] - 1 component_dropdown = widget_utils.get_widget_by_name( self.player.widget_repr, 'component_dropdown') component_dropdown.options = tuple(self._ngl_component_names) if change['new'] == 0: component_dropdown.options = tuple([' ']) component_dropdown.value = ' ' component_slider.max = 0 reprlist_choices = widget_utils.get_widget_by_name( self.player.widget_repr, 'reprlist_choices') reprlist_choices.options = tuple([' ']) repr_slider = widget_utils.get_widget_by_name( self.player.widget_repr, 'repr_slider') repr_slider.max = 0 repr_name_text = widget_utils.get_widget_by_name( self.player.widget_repr, 'repr_name_text') repr_selection = widget_utils.get_widget_by_name( self.player.widget_repr, 'repr_selection') repr_name_text.value = ' ' repr_selection.value = ' ' @observe('_ngl_repr_dict') def _handle_repr_dict_changed(self, change): if self.player.widget_repr is not None: repr_slider = widget_utils.get_widget_by_name( self.player.widget_repr, 'repr_slider') component_slider = widget_utils.get_widget_by_name( self.player.widget_repr, 'component_slider') repr_name_text = widget_utils.get_widget_by_name( self.player.widget_repr, 'repr_name_text') repr_selection = widget_utils.get_widget_by_name( self.player.widget_repr, 'repr_selection') reprlist_choices = widget_utils.get_widget_by_name( self.player.widget_repr, 'reprlist_choices') repr_names = get_repr_names_from_dict(self._ngl_repr_dict, component_slider.value) if change['new'] == {0: {}}: repr_selection.value = '' else: options = tuple( str(i) + '-' + name for (i, name) in enumerate(repr_names)) reprlist_choices.options = options try: value = reprlist_choices.options[repr_slider.value] if isinstance(value, tuple): # https://github.com/jupyter-widgets/ipywidgets/issues/1512 value = value[0] reprlist_choices.value = value except IndexError: if repr_slider.value == 0: # works fine with ipywidgets 5.2.2 reprlist_choices.options = tuple([ ' ', ]) reprlist_choices.value = ' ' else: reprlist_choices.value = reprlist_choices.options[ repr_slider.value - 1] # e.g: 0-cartoon repr_name_text.value = reprlist_choices.value.split( '-')[-1].strip() repr_slider.max = len(repr_names) - 1 if len( repr_names) >= 1 else len(repr_names) def _update_max_frame(self): self.max_frame = max( int(traj.n_frames) for traj in self._trajlist if hasattr(traj, 'n_frames')) - 1 # index starts from 0 def _wait_until_finished(self, timeout=0.0001): # NGL need to send 'finished' signal to # backend self._event.clear() while True: # idle to make room for waiting for # "finished" event sent from JS time.sleep(timeout) if self._event.is_set(): # if event is set from another thread # break while True break def _run_on_another_thread(self, func, *args): # use `event` to singal # func(*args) thread = threading.Thread( target=func, args=args, ) thread.daemon = True thread.start() return thread @observe('loaded') def on_loaded(self, change): # trick for firefox on Linux time.sleep(0.1) if change['new']: self._fire_callbacks(self._ngl_displayed_callbacks_before_loaded) def _fire_callbacks(self, callbacks): def _call(event): for callback in callbacks: callback(self) if callback._method_name == 'loadFile': self._wait_until_finished() self._run_on_another_thread(_call, self._event) def _ipython_display_(self, **kwargs): super()._ipython_display_(**kwargs) if self._init_gui: if self._gui is None: self._gui = self.player._display() display(self._gui)
[docs] def display(self, gui=False, style='ngl'): """ Parameters ---------- gui : bool If True: turn on GUI style : str, {'ngl', 'ipywidgets}, default 'ngl' GUI style (with gui=True) """ if gui: if style == 'ipywidgets': # For the old implementation # is there anyone using this? self.gui_style = None # turn off the NGL's GUI self._gui = self.player._display() self._gui.layout.align_self = 'stretch' self._gui.layout.width = '400px' b = HBox([self, self._gui]) def on(b): self.handle_resize() b.on_displayed(on) return b elif style == 'ngl': self.gui_style = 'ngl' return self else: return self
def _set_size(self, w, h): ''' Parameters ---------- w, h : float or str Examples -------- >>> import nglview; view = nglview.demo() >>> view._set_size(100, 100) >>> view._set_size('100px', '100px') >>> view._set_size('50%', '50%') ''' self._remote_call('setSize', target='Widget', args=[w, h]) def _set_sync_repr(self, other_views): model_ids = {v._model_id for v in other_views} self._synced_repr_model_ids = sorted( set(self._synced_repr_model_ids) | model_ids) self._remote_call("setSyncRepr", target="Widget", args=[self._synced_repr_model_ids]) def _set_unsync_repr(self, other_views): model_ids = {v._model_id for v in other_views} self._synced_repr_model_ids = list(set(self._synced_repr_model_ids) - model_ids) self._remote_call("setSyncRepr", target="Widget", args=[self._synced_repr_model_ids]) def _set_sync_camera(self, other_views): model_ids = {v._model_id for v in other_views} self._synced_model_ids = sorted( set(self._synced_model_ids) | model_ids) self._remote_call("setSyncCamera", target="Widget", args=[self._synced_model_ids]) def _set_unsync_camera(self, other_views): model_ids = {v._model_id for v in other_views} self._synced_model_ids = list(set(self._synced_model_ids) - model_ids) self._remote_call("setSyncCamera", target="Widget", args=[self._synced_model_ids]) def _set_spin(self, axis, angle): self._remote_call('setSpin', target='Stage', args=[axis, angle]) def _set_selection(self, selection, component=0, repr_index=0): self._remote_call("setSelection", target='Representation', args=[selection], kwargs=dict(component_index=component, repr_index=repr_index))
[docs] def color_by(self, color_scheme, component=0): '''update color for all representations of given component Notes ----- Unstable feature Parameters ---------- color_scheme : str component : int, default 0 component index Examples -------- >>> import nglview >>> view = nglview.demo() >>> # component 0 >>> view.color_by('atomindex') >>> # component 1 >>> view.color_by('atomindex', component=1) ''' repr_names = get_repr_names_from_dict(self._ngl_repr_dict, component) for index, _ in enumerate(repr_names): self.update_representation(component=component, repr_index=index, color_scheme=color_scheme)
@property def representations(self): return self._representations @representations.setter def representations(self, reps): if isinstance(reps, dict): self._remote_call("_set_representation_from_repr_dict", args=[reps]) else: self._representations = reps[:] for index in range(len(self._ngl_component_ids)): self.set_representations(reps)
[docs] def update_representation(self, component=0, repr_index=0, **parameters): """ Parameters ---------- component : int, default 0 component index repr_index : int, default 0 representation index for given component parameters : dict """ parameters = _camelize_dict(parameters) kwargs = dict(component_index=component, repr_index=repr_index) kwargs.update(parameters) self._remote_call('setParameters', target='Representation', kwargs=kwargs) self._update_repr_dict()
def _update_repr_dict(self): """ Send a request to fronend to send representation parameters back. # TODO: sync or async """ self._remote_call('request_repr_dict', target='Widget')
[docs] def set_representations(self, representations, component=0): """ Parameters ---------- representations : list of dict """ self.clear_representations(component=component) for params in representations: assert isinstance(params, dict), 'params must be a dict' kwargs = params['params'] kwargs.update({'component_index': component}) self._remote_call('addRepresentation', target='compList', args=[ params['type'], ], kwargs=kwargs)
def _remove_representation(self, component=0, repr_index=0): self._remote_call('removeRepresentation', target='Widget', args=[component, repr_index]) def _remove_representations_by_name(self, repr_name, component=0): self._remote_call('removeRepresentationsByName', target='Widget', args=[repr_name, component]) def _update_representations_by_name(self, repr_name, component=0, **kwargs): kwargs = _camelize_dict(kwargs) self._remote_call('updateRepresentationsByName', target='Widget', args=[repr_name, component], kwargs=kwargs) def _display_repr(self, component=0, repr_index=0, name=None): c = 'c' + str(component) r = str(repr_index) try: name = self._ngl_repr_dict[c][r]['type'] except KeyError: name = '' return RepresentationControl(self, component, repr_index, name=name) def _set_coordinates(self, index, movie_making=False, render_params=None): # FIXME: use movie_making here seems awkward. '''update coordinates for all trajectories at index-th frame ''' render_params = render_params or {} if self._trajlist: coordinates_dict = {} for trajectory in self._trajlist: traj_index = self._ngl_component_ids.index(trajectory.id) try: if trajectory.shown: if self.player.interpolate: t = self.player.iparams.get('t', 0.5) step = self.player.iparams.get('step', 1) coordinates_dict[traj_index] = interpolate.linear( index, t=t, traj=trajectory, step=step) else: coordinates_dict[ traj_index] = trajectory.get_coordinates(index) else: coordinates_dict[traj_index] = np.empty((0), dtype='f4') except (IndexError, ValueError): coordinates_dict[traj_index] = np.empty((0), dtype='f4') self.set_coordinates(coordinates_dict, render_params=render_params, movie_making=movie_making) else: print("no trajectory available")
[docs] def set_coordinates(self, arr_dict, movie_making=False, render_params=None): # type: (Dict[int, np.ndarray]) -> None """Used for update coordinates of a given trajectory >>> # arr: numpy array, ndim=2 >>> # update coordinates of 1st trajectory >>> view.set_coordinates({0: arr})# doctest: +SKIP """ render_params = render_params or {} self._coordinates_dict = arr_dict buffers = [] coordinates_meta = dict() for index, arr in self._coordinates_dict.items(): buffers.append(arr.astype('f4').tobytes()) coordinates_meta[index] = index msg = { 'type': 'binary_single', 'data': coordinates_meta, } if movie_making: msg['movie_making'] = movie_making msg['render_params'] = render_params self.send( msg, buffers=buffers)
@observe('frame') def _on_frame_changed(self, change): """set and send coordinates at current frame """ self._set_coordinates(self.frame)
[docs] def clear(self, *args, **kwargs): '''shortcut of `clear_representations` ''' self.clear_representations(*args, **kwargs)
[docs] def clear_representations(self, component=0): '''clear all representations for given component Parameters ---------- component : int, default 0 (first model) You need to keep track how many components you added. ''' self._remote_call("removeAllRepresentations", target='compList', kwargs={'component_index': component})
@_update_url def _add_shape(self, shapes, name='shape'): """add shape objects TODO: update doc, caseless shape keyword Parameters ---------- shapes : list of tuple name : str, default 'shape' name of given shape Notes ----- Supported shape: 'mesh', 'sphere', 'ellipsoid', 'cylinder', 'cone', 'arrow'. See also -------- {ngl_url} Examples -------- >>> import nglview >>> view = nglview.demo() >>> sphere = ('sphere', [0, 0, 9], [1, 0, 0], 1.5) >>> arrow = ('arrow', [1, 2, 7 ], [30, 3, 3], [1, 0, 1], 1.0) >>> c = view._add_shape([sphere, arrow], name='my_shape') """ self._remote_call('addShape', target='Widget', args=[name, shapes], fire_embed=True) # Added to remain in sync with the JS components # Similarly to _loadData cid = str(uuid.uuid4()) self._ngl_component_ids.append(cid) comp_name = py_utils.get_name(self.shape, {}) self._ngl_component_names.append(comp_name) self._update_component_auto_completion() return ComponentViewer(self, cid)
[docs] @_update_url def add_representation(self, repr_type, selection='all', **kwargs): '''Add structure representation (cartoon, licorice, ...) for given atom selection. Parameters ---------- repr_type : str type of representation. Please see {ngl_url} for further info. selection : str or 1D array (atom indices) or any iterator that returns integer, default 'all' atom selection **kwargs: additional arguments for representation Example ------- >>> import nglview as nv >>> import pytraj >>> t = pytraj.datafiles.load_tz2() >>> w = nv.show_pytraj(t) >>> w.add_representation('cartoon', selection='protein', color='blue') >>> w.add_representation('licorice', selection=[3, 8, 9, 11], color='red') >>> w # doctest: +SKIP Notes ----- User can also use shortcut >>> selection = 'protein' >>> w.add_cartoon(selection) # w.add_representation('cartoon', selection) ''' if repr_type == 'surface': if 'useWorker' not in kwargs: kwargs['useWorker'] = False # avoid space sensitivity repr_type = repr_type.strip() # overwrite selection selection = seq_to_string(selection).strip() # make copy kwargs2 = _camelize_dict(kwargs) if 'component' in kwargs2: component = kwargs2.pop('component') else: component = 0 for k, v in kwargs2.items(): try: kwargs2[k] = v.strip() except AttributeError: # e.g.: opacity=0.4 kwargs2[k] = v d = {'params': {'sele': selection}} d['type'] = repr_type d['params'].update(kwargs2) params = d['params'] params.update({'component_index': component}) self._remote_call('addRepresentation', target='compList', args=[ d['type'], ], kwargs=params)
[docs] @_deprecated("DEPRECATED: Please use 'center' method") def center_view(self, *args, **kwargs): """alias of `center_view` """ self.center(*args, **kwargs)
[docs] def center(self, selection='*', duration=0, component=0, **kwargs): """center view for given atom selection Examples -------- view.center_view(selection='1-4') """ self._remote_call('autoView', target='compList', args=[selection, duration], kwargs={'component_index': component}, **kwargs)
@observe('_image_data') def _on_render_image(self, change): '''update image data to widget_image Notes ----- method name might be changed ''' self._widget_image._b64value = change['new']
[docs] def render_image(self, frame=None, factor=4, antialias=True, trim=False, transparent=False): """render and get image as ipywidgets.widget_image.Image Parameters ---------- frame : int or None, default None if None, use current frame if specified, use this number. factor : int, default 4 quality of the image, higher is better antialias : bool, default True trim : bool, default False transparent : bool, default False Examples -------- # tell NGL to render send image data to notebook. view.render_image() # make sure to call `get_image` method view.get_image() Notes ----- You need to call `render_image` and `get_image` in different notebook's Cells """ if frame is not None: self.frame = frame params = dict(factor=factor, antialias=antialias, trim=trim, transparent=transparent) iw = Image() iw.width = '99%' # avoid ugly scroll bar on notebook. self._remote_call('_exportImage', target='Widget', args=[iw.model_id], kwargs=params) # iw.value will be updated later after frontend send the image_data back. return iw
[docs] def download_image(self, filename='screenshot.png', factor=4, antialias=True, trim=False, transparent=False): """render and download scene at current frame Parameters ---------- filename : str, default 'screenshot.png' factor : int, default 4 quality of the image, higher is better antialias : bool, default True trim : bool, default False transparent : bool, default False """ params = dict(factor=factor, antialias=antialias, trim=trim, transparent=transparent) self._remote_call('_downloadImage', target='Widget', args=[ filename, ], kwargs=params)
def _ngl_handle_msg(self, widget, msg, buffers): """store message sent from Javascript. How? use view.on_msg(get_msg) Notes: message format should be {'type': type, 'data': data} _ngl_handle_msg will call appropriate function to handle message "type" """ self._ngl_msg = msg msg_type = self._ngl_msg.get('type') if msg_type == 'request_frame': frame = self.frame + self.player.step if frame > self.max_frame: frame = 0 elif frame < 0: frame = self.max_frame self.frame = frame elif msg_type == 'updateIDs': self._ngl_view_id = msg['data'] elif msg_type == 'removeComponent': cindex = int(msg['data']) self._ngl_component_ids.pop(cindex) elif msg_type == 'repr_parameters': data_dict = self._ngl_msg.get('data') name = data_dict.pop('name') + '\n' selection = data_dict.get('sele', '') + '\n' # json change True to true data_dict_json = json.dumps(data_dict).replace( 'true', 'True').replace('false', 'False') data_dict_json = data_dict_json.replace('null', '"null"') if self.player.widget_repr is not None: # TODO: refactor repr_name_text = widget_utils.get_widget_by_name( self.player.widget_repr, 'repr_name_text') repr_selection = widget_utils.get_widget_by_name( self.player.widget_repr, 'repr_selection') repr_name_text.value = name repr_selection.value = selection elif msg_type == 'request_loaded': if not self.loaded: # trick to trigger observe loaded # so two viewers can have the same representations self.loaded = False self.loaded = msg.get('data') elif msg_type == 'request_repr_dict': # update _repr_dict will trigger other things # see _handle_repr_dict_changed self._ngl_repr_dict = self._ngl_msg.get('data') elif msg_type == 'stage_parameters': self._ngl_full_stage_parameters = msg.get('data') elif msg_type == 'async_message': if msg.get('data') == 'ok': self._event.set() elif msg_type == 'image_data': self._image_data = msg.get('data') Widget.widgets[msg.get('ID')].value = base64.b64decode( self._image_data) def _request_repr_parameters(self, component=0, repr_index=0): if self.n_components > 0: self._remote_call('requestReprParameters', target='Widget', args=[component, repr_index])
[docs] def add_structure(self, structure, **kwargs): '''add structure to view Parameters ---------- structure : nglview.Structure object Examples -------- >>> view.add_trajectory(traj0) # doctest: +SKIP ... view.add_trajectory(traj1) ... # then add Structure ... view.add_structure(s) See Also -------- nglview.NGLWidget.add_component ''' if not isinstance(structure, Structure): raise ValueError(f'{structure} is not an instance of Structure') self._load_data(structure, **kwargs) self._ngl_component_ids.append(structure.id) if self.n_components > 1: self.center_view(component=len(self._ngl_component_ids) - 1) self._update_component_auto_completion() return self[-1]
[docs] def add_trajectory(self, trajectory, **kwargs): '''add new trajectory to `view` Parameters ---------- trajectory: nglview.Trajectory or its derived class or a supported object, eg pytraj.Trajectory-like, mdtraj.Trajectory, MDAnalysis objects, etc See Also -------- nglview.NGLWidget.add_component Examples -------- >>> import nglview as nv, pytraj as pt >>> traj = pt.load(nv.datafiles.TRR, nv.datafiles.PDB) >>> view = nv.show_pytraj(traj) >>> # show view first >>> view # doctest: +SKIP >>> # add new Trajectory >>> traj2 = pt.datafiles.load_tz2() >>> c = view.add_trajectory(traj2) ''' backends = BACKENDS package_name = trajectory.__module__.split('.')[0] if package_name in backends: trajectory = backends[package_name](trajectory) else: trajectory = trajectory self._load_data(trajectory, **kwargs) setattr(trajectory, 'shown', True) self._trajlist.append(trajectory) self._update_max_frame() self._ngl_component_ids.append(trajectory.id) self._update_component_auto_completion() return self[-1]
[docs] def add_pdbid(self, pdbid, **kwargs): '''add new Structure view by fetching pdb id from rcsb Examples -------- >>> import nglview >>> view = nglview.NGLWidget() >>> c = view.add_pdbid('1tsu') >>> # which is equal to >>> # view.add_component('rcsb://1tsu.pdb') ''' return self.add_component(f'rcsb://{pdbid}.pdb', **kwargs)
[docs] def add_component(self, filename, **kwargs): '''add component from file/trajectory/struture Parameters ---------- filename : str or Trajectory or Structure or their derived class or url **kwargs : additional arguments, optional Examples -------- >>> import nglview >>> view = nglview.NGLWidget() >>> view # doctest: +SKIP ... filename = 'somefile.ccp4' ... view.add_component(filename) Notes ----- If you want to load binary file such as density data, mmtf format, it is faster to load file from current or subfolder. ''' # if passed a supported object, convert "filename" to nglview.Trajectory try: package_name = filename.__module__.split('.')[0] except (TypeError, AttributeError): # string filename pass else: if package_name in BACKENDS: filename = BACKENDS[package_name](filename) self._load_data(filename, **kwargs) # assign an ID self._ngl_component_ids.append(str(uuid.uuid4())) self._update_component_auto_completion() return self[-1]
def _load_data(self, obj, **kwargs): ''' Parameters ---------- obj : nglview.Structure or any object having 'get_structure_string' method or string buffer (open(fn).read()) ''' kwargs2 = _camelize_dict(kwargs) try: is_url = FileManager(obj).is_url except NameError: is_url = False if 'defaultRepresentation' not in kwargs2: kwargs2['defaultRepresentation'] = True if not is_url: if hasattr(obj, 'get_structure_string'): blob = obj.get_structure_string() kwargs2['ext'] = obj.ext passing_buffer = True binary = False else: fh = FileManager(obj, ext=kwargs.get('ext'), compressed=kwargs.get('compressed')) # assume passing string blob = fh.read() passing_buffer = not fh.use_filename if fh.ext is None and passing_buffer: raise ValueError('must provide extension') kwargs2['ext'] = fh.ext binary = fh.is_binary use_filename = fh.use_filename if binary and not use_filename: # send base64 blob = base64.b64encode(blob).decode('utf8') blob_type = 'blob' if passing_buffer else 'path' args = [{'type': blob_type, 'data': blob, 'binary': binary}] else: # is_url blob_type = 'url' url = obj args = [{'type': blob_type, 'data': url, 'binary': False}] name = py_utils.get_name(obj, kwargs2) self._ngl_component_names.append(name) self._remote_call("loadFile", target='Stage', args=args, kwargs=kwargs2)
[docs] def remove_component(self, c): """remove component by its uuid. If isinstance(c, ComponentViewer), `c` won't be associated with `self` Parameters ---------- c : Union[int, ComponentViewer] Examples -------- >>> c0 = view.add_trajectory(traj0) # doctest: +SKIP ... c1 = view.add_trajectory(traj1) ... c2 = view.add_struture(structure) ... # remove last component ... view.remove_component(c2) ... assert c2._view is None """ if isinstance(c, ComponentViewer): component_id = c.id c._view = None else: component_id = c self._clear_component_auto_completion() if self._trajlist: for traj in self._trajlist: if traj.id == component_id: self._trajlist.remove(traj) component_index = self._ngl_component_ids.index(component_id) self._ngl_component_ids.remove(component_id) self._ngl_component_names.pop(component_index) self._remote_call('removeComponent', target='Stage', args=[ component_index, ]) self._update_component_auto_completion()
def _dry_run(self, func, *args, **kwargs): return _dry_run(self, func, *args, **kwargs) def _get_remote_call_msg(self, method_name, target='Widget', args=None, kwargs=None, **other_kwargs): """call NGL's methods from Python. Parameters ---------- method_name : str target : str, {'Stage', 'Viewer', 'compList', 'StructureComponent'} args : list kwargs : dict if target is 'compList', "component_index" could be passed to specify which component will call the method. Examples -------- view._remote_call('loadFile', args=['1L2Y.pdb'], target='Stage', kwargs={'defaultRepresentation': True}) # perform autoView for 1st component # JS code # component = Stage.compList[1]; # component.autoView('*', 200) # python view._remote_call('autoView', target='component', args=['*', 200], kwargs={'component_index': 1}) """ # NOTE: _camelize_dict here? args = [] if args is None else args kwargs = {} if kwargs is None else kwargs msg = {} if 'component_index' in kwargs: msg['component_index'] = kwargs.pop('component_index') if 'repr_index' in kwargs: msg['repr_index'] = kwargs.pop('repr_index') if 'default' in kwargs: kwargs['defaultRepresentation'] = kwargs.pop('default') # Color handling reconstruc_color_scheme = False if 'color' in kwargs and isinstance(kwargs['color'], color._ColorScheme): kwargs['color_label'] = kwargs['color'].data['label'] # overite `color` kwargs['color'] = kwargs['color'].data['data'] reconstruc_color_scheme = True if kwargs.get('colorScheme') == 'volume' and kwargs.get('colorVolume'): assert isinstance(kwargs['colorVolume'], ComponentViewer) kwargs['colorVolume'] = kwargs['colorVolume']._index msg['target'] = target msg['type'] = 'call_method' msg['methodName'] = method_name msg['reconstruc_color_scheme'] = reconstruc_color_scheme msg['args'] = args msg['kwargs'] = kwargs if other_kwargs: msg.update(other_kwargs) return msg def _trim_message(self, messages): messages = messages[:] remove_comps = [(index, msg['args'][0]) for index, msg in enumerate(messages) if msg['methodName'] == 'removeComponent'] if not remove_comps: return messages load_comps = [ index for index, msg in enumerate(messages) if msg['methodName'] in ('loadFile', 'addShape') ] messages_rm = [r[0] for r in remove_comps] messages_rm += [load_comps[r[1]] for r in remove_comps] messages_rm = set(messages_rm) return [ msg for i, msg in enumerate(messages) if i not in messages_rm ] def _remote_call(self, method_name, target='Widget', args=None, kwargs=None, **other_kwargs): msg = self._get_remote_call_msg(method_name, target=target, args=args, kwargs=kwargs, **other_kwargs) def callback(widget, msg=msg): widget.send(msg) callback._method_name = method_name callback._ngl_msg = msg if self.loaded: self._remote_call_thread.q.append(callback) else: # send later # all callbacks will be called right after widget is loaded self._ngl_displayed_callbacks_before_loaded.append(callback) if callback._method_name not in _EXCLUDED_CALLBACK_AFTER_FIRING and \ (not other_kwargs.get("fire_once", False)): archive = self._ngl_msg_archive[:] archive.append(msg) self._ngl_msg_archive = self._trim_message(archive) def _get_traj_by_id(self, itsid): """return nglview.Trajectory or its derived class object """ for traj in self._trajlist: if traj.id == itsid: return traj return None
[docs] def hide(self, indices): """set invisibility for given component/struture/trajectory (by their indices) """ traj_ids = {traj.id for traj in self._trajlist} for index in indices: comp_id = self._ngl_component_ids[index] if comp_id in traj_ids: traj = self._get_traj_by_id(comp_id) traj.shown = False self._remote_call("setVisibility", target='compList', args=[ False, ], kwargs={'component_index': index})
[docs] def show(self, **kwargs): """shortcut of `show_only` """ self.show_only(**kwargs)
[docs] def show_only(self, indices='all', **kwargs): """set visibility for given components (by their indices) Parameters ---------- indices : {'all', array-like}, component index, default 'all' """ traj_ids = {traj.id for traj in self._trajlist} if indices == 'all': indices_ = set(range(self.n_components)) else: indices_ = set(indices) for index, comp_id in enumerate(self._ngl_component_ids): if comp_id in traj_ids: traj = self._get_traj_by_id(comp_id) else: traj = None if index in indices_: args = [ True, ] if traj is not None: traj.shown = True else: args = [ False, ] if traj is not None: traj.shown = False self._remote_call("setVisibility", target='compList', args=args, kwargs={'component_index': index}, **kwargs)
def _js_console(self): self.send(dict(type='get', data='any')) def _get_full_params(self): self.send(dict(type='get', data='parameters')) def _display_image(self): '''for testing ''' from IPython import display im_bytes = base64.b64decode(self._image_data) return display.Image(im_bytes) def _clear_component_auto_completion(self): for index, _ in enumerate(self._ngl_component_ids): name = 'component_' + str(index) delattr(self, name) def _js(self, code, **kwargs): self._execute_js_code(code, **kwargs) def _execute_js_code(self, code, **kwargs): self._remote_call('executeCode', target='Widget', args=[code], **kwargs) def _update_component_auto_completion(self): trajids = [traj.id for traj in self._trajlist] for index, cid in enumerate(self._ngl_component_ids): comp = ComponentViewer(self, cid) name = 'component_' + str(index) setattr(self, name, comp) if cid in trajids: traj_name = 'trajectory_' + str(trajids.index(cid)) setattr(self, traj_name, comp) def __getitem__(self, index): """return ComponentViewer """ postive_index = py_utils.get_positive_index( index, len(self._ngl_component_ids)) return ComponentViewer(self, self._ngl_component_ids[postive_index]) def __iter__(self): """return ComponentViewer """ for i, _ in enumerate(self._ngl_component_ids): yield self[i]
class Fullscreen(DOMWidget): """EXPERIMENTAL """ _view_name = Unicode("FullscreenView").tag(sync=True) _view_module = Unicode("nglview-js-widgets").tag(sync=True) _view_module_version = Unicode(__frontend_version__).tag(sync=True) _model_name = Unicode("FullscreenModel").tag(sync=True) _model_module = Unicode("nglview-js-widgets").tag(sync=True) _model_module_version = Unicode(__frontend_version__).tag(sync=True) _is_fullscreen = Bool().tag(sync=True) def __init__(self, target, views): super().__init__() self._target = target self._views = views def fullscreen(self): self._js("this.fullscreen('%s')" % self._target.model_id) def _js(self, code): msg = {"executeCode": code} self.send(msg) @observe('_is_fullscreen') def _fullscreen_changed(self, change): if not change.new: self._target.layout.height = '300px' self.handle_resize() def handle_resize(self): for v in self._views: v.handle_resize()