def __init__(self, df, to_show, widths, wrap_around, **kwargs): super().__init__(**kwargs) self.add_class("content") d = Event(source=self, watched_events=['wheel', "mousemove", "mouseleave"]) d.on_dom_event(self.event_handler) self.to_show = to_show self.num_rows = min(len(df), to_show if to_show % 2 == 0 else to_show + 1) self.idx = 0 self.wrap_around = wrap_around self.df = df self.records = df.to_records() def row_on_click(event): if event["new"] != -1: self.value = event["new"] self.rows = deque([ _Row(data=self.records[i], widths=widths, on_click=row_on_click, style=["row_even", "row_odd"][i % 2]) for i in range(self.num_rows) ]) self.children = list(self.rows)[0:self.to_show]
class MouseDataWidget(MouseData): """Get data from mouse input, using ipywidget/ipyevent methods (Jupyter: %matplotlib inline) usage: data = MouseDataWidget(); data.display(); ... after input, stores 'data.m' points, with locations 'data.X' and targets 'data.Y' plot image widget available as "data.image" """ def __init__(self, *args, **kwargs): from ipywidgets import Image from ipyevents import Event #self.plot = None # set in super? super().__init__(*args, **kwargs) self.image = Image(value=blank_png, format='png') self.no_drag = Event(source=self.image, watched_events=['dragstart'], prevent_default_action=True) self.events = Event(source=self.image, watched_events=['click', 'contextmenu'], prevent_default_action=True) self.events.on_dom_event(self.__on_click) self.update_plot() def __get_coord(self, event): return np.array([event['dataX'] / self.w, 1 - event['dataY'] / self.h ]) * 2 / (1 - 2 * self.border) - 1 - self.border def update_plot(self): import matplotlib.pyplot as plt from .plot import plotClassify2D fig = plt.figure(figsize=(6, 6)) plt.gcf().add_axes((self.border, self.border, 1 - 2 * self.border, 1 - 2 * self.border)) # full width figure plt.axis(self.lim) plt.gca().set_xticks([]) plt.gca().set_yticks([]) if (self.m > 0) or (self.plot is not None): if self.plot is not None: self.plot(self.X, self.Y) # user-provided plot f'n else: fig.gca().scatter(self.X[:, 0], self.X[:, 1], c=self.Y) # or default self.image.value = plot_to_png(plt.gcf()) self.w, self.h = plt.gcf().canvas.get_renderer( ).get_canvas_width_height() fig.clear() def display(self, fig=None, **kwargs): from IPython.display import display if self.plot is None: print( "Default plot. Left-click to add data; shift/alt/ctrl combos determine target value; right click to remove data." ) display(self.image) def __on_click(self, event): if (event['type'] == 'click'): # left click = add point self.add_point(self.__get_coord(event), wkeys(event)) elif (event['type'] == 'contextmenu') and (self.m > 0): # right click = remove point self.remove_nearest(self.__get_coord(event)) self.update_plot()
class Icon(WidgetWrapper): def __init__(self, fontawesome_icon: str): """Clickable icon Parameters ---------- fontawesome_icon: str icon string from the fontawesome https://fontawesome.com. For example, "fab fa-500px" """ self.el_icon = widgets.HTML( value=f'<i class="{fontawesome_icon}"></i>') self.on_click_callback = lambda: 1 self.event_listener = Event(source=self.el_icon, watched_events=['click']) self.event_listener.on_dom_event(self.fire_on_click_event) @property def widget(self): return self.el_icon def on_click(self, callback: Callable[[], None]): self.on_click_callback = callback def fire_on_click_event(self, event: dict): if event['type'] == 'click': self.on_click_callback()
def construct_arrow_handler(self): """ Should only be run once """ disp_events = Event(source=self, watched_events=['keydown']) def handle_disp(event): if event["type"] == "keydown": self.key_funcs[event["key"]]() disp_events.on_dom_event(handle_disp)
def __init__(self, data, widths, on_click, style, _types=None, **kwargs): super().__init__(**kwargs) self.add_class(style) self.observe(on_click, "value") d = Event(source=self, watched_events=['click']) d.on_dom_event(self.on_click) self.data = data self.cells = [_Cell(x, w) for x, w in zip(data, widths)] self.cells[0].add_class("index") self.children = self.cells
def construct_click_handler(self): """ Should only be run once """ disp_events = Event(source=self, watched_events=['click']) def handle_disp(event): if event["type"] == "click": x = self.images[self.page_index]._size[0] if event["relativeX"] > (x / 2): self.next_page() elif event["relativeX"] < (x / 2): self.previous_page() disp_events.on_dom_event(handle_disp)
class HTML(WidgetWrapper): def __init__(self, html: str): self.el = widgets.HTML(value=html) self.on_click_callback = lambda: 1 self.event_listener = Event(source=self.el, watched_events=['click']) self.event_listener.on_dom_event(self.fire_on_click_event) @property def widget(self): return self.el def on_click(self, callback: Callable[[], None]): self.on_click_callback = callback def fire_on_click_event(self, event: dict): if event['type'] == 'click': self.on_click_callback()
class Button(WidgetWrapper): """An extended button widget that support mouse over event. """ def __init__(self, **kwargs): """Same constructor as `ipywidgets.widgets.Button`""" self.el_btn = widgets.Button(**kwargs) self.el_btn.on_click(self.fire_on_click_event) self.on_click_callback = lambda x: x self.on_mouseenter_callback = lambda x: x self.on_mouseleave_callback = lambda x: x self.event_listener = Event( source=self.el_btn, watched_events=['mouseenter', 'mouseleave']) self.event_listener.on_dom_event(self.fire_mouse_event) @property def widget(self): return self.el_btn def on_click(self, callback: Callable[['Button'], None]): """Register for on click event""" self.on_click_callback = callback def on_mouseenter(self, callback: Callable[['Button'], None]): """Register for on mouse enter event""" self.on_mouseenter_callback = callback def on_mouseleave(self, callback: Callable[['Button'], None]): """Register for on mouse leave event""" self.on_mouseleave_callback = callback def fire_on_click_event(self, _btn: widgets.Button): """Fire the on_click event when users click the button""" self.on_click_callback(self) def fire_mouse_event(self, event: dict): """Show the content on mouse over""" if event['type'] == 'mouseenter': self.on_mouseenter_callback(self) else: self.on_mouseleave_callback(self)
def test_callbacks(): # Test that the initial callbacks look right, that we can add one, and # that clearing removes them all. event_widget = Event() assert len(event_widget._dom_handlers.callbacks) == 0 def noop(event): pass def noop2(event): pass event_widget.on_dom_event(noop) assert noop in event_widget._dom_handlers.callbacks assert len(event_widget._dom_handlers.callbacks) == 1 # Add noop2.... event_widget.on_dom_event(noop2) assert event_widget._dom_handlers.callbacks == [noop, noop2] # Try removing noop2 event_widget.on_dom_event(noop2, remove=True) assert event_widget._dom_handlers.callbacks == [noop] # Finally, clear all callbacks event_widget.reset_callbacks() assert len(event_widget._dom_handlers.callbacks) == 0
def init(*args, **kwargs): global flag, downflag, shiftflag w = widgets.Image(value=m.system.image_data(), width=600) d = Event(source=w, watched_events=[ 'mousedown', 'mouseup', 'mousemove', 'keyup', 'keydown', 'wheel' ]) no_drag = Event(source=w, watched_events=['dragstart'], prevent_default_action=True) d.on_dom_event(listen_mouse) run = widgets.ToggleButton( value=False, description='Run', disabled=False, button_style='', # 'success', 'info', 'warning', 'danger' or '' tooltip='run the simulation', icon='play') pause = widgets.ToggleButton( value=False, description='Pause', disabled=False, button_style='', # 'success', 'info', 'warning', 'danger' or '' tooltip='pause the simulation', icon='pause') reset = widgets.ToggleButton( value=False, description='Reset', disabled=False, button_style='', # 'success', 'info', 'warning', 'danger' or '' tooltip='reset the simulation', icon='stop') def onToggleRun(b): global flag if run.value: run.button_style = 'success' pause.value = False pause.button_style = '' reset.value = False reset.button_style = '' flag = True else: run.button_style = '' flag = False def onTogglePause(b): global flag if pause.value: pause.button_style = 'success' run.value = False run.button_style = '' reset.value = False reset.button_style = '' flag = False else: pause.button_style = '' flag = True def onToggleReset(b): global flag if reset.value: reset.button_style = 'success' pause.value = False pause.button_style = '' run.value = False run.button_style = '' flag = False m.Universe.reset() else: reset.button_style = '' #w = create_simulation() buttons = widgets.HBox([run, pause, reset]) run.observe(onToggleRun, 'value') pause.observe(onTogglePause, 'value') reset.observe(onToggleReset, 'value') box = widgets.VBox([w, buttons]) display(box) def background_threading(): global flag while True: m.Simulator.context_make_current() if flag: m.step() w.value = m.system.image_data() m.Simulator.context_release() time.sleep(0.01) t = threading.Thread(target=background_threading) t.start()
class Core: # All constants that will be injected into global scope in the user"s cell global_constants = { "pi": pi } ignite_globals = _ignite_globals def __init__(self, globals_dict): self.status_text = display(Code(""), display_id=True) self._globals_dict = globals_dict self._methods = {} self.stop_button = Button(description="Stop") self.stop_button.on_click(self.on_stop_button_clicked) self._globals_dict["canvas"] = Canvas() self.kb_mon = Event(source=self.canvas, watched_events=['keydown', 'keyup'], wait=1000 // FRAME_RATE, prevent_default_actions=True) self.output_text = "" self.color_strings = { "default": "#888888" } match_255 = r"(?:(?:2(?:(?:5[0-5])|(?:[0-4][0-9])))|(?:[01]?[0-9]{1,2}))" match_alpha = r"(?:(?:1(?:\.0*)?)|(?:0(?:\.[0-9]*)?))" match_360 = r"(?:(?:3[0-5][0-9])|(?:[0-2]?[0-9]{1,2}))" match_100 = r"(?:100|[0-9]{1,2})" self.regexes = [ re.compile(r"#[0-9A-Fa-f]{6}"), re.compile(r"rgb\({},{},{}\)".format(match_255, match_255, match_255)), re.compile(r"rgba\({},{},{},{}\)".format(match_255, match_255, match_255, match_alpha)), re.compile(r"hsl\({},{}%,{}%\)".format(match_360, match_100, match_100)), re.compile(r"hsla\({},{}%,{}%,{}\)".format(match_360, match_100, match_100, match_alpha)) ] self.width, self.height = DEFAULT_CANVAS_SIZE self.mouse_x = 0 self.mouse_y = 0 self.mouse_is_pressed = False self.key = "" self._keys_held = {} # Settings for drawing text (https://ipycanvas.readthedocs.io/en/latest/drawing_text.html). self.font_settings = { 'size': 12.0, 'font': 'sans-serif', 'baseline': 'top', 'align': 'left' } ### Properties ### @property @ignite_global def canvas(self) -> Canvas: return self._globals_dict["canvas"] @property @ignite_global def mouse_x(self): return self._globals_dict["mouse_x"] @mouse_x.setter def mouse_x(self, val): self._globals_dict["mouse_x"] = val @property @ignite_global def mouse_y(self): return self._globals_dict["mouse_y"] @mouse_y.setter def mouse_y(self, val): self._globals_dict["mouse_y"] = val @property @ignite_global def mouse_is_pressed(self): return self._globals_dict["mouse_is_pressed"] @mouse_is_pressed.setter def mouse_is_pressed(self, val): self._globals_dict["mouse_is_pressed"] = val @property @ignite_global def key(self): return self._globals_dict["key"] @key.setter def key(self, val): self._globals_dict["key"] = val @property @ignite_global def width(self): return self._globals_dict["width"] @width.setter def width(self, val): self._globals_dict["width"] = val self.canvas.width = val @property @ignite_global def height(self): return self._globals_dict["height"] @height.setter def height(self, val): self._globals_dict["height"] = val self.canvas.height = val ### Library init ### # Updates last activity time @staticmethod def refresh_last_activity(): global _sparkplug_last_activity _sparkplug_last_activity = time.time() # Creates canvas and starts thread def start(self, methods): self._methods = methods draw = self._methods.get("draw", None) if draw: self.print_status("Running...") display(self.stop_button) display(self.canvas) self.output_text_code = display(Code(self.output_text), display_id=True) self.canvas.on_mouse_down(self.on_mouse_down) self.canvas.on_mouse_up(self.on_mouse_up) self.canvas.on_mouse_move(self.on_mouse_move) self.kb_mon.on_dom_event(self.handle_kb_event) # Initialize text drawing settings for the canvas. () self.canvas.font = f"{self.font_settings['size']}px {self.font_settings['font']}" self.canvas.text_baseline = 'top' self.canvas.text_align = 'left' thread = threading.Thread(target=self.loop) thread.start() def stop(self, message="Stopped"): global _sparkplug_running if not _sparkplug_running: return _sparkplug_running = False self.print_status(message) self.kb_mon.reset_callbacks() self.kb_mon.close() # Assuming we're using IPython to draw the canvas through the display() function. # Commenting this out for now, it throws exception since it does not derive BaseException # raise IpyExit # Loop method that handles drawing and setup def loop(self): global _sparkplug_active_thread_id, _sparkplug_running # Set active thread to this thread. This will stop any other active thread. current_thread_id = threading.current_thread().native_id _sparkplug_active_thread_id = current_thread_id _sparkplug_running = True self.refresh_last_activity() draw = self._methods.get("draw", None) setup = self._methods.get("setup", None) if setup: try: setup() except Exception as e: self.stop("Error in setup() function: " + str(e)) return while _sparkplug_running: if _sparkplug_active_thread_id != current_thread_id \ or time.time() - _sparkplug_last_activity > NO_ACTIVITY_THRESHOLD: self.stop("Stopped due to inactivity") return if not draw: self.stop("Done drawing.") return with hold_canvas(self.canvas): try: draw() except Exception as e: self.stop("Error in draw() function: " + str(e)) return time.sleep(1 / FRAME_RATE) # Prints status to embedded error box def print_status(self, msg): self.status_text.update(Code(msg)) # Prints output to embedded output box # Can't use @validate_args decorator for functions actually accepting variable arguments @ignite_global def print(self, *args, sep=' ', end='\n', flush=True): global _sparkplug_running self.output_text += sep.join([str(arg) for arg in args]) + end if _sparkplug_running and flush: self.output_text_code.update(Code(self.output_text)) # Update mouse_x, mouse_y, and call mouse_down handler def on_mouse_down(self, x, y): self.refresh_last_activity() self.mouse_x, self.mouse_y = int(x), int(y) self.mouse_is_pressed = True mouse_down = self._methods.get("mouse_down", None) if mouse_down: mouse_down() # Update mouse_x, mouse_y, and call mouse_up handler def on_mouse_up(self, x, y): self.refresh_last_activity() self.mouse_x, self.mouse_y = int(x), int(y) self.mouse_is_pressed = False mouse_up = self._methods.get("mouse_up", None) if mouse_up: mouse_up() # Update mouse_x, mouse_y, and call mouse_moved handler def on_mouse_move(self, x, y): self.refresh_last_activity() self.mouse_x, self.mouse_y = int(x), int(y) mouse_moved = self._methods.get("mouse_moved", None) if mouse_moved: mouse_moved() def on_stop_button_clicked(self, button): self.stop() @extern def handle_kb_event(self, event): pass ### User overrideable functions ### # The function bodies here do not matter, they are discarded @ignite_global(mutable=True) def setup(self): pass @ignite_global(mutable=True) def draw(self): pass @ignite_global(mutable=True) def mouse_up(self): pass @ignite_global(mutable=True) def mouse_down(self): pass @ignite_global(mutable=True) def mouse_moved(self): pass @ignite_global(mutable=True) def key_pressed(self): pass @ignite_global(mutable=True) def key_released(self): pass @ignite_global(mutable=True) def key_repeated(self): pass ### Global functions ### # From .util.helper_functions.keyboard_functions @extern def keys_held(self, *args): pass @extern def key_held(self, *args): pass # From .util.helper_functions.canvas_functions @extern def size(self, *args): pass @extern def fill_style(self): pass @extern def stroke_style(self, *args): pass @extern def clear(self, *args): pass @extern def background(self, *args): pass # From util.helper_functions.rect_functions @extern def rect(self, *args): pass @extern def fill_rect(self, *args): pass @extern def stroke_rect(self, *args): pass @extern def clear_rect(self, *args): pass # From util.helper_functions.square_functions @extern def square(self, *args): pass @extern def stroke_square(self, *args): pass @extern def fill_square(self, *args): pass # From util.helper_functions.circle_functions @extern def circle(self, *args): pass @extern def fill_circle(self, *args): pass @extern def stroke_circle(self, *args): pass # From util.helper_functions.ellipse_functions @extern def ellipse(self, *args): pass @extern def fill_ellipse(self, *args): pass @extern def stroke_ellipse(self, *args): pass # From util.helper_functions.arc_functions @extern def arc(self, *args): pass @extern def fill_arc(self, *args): pass @extern def stroke_arc(self, *args): pass # From util.helper_functions.triangle_functions @extern def triangle(self, *args): pass @extern def fill_triangle(self, *args): pass @extern def stroke_triangle(self, *args): pass # From util.helper_functions.text_functions @extern def text_size(self, *args): pass @extern def text_align(self, *args): pass @extern def text(self, *args): pass # From util.helper_functions.line_functions @extern def draw_line(self, *args): pass @extern def line(self, *args): pass @extern def line_width(self, *args): pass # An alias to line_width @extern def stroke_width(self, *args): pass ### Helper Functions ### # From util.helper_functions.misc_functions @extern def parse_color(self, *args, func_name="parse_color"): pass @extern def color(self, *args): pass @extern def parse_color_string(self, func_name, s): pass @extern def arc_args(self, *args): pass @extern def random(self, *args): pass @extern def randint(self, *args): pass
class ViewInteractiveWidget(Canvas): """Remote controller for VTK render windows. Parameters ---------- allow_wheel : bool, optional Capture wheel events and allow zooming using the mouse wheel. quality : float, optional Full rendering image quality. 100 for best quality, 0 for min quality. Default 85. quick_quality : float, optional Quick rendering image quality during mouse dragging. 100 for best quality, 0 for min quality. Default 50. Keep this number low to allow rapid rendering on limited bandwidth. on_close : callable A callable function with no arguments to be triggered when the widget is destroyed. This is useful to have a callback to close/clean up the render window. """ def __init__(self, render_window, log_events=True, transparent_background=False, allow_wheel=True, quality=85, quick_quality=50, on_close=None, **kwargs): """Accepts a vtkRenderWindow.""" super().__init__(**kwargs) if quality < 0 or quality > 100: raise ValueError('`quality` parameter must be between 0 and 100') self._quality = quality self._render_window = weakref.ref(render_window) self.render_window.SetOffScreenRendering(1) # Force off screen self.transparent_background = transparent_background self._full_quality = quality self._quick_quality = quick_quality # Frame rate (1/renderDelay) self.last_render_time = 0 self.quick_render_delay_sec = 0.01 self.quick_render_delay_sec_range = [0.02, 2.0] self.adaptive_render_delay = True self.last_mouse_move_event = None # refresh if mouse is just moving (not dragging) self.track_mouse_move = False self.message_timestamp_offset = None self.layout.width = '100%' self.layout.height = 'auto' # Set Canvas size from window size self.width, self.height = self.render_window.GetSize() # record first render time tstart = time.time() self.update_canvas() self._first_render_time = time.time() - tstart log.debug('First image in %.5f seconds', self._first_render_time) # this is the minimum time to render anyway self.set_quick_render_delay(self._first_render_time) self.dragging = False self.interaction_events = Event() # Set the throttle or debounce time in millseconds (must be an non-negative integer) # See https://github.com/mwcraig/ipyevents/pull/55 self.interaction_events.throttle_or_debounce = "throttle" self.interaction_events.wait = INTERACTION_THROTTLE self.interaction_events.source = self allowed_events = [ "dragstart", "mouseenter", "mouseleave", "mousedown", "mouseup", "mousemove", "keyup", "keydown", "contextmenu", # prevent context menu from appearing on right-click ] # May be disabled out so that user can scroll through the # notebook using mousewheel if allow_wheel: allowed_events.append("wheel") self.interaction_events.watched_events = allowed_events # self.interaction_events.msg_throttle = 1 # does not seem to have effect self.interaction_events.prevent_default_action = True self.interaction_events.on_dom_event(self.handle_interaction_event) # Errors are not displayed when a widget is displayed, # this variable can be used to retrieve error messages self.error = None # Enable logging of UI events self.log_events = log_events self.logged_events = [] self.elapsed_times = [] self.age_of_processed_messages = [] if hasattr(on_close, '__call__'): self._on_close = on_close else: self._on_close = lambda: None @property def render_window(self): """reference the weak reference""" ren_win = self._render_window() if ren_win is None: raise RuntimeError('VTK render window has closed') return ren_win @property def interactor(self): return self.render_window.GetInteractor() def set_quick_render_delay(self, delay_sec): if delay_sec < self.quick_render_delay_sec_range[0]: delay_sec = self.quick_render_delay_sec_range[0] elif delay_sec > self.quick_render_delay_sec_range[1]: delay_sec = self.quick_render_delay_sec_range[1] self.quick_render_delay_sec = delay_sec def update_canvas(self, force_render=True, quality=75): """Updates the canvas with the current render""" raw_img = self.get_image(force_render=force_render) f = BytesIO() PIL.Image.fromarray(raw_img).save(f, 'JPEG', quality=quality) image = Image(value=f.getvalue(), width=self.width, height=self.height) self.draw_image(image) def get_image(self, force_render=True): if force_render: self.render_window.Render() return self._fast_image @property def _fast_image(self): import vtk.util.numpy_support as nps import vtk arr = vtk.vtkUnsignedCharArray() self.render_window.GetRGBACharPixelData(0, 0, self.width - 1, self.height - 1, 0, arr) data = nps.vtk_to_numpy(arr).reshape(self.height, self.width, -1)[::-1] if self.transparent_background: return data else: # ignore alpha channel return data[:, :, :-1] @throttle(0.1) def full_render(self): try: import time tstart = time.time() self.update_canvas(True, self._full_quality) self.last_render_time = time.time() log.debug('full render in %.5f seconds', time.time() - tstart) except Exception as e: self.error = str(e) def send_pending_mouse_move_event(self): if self.last_mouse_move_event is not None: self.update_interactor_event_data(self.last_mouse_move_event) self.interactor.MouseMoveEvent() self.last_mouse_move_event = None @throttle(0.01) def quick_render(self): try: self.send_pending_mouse_move_event() self.update_canvas(quality=self._quick_quality) if self.log_events: self.elapsed_times.append(time.time() - self.last_render_time) self.last_render_time = time.time() except Exception as e: self.error = str(e) def update_interactor_event_data(self, event): try: if event["event"] == "keydown" or event["event"] == "keyup": key = event["key"] sym = KEY_TO_SYM[key] if key in KEY_TO_SYM.keys() else key self.interactor.SetKeySym(sym) if len(key) == 1: self.interactor.SetKeyCode(key) self.interactor.SetRepeatCount(1) else: self.interactor.SetEventPosition( event["offsetX"], self.height - event["offsetY"]) self.interactor.SetShiftKey(event["shiftKey"]) self.interactor.SetControlKey(event["ctrlKey"]) self.interactor.SetAltKey(event["altKey"]) except Exception as e: self.error = str(e) def handle_interaction_event(self, event): event_name = event["event"] # we have to scale the mouse movement here relative to the # canvas size, otherwise mouse movement won't correspond to # the render window. if 'offsetX' in event: event['offsetX'] = round( event["clientX"] - event["boundingRectLeft"]) #re-calculate coordinates scale_x = self.width / event['boundingRectWidth'] event['offsetX'] = round(event['offsetX'] * scale_x) event['offsetY'] = round( event["clientY"] - event["boundingRectTop"]) #re-calculate coordinates scale_y = self.height / event['boundingRectHeight'] event['offsetY'] = round(event['offsetY'] * scale_y) try: if self.log_events: self.logged_events.append(event) if event_name == "mousemove": if self.message_timestamp_offset is None: self.message_timestamp_offset = ( time.time() - event["timeStamp"] * 0.001) self.last_mouse_move_event = event if not self.dragging and not self.track_mouse_move: return if self.adaptive_render_delay: ageOfProcessedMessage = time.time() - ( event["timeStamp"] * 0.001 + self.message_timestamp_offset) if ageOfProcessedMessage > 1.5 * self.quick_render_delay_sec: # we are falling behind, try to render less frequently self.set_quick_render_delay( self.quick_render_delay_sec * 1.05) elif ageOfProcessedMessage < 0.5 * self.quick_render_delay_sec: # we can keep up with events, try to render more frequently self.set_quick_render_delay( self.quick_render_delay_sec / 1.05) if self.log_events: self.age_of_processed_messages.append([ ageOfProcessedMessage, self.quick_render_delay_sec ]) # We need to render something now it no rendering # since self.quick_render_delay_sec if time.time( ) - self.last_render_time > self.quick_render_delay_sec: self.quick_render() elif event_name == "mouseenter": self.update_interactor_event_data(event) self.interactor.EnterEvent() self.last_mouse_move_event = None elif event_name == "mouseleave": self.update_interactor_event_data(event) self.interactor.LeaveEvent() self.last_mouse_move_event = None if self.dragging: # have to trigger a leave event and release event self.interactor.LeftButtonReleaseEvent() self.dragging = False self.full_render() elif event_name == "mousedown": self.dragging = True self.send_pending_mouse_move_event() self.update_interactor_event_data(event) if event["button"] == 0: self.interactor.LeftButtonPressEvent() elif event["button"] == 2: self.interactor.RightButtonPressEvent() elif event["button"] == 1: self.interactor.MiddleButtonPressEvent() self.full_render() # does this have to be rendered? elif event_name == "mouseup": self.send_pending_mouse_move_event() self.update_interactor_event_data(event) if event["button"] == 0: self.interactor.LeftButtonReleaseEvent() elif event["button"] == 2: self.interactor.RightButtonReleaseEvent() elif event["button"] == 1: self.interactor.MiddleButtonReleaseEvent() self.dragging = False self.full_render() elif event_name == "keydown": self.send_pending_mouse_move_event() self.update_interactor_event_data(event) self.interactor.KeyPressEvent() self.interactor.CharEvent() if (event["key"] != "Shift" and event["key"] != "Control" and event["key"] != "Alt"): self.full_render() elif event_name == "keyup": self.send_pending_mouse_move_event() self.update_interactor_event_data(event) self.interactor.KeyReleaseEvent() if (event["key"] != "Shift" and event["key"] != "Control" and event["key"] != "Alt"): self.full_render() elif event_name == 'wheel': if 'wheel' in self.interaction_events.watched_events: self.send_pending_mouse_move_event() self.update_interactor_event_data(event) if event['deltaY'] < 0: self.interactor.MouseWheelForwardEvent() else: self.interactor.MouseWheelBackwardEvent() self.full_render() except Exception as e: self.error = str(e) def close(self): super().close() self._on_close() def __del__(self): super().__del__() self.close()
class ImageButton(VBox, HasTraits): """ Represents simple image with label and toggle button functionality. # Class methods - clear(): Clear image infos - on_click(p_function): Handle click events # Class methods - clear(): Clear image infos - on_click(p_function): Handle click events - reset_callbacks(): Reset event callbacks """ debug_output = Output(layout={'border': '1px solid black'}) active = Bool() image_path = Unicode() label_value = Unicode() def __init__(self, im_path=None, label=None, im_name=None, im_index=None, display_label=True, image_width='50px', image_height=None): self.display_label = display_label self.label = 'None' self.image = Image( layout=Layout(display='flex', justify_content='center', align_items='center', align_content='center', width=image_width, height=image_height), ) if self.display_label: # both image and label self.label = HTML( value='?', layout=Layout(display='flex', justify_content='center', align_items='center', align_content='center'), ) else: # no label (capture image case) self.im_name = im_name self.im_index = im_index self.image.layout.border = 'solid 1px gray' self.image.layout.object_fit = 'contain' super().__init__(layout=Layout(align_items='center', margin='3px', padding='2px')) if not im_path: self.clear() self.d = Event(source=self, watched_events=['click']) @observe('image_path') def _read_image(self, change=None): new_path = change['new'] if new_path: self.image.value = open(new_path, "rb").read() if not self.children: self.children = (self.image,) if self.display_label: self.children += (self.label,) else: #do not display image widget self.children = [] @observe('label_value') def _read_label(self, change=None): new_label = change['new'] if isinstance(self.label, HTML): self.label.value = new_label else: self.label = new_label def clear(self): if isinstance(self.label, HTML): self.label.value = '' else: self.label = '' self.image_path = '' self.active = False @observe('active') def mark(self, ev): # pad to compensate self size with border if self.active: if self.display_label: self.layout.border = 'solid 2px #1B8CF3' self.layout.padding = '0px' else: self.image.layout.border = 'solid 3px #1B8CF3' self.image.layout.padding = '0px' else: if self.display_label: self.layout.border = 'none' self.layout.padding = '2px' else: self.image.layout.border = 'solid 1px gray' @property def name(self): return Path(self.image_path).name @debug_output.capture(clear_output=False) def on_click(self, cb): self.d.on_dom_event(cb) def reset_callbacks(self): self.d.reset_callbacks()
def showCmd(b, layout, out): layout.toggle_hide(names=("show",)) params, summary = layout["view"].children controls, view, fov_controls = params.children with out: logger.info("Loading results ...") show_ret = show( **layout.kwargs, n=0, f1=None, f2=None, gui=True, ) if show_ret == 1: out.clear_output(wait=True) return model, fig, item, ax, fov = show_ret with view: plt.show() with summary: display(model.summary) n = widgets.BoundedIntText( value=0, min=0, max=model.data.Nt - 1, description=f"AOI (0-{model.data.Nt-1})", style={"description_width": "initial"}, layout={"width": "150px"}, ) n_counter = widgets.BoundedIntText( min=0, max=model.data.Nt - 1, ) f1_text = widgets.BoundedIntText( value=0, min=0, max=model.data.F - 15, step=1, description=f"Frame (0-{model.data.F-1})", style={"description_width": "initial"}, layout={"width": "300px"}, ) f1_counter = widgets.BoundedIntText( min=0, max=model.data.F - 15, ) f1_slider = widgets.IntSlider( value=0, min=0, max=model.data.F - 15, step=1, continuous_update=True, readout=False, readout_format="d", layout={"width": "210px"}, ) f1_box = widgets.VBox(layout=widgets.Layout(width="310px")) f1_incr = widgets.Button(description="+15", layout=widgets.Layout(width="45px")) f1_decr = widgets.Button(description="-15", layout=widgets.Layout(width="45px")) f1_controls = widgets.HBox( children=[f1_decr, f1_slider, f1_incr], layout=widgets.Layout(width="305px"), ) f1_box.children = [f1_text, f1_controls] widgets.jslink((f1_slider, "value"), (f1_text, "value")) widgets.jslink((f1_text, "value"), (f1_counter, "value")) widgets.jslink((n, "value"), (n_counter, "value")) zoom = widgets.Checkbox( value=False, description="Zoom out frames ['z']", indent=False, layout={"width": "240px"}, ) labels = widgets.Checkbox( value=False, description="Show labels", indent=False, layout={"width": "240px"}, ) targets = widgets.Checkbox( value=False, description="Show target location ['o']", indent=False, layout={"width": "240px"}, ) nonspecific = widgets.Checkbox( value=True, description="Show non-specific spots ['n']", indent=False, layout={"width": "240px"}, ) exclude_aoi = widgets.Checkbox( value=not model.data.mask[0], description="Exclude AOI from analysis ['e']", indent=False, layout={"width": "240px"}, ) checkboxes = widgets.VBox(layout=widgets.Layout(width="250px")) if model.data.labels is None: checkboxes.children = [zoom, targets, nonspecific, exclude_aoi] else: checkboxes.children = [zoom, targets, nonspecific, exclude_aoi, labels] controls.children = [n, f1_box, checkboxes] # fov controls if fov is not None: for dtype in fov.dtypes: fov_controls.add_child( dtype, widgets.Checkbox(value=True, description=f"Show {dtype} AOIs") ) fov_controls[dtype].observe( partial( showAOIs, fov=fov, n=n_counter, item=item, fig=fig, ), names="value", ) fov_controls.add_child("save_data", widgets.Button(description="Save data")) # callbacks n.observe( partial( updateParams, f1=f1_slider, model=model, fig=fig, item=item, ax=ax, targets=targets, nonspecific=nonspecific, labels=labels, fov_controls=fov_controls, exclude_aoi=exclude_aoi, show_fov=layout["show_fov"], ), names="value", ) f1_slider.observe( partial( updateRange, n=n, model=model, fig=fig, item=item, ax=ax, zoom=zoom, targets=targets, fov=fov, ), names="value", ) f1_incr.on_click(partial(incrementRange, x=15, counter=f1_counter)) f1_decr.on_click(partial(incrementRange, x=-15, counter=f1_counter)) zoom.observe( partial(zoomOut, f1=f1_slider, model=model, fig=fig, item=item, ax=ax), names="value", ) labels.observe( partial(showLabels, n=n_counter, model=model, item=item, ax=ax), names="value", ) targets.observe( partial( showTargets, n=n_counter, f1=f1_slider, model=model, item=item, ax=ax, ), names="value", ) nonspecific.observe( partial(showNonspecific, n=n_counter, model=model, item=item, ax=ax), names="value", ) exclude_aoi.observe( partial( excludeAOI, n=n_counter, model=model, item=item, show_fov=layout["show_fov"] ), names="value", ) fov_controls["save_data"].on_click( partial(saveData, data=model.data, path=model.path, out=out) ) # mouse click UI fig.canvas.mpl_connect( "button_release_event", partial(onFrameClick, counter=f1_counter), ) # key press UI d = Event(source=layout, watched_events=["keyup"], prevent_default_action=True) d.on_dom_event( partial( onKeyPress, n=n_counter, f1=f1_counter, zoom=zoom, targets=targets, nonspecific=nonspecific, exclude_aoi=exclude_aoi, ) ) with out: logger.info("Loading results: Done") out.clear_output(wait=True) b.disabled = True
class ImageViewEvent(ImageViewJpw): def __init__(self, logger=None, rgbmap=None, settings=None): ImageViewJpw.__init__(self, logger=logger, rgbmap=rgbmap, settings=settings) self._button = 0 # maps EventListener events to callback handlers self._evt_dispatch = { 'mousedown': self.button_press_event, 'mouseup': self.button_release_event, 'mousemove': self.motion_notify_event, 'wheel': self.scroll_event, 'mouseenter': self.enter_notify_event, 'mouseleave': self.leave_notify_event, 'keydown': self.key_press_event, 'keyup': self.key_release_event, } # mapping from EventListener key events to ginga key events self._keytbl = { 'shiftleft': 'shift_l', 'shiftright': 'shift_r', 'controlleft': 'control_l', 'controlright': 'control_r', 'altleft': 'alt_l', 'altright': 'alt_r', 'osleft': 'super_l', 'osright': 'super_r', 'contextmenu': 'menu_r', 'backslash': 'backslash', 'space': 'space', 'escape': 'escape', 'enter': 'return', 'tab': 'tab', 'arrowright': 'right', 'arrowleft': 'left', 'arrowup': 'up', 'arrowdown': 'down', 'pageup': 'page_up', 'pagedown': 'page_down', 'f1': 'f1', 'f2': 'f2', 'f3': 'f3', 'f4': 'f4', 'f5': 'f5', 'f6': 'f6', 'f7': 'f7', 'f8': 'f8', 'f9': 'f9', 'f10': 'f10', 'f11': 'f11', 'f12': 'f12', } self._keytbl2 = { '`': 'backquote', '"': 'doublequote', "'": 'singlequote', } # Define cursors for pick and pan #hand = openHandCursor() hand = 'fleur' self.define_cursor('pan', hand) cross = 'cross' self.define_cursor('pick', cross) for name in ('motion', 'button-press', 'button-release', 'key-press', 'key-release', 'drag-drop', 'scroll', 'map', 'focus', 'enter', 'leave', 'pinch', 'rotate', 'pan', 'swipe', 'tap'): self.enable_callback(name) def set_widget(self, jp_imgw): """Call this method with the Jupyter image widget (image_w) that will be used. """ super(ImageViewEvent, self).set_widget(jp_imgw) self.jp_evt = EventListener(source=jp_imgw) self.jp_evt.watched_events = [ 'keydown', 'keyup', 'mouseenter', 'mouseleave', 'mousedown', 'mouseup', 'mousemove', 'wheel', 'contextmenu' ] self.jp_evt.prevent_default_action = True self.jp_evt.on_dom_event(self._handle_event) self.logger.info("installed event handlers") return self.make_callback('map') def _handle_event(self, event): # TODO: need focus events and maybe a map event # TODO: Set up widget as a drag and drop destination evt_kind = event['type'] handler = self._evt_dispatch.get(evt_kind, None) if handler is not None: return handler(event) return False def transkey(self, keycode, keyname=None): keycode = str(keycode).lower() if keyname is None: keyname = keycode self.logger.debug("key code in jupyter '%s'" % (keycode)) res = self._keytbl.get(keycode, None) if res is None: res = self._keytbl2.get(keyname, keyname) return res def get_keyTable(self): return self._keytbl def focus_event(self, event, has_focus): return self.make_callback('focus', has_focus) def enter_notify_event(self, event): enter_focus = self.t_.get('enter_focus', False) if enter_focus: # TODO: set focus on canvas pass return self.make_callback('enter') def leave_notify_event(self, event): self.logger.debug("leaving widget...") return self.make_callback('leave') def key_press_event(self, event): keyname = self.transkey(event['code'], keyname=event['key']) self.logger.debug("key press event, key=%s" % (keyname)) return self.make_ui_callback('key-press', keyname) def key_release_event(self, event): keyname = self.transkey(event['code'], keyname=event['key']) self.logger.debug("key release event, key=%s" % (keyname)) return self.make_ui_callback('key-release', keyname) def button_press_event(self, event): x, y = event['dataX'], event['dataY'] self.last_win_x, self.last_win_y = x, y button = 0 button |= 0x1 << event['button'] self._button = button self.logger.debug("button event at %dx%d, button=%x" % (x, y, button)) data_x, data_y = self.check_cursor_location() return self.make_ui_callback('button-press', button, data_x, data_y) def button_release_event(self, event): x, y = event['dataX'], event['dataY'] self.last_win_x, self.last_win_y = x, y button = 0 button |= 0x1 << event['button'] self._button = 0 self.logger.debug("button release at %dx%d button=%x" % (x, y, button)) data_x, data_y = self.check_cursor_location() return self.make_ui_callback('button-release', button, data_x, data_y) def motion_notify_event(self, event): button = self._button x, y = event['dataX'], event['dataY'] self.last_win_x, self.last_win_y = x, y self.logger.debug("motion event at %dx%d, button=%x" % (x, y, button)) data_x, data_y = self.check_cursor_location() return self.make_ui_callback('motion', button, data_x, data_y) def scroll_event(self, event): x, y = event['dataX'], event['dataY'] self.last_win_x, self.last_win_y = x, y dx, dy = event['deltaX'], event['deltaY'] if (dx != 0 or dy != 0): # <= This browser gives us deltas for x and y # Synthesize this as a pan gesture event self.make_ui_callback('pan', 'start', 0, 0) self.make_ui_callback('pan', 'move', -dx, -dy) return self.make_ui_callback('pan', 'stop', 0, 0) # <= This code path should not be followed under normal # circumstances. # we leave it here in case we want to make the scroll # callback configurable in the future # TODO: calculate actual angle of direction if dy < 0: direction = 0.0 # up elif dy > 0: direction = 180.0 # down else: return False # 15 deg is standard 1-click turn for a wheel mouse num_deg = 15.0 self.logger.debug("scroll deg=%f direction=%f" % (num_deg, direction)) data_x, data_y = self.check_cursor_location() return self.make_ui_callback('scroll', direction, num_deg, data_x, data_y)
class IPyRemoteWidget(RemoteWidget): def __init__(self, scene_proxy, bridge, *args, **kw): super(IPyRemoteWidget, self).__init__(scene_proxy, bridge, *args, **kw) self.image = Image(format='PNG') self.event = Event(source=self.image, watched_events=[ 'dragstart', 'mouseenter', 'mouseleave', 'mousedown', 'mouseup', 'mousemove', 'wheel', 'keyup', 'keydown' ], prevent_default_action=True) self.event.on_dom_event(self.handle_ipyevent) self._update_image() def _ipython_display_(self): display(self.image) # ###### Public protocol ############## def show(self): pass def show_image(self, data, format='PNG'): self.image.format = format self.image.value = data # ##### VTK Event handling ########## def on_render(self, data): self.show_image(base64_to_bytes(data['data']), format=data.get('format', 'PNG')) def on_cursor_changed(self, data): # self.setCursor(cursor) pass def handle_ipyevent(self, event): type = event['type'] shift = event.get('shiftKey', False) ctrl = event.get('ctrlKey', False) btn_map = {0: 'left', 1: 'right', 2: 'middle'} if type == 'mouseenter': self.on_enter(ctrl, shift) elif type == 'mouseleave': self.on_leave(ctrl, shift) elif type == 'mouseleave': self.on_leave(ctrl, shift) elif type == 'mousedown': repeat = 0 button = btn_map.get(event.get('button'), 'none') x, y = event.get('relativeX'), event.get('relativeY') self.on_mouse_press(ctrl, shift, x, y, button, repeat) elif type == 'mouseup': x, y = event.get('relativeX'), event.get('relativeY') self.on_mouse_release(ctrl, shift, x, y) elif type == 'mousemove': button = btn_map.get(event.get('button'), 'none') x, y = event.get('relativeX'), event.get('relativeY') self.on_mouse_move(ctrl, shift, button, x, y) elif type == 'wheel': dx = event.get('deltaX') dy = event.get('deltaY') dz = event.get('deltaZ') delta = dx + dy + dz self.on_wheel(delta) elif type == 'keydown': key = event.get('key') # Need to map the special keys correctly. key_sym = key self.on_key_press(ctrl, shift, key, key_sym) elif type == 'keyup': key = event.get('key') # Need to map the special keys correctly. key_sym = key self.on_key_release(ctrl, shift, key)
class Cipher(object): """ Main cipher class Creates a list Character objects used to map the cipher into plaintext """ cipher = None alphabet = None _vbox = None _hbox = None _label = None _event = None _html = None _use = 'cipher' _cindex = 0 _currentr = 0 _currentc = 0 def __init__(self, ciphertext, invert=False): self.cipher = [] self.ciphertext = ciphertext.upper() # ------------------------------------------------------------ # `lacunatext` is the full ciphertext, each character removed # from Z. # ------------------------------------------------------------ self.lacunatext = ''.join( [helpers.distancefrom(c, 'Z') for c in self.ciphertext] ) if invert: self.ciphertext = self.lacunatext self.lacunatext = ciphertext helpers.distance_calculator(self.ciphertext, self.lacunatext) # ------------------------------------------------------------ # A polarity table is formed. This is used to help determine # the rules. As each character is found, the polarity of that # character changes # ------------------------------------------------------------ self.alphabet = { character: False for character in helpers.alphabet } # ------------------------------------------------------------ # Create an object for each character setting the value of # 'use_alt' to the current value of the boolean alphabet. # We then invert the alphabet flag for the next occurance of # that character. # ------------------------------------------------------------ for i, c, l in zip(range(1, self.length + 1), self.ciphertext, self.lacunatext): cipher_flag = self.alphabet[c] if c not in ['M', 'Z'] else True lacuna_flag = self.alphabet[c] if l not in ['M', 'Z'] else True self.cipher.append( Character(c, i, cipher_flag, self.ciphertext) ) self.alphabet[c] = not self.alphabet[c] # Used for displaying the current position on the ciphergrid self._currentc = 'A' self._currentr = 0 def setup_jupyter(self): """ Sets up elements on the page for use with a Jupyter notebook. """ self._label = Label('Move the cursor over the cell and use the left and right arrow keys to navigate') self._hbox = HBox() self._html = HTML('<h3>Label position?</h3>') self._inner = VBox() self._vbox = VBox([self._html, self._inner, self._label]) self._event = Event(source=self._vbox, watched_events=['keydown']) self._event.on_dom_event(self.handle_event) return self @property def length(self): """ Return the length of the current cipher """ return len(self.ciphertext) def intermediate(self, pos): """ Get the intermediate character from the cipher """ return self[pos].decipher def __str__(self): return ''.join([str(c) for c in self]) def __iter__(self): return getattr(self, self._use).__iter__() def __getitem__(self, key): return getattr(self, self._use).__getitem__(key) def __len__(self): return self.length @globalout.capture() def handle_event(self, event): """ Jupyter ipyevents binding code """ if 'code' in event.keys(): if event['shiftKey']: event['code'] = 'Shift+' + event['code'] try: self.setposition(event['code']) self._draw() except IndexError: # If we're out of index, we're beyond the end of the cipher # simply call again to move to the bext row self.handle_event(event) def setposition(self, code): """ Sets the current cipher position on the grids """ pagesize = 10 shiftpage = 5 prevrow = ((26 - helpers.a2i(self._currentc)) + helpers.a2i(self._currentc)) nextrow = helpers.a2i(self._currentc) + (26 - helpers.a2i(self._currentc)) lastrow = (len(self) % 26) - nextrow if (len(self) % 26) - nextrow > 0 \ else (len(self) - (26 * (len(self) // 26))) primary = { 'ArrowLeft': self._cindex - 1 if self._cindex > 0 else len(self) - 1, 'ArrowRight': self._cindex + 1 if self._cindex < len(self)-1 else 0, 'PageDown': self._cindex + pagesize if (self._cindex + pagesize) < len(self)-1 \ else 0 + ((self._cindex + pagesize) - len(self)), 'PageUp': self._cindex - pagesize if (self._cindex - pagesize) >= 0 \ else (len(self) - (pagesize - self._cindex)), 'Shift+ArrowLeft': self._cindex - shiftpage if (self._cindex - shiftpage) >= 0 \ else (len(self) - (shiftpage - self._cindex)), 'Shift+ArrowRight': self._cindex + shiftpage if (self._cindex + shiftpage) < len(self)-1 \ else 0 + ((self._cindex + shiftpage) - len(self)), 'Home': 0, 'End': len(self) - 1, 'ArrowDown': (self._cindex + prevrow) if self._cindex + prevrow < len(self) \ else helpers.a2i(self._currentc) - 1, # cant get this to work properly #'ArrowUp': (self._cindex - nextrow) if self._cindex - nextrow >= 0 else lastrow, 'ArrowUp': helpers.a2i(self._currentc) - 1 + ( (26 * (self._currentr - 1)) if self._currentr - 1 >= 0 else (26 * (len(self) // 26)) ) } self._cindex = primary[code] if code in primary.keys() else self._cindex self._currentr = self._cindex // 26 self._currentc = helpers.i2a((self._cindex % 26) + 1) def _draw(self): """ Jupyter notebook code to draw widgets """ left = Output() right = Output() properties = Output() conditions = Output() deciphered = Output() tablekey = Output() tables = { True: [], False: [] } for key in self[self._cindex].cipher.keys(): characters = self[self._cindex].cipher[key].get() for i, character in zip(range(len(characters)), characters): partial = Output() df = self[self._cindex].all_positions(helpers.i2a(character)) style = df.style.set_caption( '{} ({})'.format(character, helpers.i2a(character)) ).set_table_attributes( 'style="font-size: 10px"' ).hide_index() if self[self._cindex].table == key and df.equals(self[self._cindex].all_positions()): style.set_properties(**{'background-color': '#FF0000', 'color': '#FFFFFF'}) with partial: display.display(style) tables[key].append(partial) with properties: display.display( self[self._cindex].properties_frame .style.set_caption('Properties') .set_table_attributes( 'style="font-size: 10px"' ).set_properties( subset=['Value'], **{'width': '120px'} ).hide_index() ) with conditions: df = self[self._cindex].condition_frame.reset_index() df.columns = ['', 'index', 'cipher', 'lacuna',] display.display( df.style.set_caption('Conditions') .set_table_attributes( 'style="font-size: 10px"' ).hide_index() ) with deciphered: display.display(self.as_dataframe()) with tablekey: display.display(self.table_key) with left: display.display(self[self._cindex].cipher[True].apply) with right: display.display(self[self._cindex].cipher[False].apply) subtables = VBox() subtables.children = [HBox(tables[True]), HBox(tables[False])] self._hbox.children = [left, right] self._inner.children = [ self._hbox, HBox([VBox([properties, conditions]), subtables, VBox([deciphered, tablekey])]), globalout ] self._html.value = '<h3>Current character {} ({}), lacuna {} ({}) index {}, deciphered to {} algorithm {}</h3>'.format( self[self._cindex].character, self[self._cindex].cindex, self[self._cindex].lacuna, self[self._cindex].lindex, self._cindex + 1, str(self[self._cindex]), self[self._cindex].algorithm + 1 ) @property def table_key(self): key = pd.DataFrame([ ['', 'Cipher character',], ['', 'Active character',], ['', 'Cipher lacuna',], ['', 'Active lacuna',], ['', 'Both match',], ['', 'Properties match',], ['', 'Contitions match',], ], columns=['Colour', 'Description']) return key.style.applymap( lambda _: 'background-color: #00FF00', pd.IndexSlice[0, 'Colour',] ).applymap( lambda _: 'background-color: #FF0000; color: #FFFFFF', pd.IndexSlice[1, 'Colour',] ).applymap( lambda _: 'background-color: #90EE90', pd.IndexSlice[2, 'Colour',] ).applymap( lambda _: 'background-color: #FBACA8', pd.IndexSlice[3, 'Colour',] ).applymap( lambda _: 'background-color: #EDC9AF', pd.IndexSlice[4, 'Colour',] ).applymap( lambda _: 'background-color: #D291BC', pd.IndexSlice[5, 'Colour',] ).applymap( lambda _: 'background-color: #85E3FF', pd.IndexSlice[6, 'Colour',] ).set_table_attributes( 'style="font-size: 10px"' ).hide_index().set_caption('Key') def as_dataframe(self, ciphertext=False): """ display the finished cipher in a dataframe """ n = 26 ciphertext = [str(i) for i in self] if not ciphertext else [i for i in ciphertext] df = pd.DataFrame( [self[i:i + n] for i in range(0, len(ciphertext), n)] ) mask = df.applymap(lambda x: x is None) cols = df.columns[(mask).any()] for col in df[cols]: df.loc[mask[col], col] = '' df.columns = helpers.alphabet style = df.style.hide_index().set_caption( 'Deciphered plaintext' ).set_table_attributes( 'style="font-size: 10px"' ).applymap( Highlighter(None, None).highlightr, subset=pd.IndexSlice[self._currentr, self._currentc] ) for i in range(len(self)): if i == self._cindex: continue row = i // 26 col = helpers.i2a((i % 26) + 1) properties_match = self[i].properties_table == self[self._cindex].properties_table conditions_match = self[i].condition_table == self[self._cindex].condition_table if properties_match and conditions_match: style = style.applymap( Highlighter(None, None).highlights, subset=pd.IndexSlice[row, col] ) elif properties_match: style = style.applymap( Highlighter(None, None).highlightl, subset=pd.IndexSlice[row, col] ) elif conditions_match: style = style.applymap( Highlighter(None, None).highlightb, subset=pd.IndexSlice[row, col] ) return style def display(self, index=1): """ Display a given character in a Jupyter cell """ index = 1 if index == 0 else index display.clear_output(wait=True) if index is not None: self._cindex = (index - 1) self._currentc = helpers.i2a((self._cindex % 26)+1) self._currentr = self._cindex // 26 self._draw() display.display(self._vbox)
class ImageViewEvent(ImageViewJpw): def __init__(self, logger=None, rgbmap=None, settings=None): ImageViewJpw.__init__(self, logger=logger, rgbmap=rgbmap, settings=settings) self._button = 0 # maps EventListener events to callback handlers self._evt_dispatch = { 'mousedown': self.button_press_event, 'mouseup': self.button_release_event, 'mousemove': self.motion_notify_event, 'wheel': self.scroll_event, 'mouseenter': self.enter_notify_event, 'mouseleave': self.leave_notify_event, 'keydown': self.key_press_event, 'keyup': self.key_release_event, } # mapping from EventListener key events to ginga key events self._keytbl = { 'shiftleft': 'shift_l', 'shiftright': 'shift_r', 'controlleft': 'control_l', 'controlright': 'control_r', 'altleft': 'alt_l', 'altright': 'alt_r', 'osleft': 'super_l', 'osright': 'super_r', 'contextmenu': 'menu_r', 'backslash': 'backslash', 'space': 'space', 'escape': 'escape', 'enter': 'return', 'tab': 'tab', 'arrowright': 'right', 'arrowleft': 'left', 'arrowup': 'up', 'arrowdown': 'down', 'pageup': 'page_up', 'pagedown': 'page_down', 'f1': 'f1', 'f2': 'f2', 'f3': 'f3', 'f4': 'f4', 'f5': 'f5', 'f6': 'f6', 'f7': 'f7', 'f8': 'f8', 'f9': 'f9', 'f10': 'f10', 'f11': 'f11', 'f12': 'f12', } self._keytbl2 = { '`': 'backquote', '"': 'doublequote', "'": 'singlequote', } # Define cursors for pick and pan #hand = openHandCursor() hand = 'fleur' self.define_cursor('pan', hand) cross = 'cross' self.define_cursor('pick', cross) for name in ('motion', 'button-press', 'button-release', 'key-press', 'key-release', 'drag-drop', 'scroll', 'map', 'focus', 'enter', 'leave', 'pinch', 'rotate', 'pan', 'swipe', 'tap'): self.enable_callback(name) def set_widget(self, jp_imgw): """Call this method with the Jupyter image widget (image_w) that will be used. """ super(ImageViewEvent, self).set_widget(jp_imgw) self.jp_evt = EventListener(source=jp_imgw) self.jp_evt.watched_events = [ 'keydown', 'keyup', 'mouseenter', 'mouseleave', 'mousedown', 'mouseup', 'mousemove', 'wheel', 'contextmenu' ] self.jp_evt.prevent_default_action = True self.jp_evt.on_dom_event(self._handle_event) self.logger.info("installed event handlers") return self.make_callback('map') def _handle_event(self, event): # TODO: need focus events and maybe a map event # TODO: Set up widget as a drag and drop destination evt_kind = event['type'] handler = self._evt_dispatch.get(evt_kind, None) if handler is not None: return handler(event) return False def transkey(self, keycode, keyname=None): keycode = str(keycode).lower() if keyname is None: keyname = keycode self.logger.debug("key code in jupyter '%s'" % (keycode)) res = self._keytbl.get(keycode, None) if res is None: res = self._keytbl2.get(keyname, keyname) return res def get_key_table(self): return self._keytbl def focus_event(self, event, has_focus): return self.make_callback('focus', has_focus) def enter_notify_event(self, event): enter_focus = self.t_.get('enter_focus', False) if enter_focus: # TODO: set focus on canvas pass return self.make_callback('enter') def leave_notify_event(self, event): self.logger.debug("leaving widget...") return self.make_callback('leave') def key_press_event(self, event): keyname = self.transkey(event['code'], keyname=event['key']) self.logger.debug("key press event, key=%s" % (keyname)) return self.make_ui_callback('key-press', keyname) def key_release_event(self, event): keyname = self.transkey(event['code'], keyname=event['key']) self.logger.debug("key release event, key=%s" % (keyname)) return self.make_ui_callback('key-release', keyname) def button_press_event(self, event): x, y = event['dataX'], event['dataY'] self.last_win_x, self.last_win_y = x, y button = 0 button |= 0x1 << event['button'] self._button = button self.logger.debug("button event at %dx%d, button=%x" % (x, y, button)) data_x, data_y = self.check_cursor_location() return self.make_ui_callback('button-press', button, data_x, data_y) def button_release_event(self, event): x, y = event['dataX'], event['dataY'] self.last_win_x, self.last_win_y = x, y button = 0 button |= 0x1 << event['button'] self._button = 0 self.logger.debug("button release at %dx%d button=%x" % (x, y, button)) data_x, data_y = self.check_cursor_location() return self.make_ui_callback('button-release', button, data_x, data_y) def motion_notify_event(self, event): button = self._button x, y = event['dataX'], event['dataY'] self.last_win_x, self.last_win_y = x, y self.logger.debug("motion event at %dx%d, button=%x" % (x, y, button)) data_x, data_y = self.check_cursor_location() return self.make_ui_callback('motion', button, data_x, data_y) def scroll_event(self, event): x, y = event['dataX'], event['dataY'] self.last_win_x, self.last_win_y = x, y dx, dy = event['deltaX'], event['deltaY'] if (dx != 0 or dy != 0): # <= This browser gives us deltas for x and y # Synthesize this as a pan gesture event self.make_ui_callback('pan', 'start', 0, 0) self.make_ui_callback('pan', 'move', -dx, -dy) return self.make_ui_callback('pan', 'stop', 0, 0) # <= This code path should not be followed under normal # circumstances. # we leave it here in case we want to make the scroll # callback configurable in the future # TODO: calculate actual angle of direction if dy < 0: direction = 0.0 # up elif dy > 0: direction = 180.0 # down else: return False # 15 deg is standard 1-click turn for a wheel mouse num_deg = 15.0 self.logger.debug("scroll deg=%f direction=%f" % ( num_deg, direction)) data_x, data_y = self.check_cursor_location() return self.make_ui_callback('scroll', direction, num_deg, data_x, data_y)
def mouse_hover(widget, callback): e = Event(source=widget, watched_events=['mousemove']) e.on_dom_event(callback)
class IPyRemoteWidget(RemoteWidget): def __init__(self, scene_proxy, bridge, *args, **kw): super(IPyRemoteWidget, self).__init__(scene_proxy, bridge, *args, **kw) self.image = Image(format='PNG') self.event = Event( source=self.image, watched_events=[ 'dragstart', 'mouseenter', 'mouseleave', 'mousedown', 'mouseup', 'mousemove', 'wheel', 'keyup', 'keydown' ], prevent_default_action=True ) self.event.on_dom_event(self.handle_ipyevent) self._update_image() def _ipython_display_(self): display(self.image) # ###### Public protocol ############## def show(self): pass def show_image(self, data, format='PNG'): self.image.format = format self.image.value = data # ##### VTK Event handling ########## def on_render(self, data): self.show_image(base64_to_bytes(data['data']), format=data.get('format', 'PNG')) def on_cursor_changed(self, data): # self.setCursor(cursor) pass def handle_ipyevent(self, event): type = event['type'] shift = event.get('shiftKey', False) ctrl = event.get('ctrlKey', False) btn_map = {0: 'left', 1: 'right', 2: 'middle'} if type == 'mouseenter': self.on_enter(ctrl, shift) elif type == 'mouseleave': self.on_leave(ctrl, shift) elif type == 'mouseleave': self.on_leave(ctrl, shift) elif type == 'mousedown': repeat = 0 button = btn_map.get(event.get('button'), 'none') x, y = event.get('relativeX'), event.get('relativeY') self.on_mouse_press(ctrl, shift, x, y, button, repeat) elif type == 'mouseup': x, y = event.get('relativeX'), event.get('relativeY') self.on_mouse_release(ctrl, shift, x, y) elif type == 'mousemove': button = btn_map.get(event.get('button'), 'none') x, y = event.get('relativeX'), event.get('relativeY') self.on_mouse_move(ctrl, shift, button, x, y) elif type == 'wheel': dx = event.get('deltaX') dy = event.get('deltaY') dz = event.get('deltaZ') delta = dx + dy + dz self.on_wheel(delta) elif type == 'keydown': key = event.get('key') # Need to map the special keys correctly. key_sym = key self.on_key_press(ctrl, shift, key, key_sym) elif type == 'keyup': key = event.get('key') # Need to map the special keys correctly. key_sym = key self.on_key_release(ctrl, shift, key)
def run(): # Time to build the figure! fig, ax = plt.subplots(figsize=[9, 9]) # Draw the stars. def scatter_stars(): marker_size = (0.5 + limiting_magnitude - stars['magnitude']) ** 2.0 mask = ( (marker_size > 0.25) & (stars['x'] > -0.3) & (stars['x'] < 0.3) & (stars['y'] > -0.3) & (stars['y'] < 0.3) ) #print('Number of stars:', mask.sum()) #scatter.set_data(stars['x'][mask], stars['y'][mask]) xy = np.array([stars['x'][mask], stars['y'][mask]]) scatter.set_offsets(xy.T) s = marker_size[mask] #[bright_stars]] scatter.set_sizes(s) scatter = ax.scatter([], [], color='k') scatter_stars() # Finally, title the plot and set some final parameters. angle = np.pi - field_of_view_degrees / 360.0 * np.pi limit = np.sin(angle) / (1.0 - np.cos(angle)) #print(limit) ax.set_xlim(-limit, limit) ax.set_ylim(-limit, limit) ax.xaxis.set_visible(False) ax.yaxis.set_visible(False) ax.set_aspect(1.0) ax.set_title('Stars') from ipywidgets import Label, HTML, HBox, Image, VBox, Box, HBox, interact from ipyevents import Event from IPython.display import display l = Label('Click or type on me!') l.layout.border = '2px solid red' h = HTML('Event info') d = Event(source=l, watched_events=['click', 'keydown', 'mouseenter']) def handle_event(event): lines = ['{}: {}'.format(k, v) for k, v in event.items()] content = '<br>'.join(lines) h.value = content d.on_dom_event(handle_event) #display(l, h) import ipywidgets as widgets def demo(i): field_of_view_degrees = 45.0 - i angle = np.pi - field_of_view_degrees / 360.0 * np.pi limit = np.sin(angle) / (1.0 - np.cos(angle)) ax.set_xlim(-limit, limit) ax.set_ylim(-limit, limit) return fig #widgets.interact(demo, i = d) def callback(event): try: return callback2(event) except Exception as e: place.value = str(e) def callback2(event): nonlocal limit place.value = str(event['code']) code = event['code'] if event['code'] == 'PageUp': limit /= 1.1 ax.set_xlim(-limit, limit) ax.set_ylim(-limit, limit) elif event['code'] == 'PageDown': limit *= 1.1 ax.set_xlim(-limit, limit) ax.set_ylim(-limit, limit) elif code.startswith('Arrow'): if code == 'ArrowUp': set_center(0, 0.5) elif code == 'ArrowDown': set_center(0, -0.5) elif code == 'ArrowLeft': set_center(0.1, 0) elif code == 'ArrowRight': set_center(-0.1, 0) set_positions() scatter_stars() place.value = 'Surv' #PageUp: bigger # PageDown: smaller redraw() from io import BytesIO def redraw(): io = BytesIO() plt.savefig(io, format="png") #plt.close() plt.ioff() image.value = io.getvalue() image = Image(format='png') redraw() # The layout bits below make sure the image display looks the same in lab and classic notebook image.layout.max_width = '4in' image.layout.height = 'auto' im_events = Event() im_events.source = image im_events.watched_events = ['keydown'] im_events.on_dom_event(callback) place = HTML('Test') return VBox([place, image])
class ViewInteractiveWidget(Canvas): """Remote controller for Slicer viewers. :param layoutLabel: specify view by label (displayed in the view's header in the layout, such as R, Y, G, 1) :param renderView: specify view by renderView object (ctkVTKRenderView). """ def __init__(self, layoutLabel=None, renderView=None, **kwargs): from ipyevents import Event #import time super().__init__(**kwargs) # Find renderView from layoutLabel layoutManager = slicer.app.layoutManager() # Find it among 3D views if not renderView: for threeDViewIndex in range(layoutManager.threeDViewCount): threeDWidget = layoutManager.threeDWidget(threeDViewIndex) if (threeDWidget.mrmlViewNode().GetLayoutLabel() == layoutLabel) or (layoutLabel is None): renderView = threeDWidget.threeDView() break # Find it among slice views if not renderView: sliceViewNames = layoutManager.sliceViewNames() for sliceViewName in sliceViewNames: sliceWidget = layoutManager.sliceWidget(sliceViewName) if (sliceWidget.mrmlSliceNode().GetLayoutLabel() == layoutLabel) or (layoutLabel is None): renderView = sliceWidget.sliceView() break if not renderView: if layoutLabel: raise ValueError( "renderView is not specified and view cannot be found by layout label " + layoutLabel) else: raise ValueError("renderView is not specified") self.renderView = renderView # Frame rate (1/renderDelay) self.lastRenderTime = 0 self.quickRenderDelaySec = 0.1 self.quickRenderDelaySecRange = [0.02, 2.0] self.adaptiveRenderDelay = True self.lastMouseMoveEvent = None # Quality vs performance self.compressionQuality = 50 self.trackMouseMove = False # refresh if mouse is just moving (not dragging) self.messageTimestampOffset = None # If not receiving new rendering request for 10ms then a render is requested self.fullRenderRequestTimer = qt.QTimer() self.fullRenderRequestTimer.setSingleShot(True) self.fullRenderRequestTimer.setInterval(500) self.fullRenderRequestTimer.connect('timeout()', self.fullRender) # If not receiving new rendering request for 10ms then a render is requested self.quickRenderRequestTimer = qt.QTimer() self.quickRenderRequestTimer.setSingleShot(True) self.quickRenderRequestTimer.setInterval(self.quickRenderDelaySec * 1000) self.quickRenderRequestTimer.connect('timeout()', self.quickRender) # Get image size image = self.getImage() self.width = image.width self.height = image.height self.draw_image(image) self.interactor = self.renderView.interactorStyle().GetInteractor() self.dragging = False self.interactionEvents = Event() self.interactionEvents.source = self self.interactionEvents.watched_events = [ 'dragstart', 'mouseenter', 'mouseleave', 'mousedown', 'mouseup', 'mousemove', #'wheel', # commented out so that user can scroll through the notebook using mousewheel 'keyup', 'keydown', 'contextmenu' # prevent context menu from appearing on right-click ] #self.interactionEvents.msg_throttle = 1 # does not seem to have effect self.interactionEvents.prevent_default_action = True self.interactionEvents.on_dom_event(self.handleInteractionEvent) self.keyToSym = { 'ArrowLeft': 'Left', 'ArrowRight': 'Right', 'ArrowUp': 'Up', 'ArrowDown': 'Down', 'BackSpace': 'BackSpace', 'Tab': 'Tab', 'Enter': 'Return', #'Shift': 'Shift_L', #'Control': 'Control_L', #'Alt': 'Alt_L', 'CapsLock': 'Caps_Lock', 'Escape': 'Escape', ' ': 'space', 'PageUp': 'Prior', 'PageDown': 'Next', 'Home': 'Home', 'End': 'End', 'Delete': 'Delete', 'Insert': 'Insert', '*': 'asterisk', '+': 'plus', '|': 'bar', '-': 'minus', '.': 'period', '/': 'slash', 'F1': 'F1', 'F2': 'F2', 'F3': 'F3', 'F4': 'F4', 'F5': 'F5', 'F6': 'F6', 'F7': 'F7', 'F8': 'F8', 'F9': 'F9', 'F10': 'F10', 'F11': 'F11', 'F12': 'F12' } # Errors are not displayed when a widget is displayed, # this variable can be used to retrieve error messages self.error = None # Enable logging of UI events self.logEvents = False self.loggedEvents = [] self.elapsedTimes = [] self.ageOfProcessedMessages = [] def setQuickRenderDelay(self, delaySec): """Delay this much after a view update before sending a low-resolution update.""" if delaySec < self.quickRenderDelaySecRange[0]: delaySec = self.quickRenderDelaySecRange[0] elif delaySec > self.quickRenderDelaySecRange[1]: delaySec = self.quickRenderDelaySecRange[1] self.quickRenderDelaySec = delaySec self.quickRenderRequestTimer.setInterval(self.quickRenderDelaySec * 1000) def setFullRenderDelay(self, delaySec): """Delay this much after a view update before sending a full-resolution update.""" self.fullRenderRequestTimer.setInterval(delaySec) def getImage(self, compress=True, forceRender=True): """Retrieve an image from the view.""" from ipywidgets import Image slicer.app.processEvents() if forceRender: self.renderView.forceRender() screenshot = self.renderView.grab() bArray = qt.QByteArray() buffer = qt.QBuffer(bArray) buffer.open(qt.QIODevice.WriteOnly) if compress: screenshot.save(buffer, "JPG", self.compressionQuality) else: screenshot.save(buffer, "PNG") return Image(value=bArray.data(), width=screenshot.width(), height=screenshot.height()) def fullRender(self): """Perform a full render now.""" try: import time self.fullRenderRequestTimer.stop() self.quickRenderRequestTimer.stop() self.draw_image(self.getImage(compress=False, forceRender=True)) self.lastRenderTime = time.time() except Exception as e: self.error = str(e) def sendPendingMouseMoveEvent(self): if self.lastMouseMoveEvent is not None: self.updateInteractorEventData(self.lastMouseMoveEvent) self.interactor.MouseMoveEvent() self.lastMouseMoveEvent = None def quickRender(self): """Perform a quick render now.""" try: import time self.fullRenderRequestTimer.stop() self.quickRenderRequestTimer.stop() self.sendPendingMouseMoveEvent() self.draw_image(self.getImage(compress=True, forceRender=False)) self.fullRenderRequestTimer.start() if self.logEvents: self.elapsedTimes.append(time.time() - self.lastRenderTime) self.lastRenderTime = time.time() except Exception as e: self.error = str(e) def updateInteractorEventData(self, event): try: if event['event'] == 'keydown' or event['event'] == 'keyup': key = event['key'] sym = self.keyToSym[key] if key in self.keyToSym.keys( ) else key self.interactor.SetKeySym(sym) if len(key) == 1: self.interactor.SetKeyCode(key) self.interactor.SetRepeatCount(1) else: self.interactor.SetEventPosition( event['offsetX'], self.height - event['offsetY']) self.interactor.SetShiftKey(event['shiftKey']) self.interactor.SetControlKey(event['ctrlKey']) self.interactor.SetAltKey(event['altKey']) except Exception as e: self.error = str(e) def handleInteractionEvent(self, event): try: if self.logEvents: self.loggedEvents.append(event) if event['event'] == 'mousemove': import time if self.messageTimestampOffset is None: self.messageTimestampOffset = time.time( ) - event['timeStamp'] * 0.001 self.lastMouseMoveEvent = event if not self.dragging and not self.trackMouseMove: return if self.adaptiveRenderDelay: ageOfProcessedMessage = time.time() - ( event['timeStamp'] * 0.001 + self.messageTimestampOffset) if ageOfProcessedMessage > 1.5 * self.quickRenderDelaySec: # we are falling behind, try to render less frequently self.setQuickRenderDelay(self.quickRenderDelaySec * 1.05) elif ageOfProcessedMessage < 0.5 * self.quickRenderDelaySec: # we can keep up with events, try to render more frequently self.setQuickRenderDelay(self.quickRenderDelaySec / 1.05) if self.logEvents: self.ageOfProcessedMessages.append( [ageOfProcessedMessage, self.quickRenderDelaySec]) # We need to render something now it no rendering since self.quickRenderDelaySec if time.time( ) - self.lastRenderTime > self.quickRenderDelaySec: self.quickRender() else: self.quickRenderRequestTimer.start() elif event['event'] == 'mouseenter': self.updateInteractorEventData(event) self.interactor.EnterEvent() self.lastMouseMoveEvent = None self.quickRenderRequestTimer.start() elif event['event'] == 'mouseleave': self.updateInteractorEventData(event) self.interactor.LeaveEvent() self.lastMouseMoveEvent = None self.quickRenderRequestTimer.start() elif event['event'] == 'mousedown': self.dragging = True self.sendPendingMouseMoveEvent() self.updateInteractorEventData(event) if event['button'] == 0: self.interactor.LeftButtonPressEvent() elif event['button'] == 2: self.interactor.RightButtonPressEvent() elif event['button'] == 1: self.interactor.MiddleButtonPressEvent() self.fullRender() elif event['event'] == 'mouseup': self.sendPendingMouseMoveEvent() self.updateInteractorEventData(event) if event['button'] == 0: self.interactor.LeftButtonReleaseEvent() elif event['button'] == 2: self.interactor.RightButtonReleaseEvent() elif event['button'] == 1: self.interactor.MiddleButtonReleaseEvent() self.dragging = False self.fullRender() elif event['event'] == 'keydown': self.sendPendingMouseMoveEvent() self.updateInteractorEventData(event) self.interactor.KeyPressEvent() self.interactor.CharEvent() if event['key'] != 'Shift' and event[ 'key'] != 'Control' and event['key'] != 'Alt': self.fullRender() elif event['event'] == 'keyup': self.sendPendingMouseMoveEvent() self.updateInteractorEventData(event) self.interactor.KeyReleaseEvent() if event['key'] != 'Shift' and event[ 'key'] != 'Control' and event['key'] != 'Alt': self.fullRender() except Exception as e: self.error = str(e)
class ImageLabeler: def __init__(self, label_name): self.clix = 0 # current landmark index self.length = len(db) self.n_panels = 5 self.panels = [ImageAndLabel() for _ in range(self.n_panels)] self.label_name = label_name self.landmarks = Landmarks({k: None for k in db.keys()}) self.c = widgets.HTML('Click or type on me!') button = widgets.Button(description="Save", layout=widgets.Layout(width='auto')) button.on_click(self.save) w = widgets.HBox([*self.panels]) w = widgets.VBox([w, self.c, button]) self.widget = w self.d = Event(source=self.widget, watched_events=['keydown']) self.d.on_dom_event(self.handle_event) self.target_label = 'q' self.render() display(self.widget) def save(self, _): db.save() def render(self): self.update_panels() self.c.value = '<pre style="line-height: 12px;">' + db.vc().to_string( ).replace('\n', '\n') + '</pre>' def update_panels(self): for pix in range(self.n_panels): self.update_panel(pix, self.clix + pix) def update_panel(self, pix, lix): if not 0 <= lix < len(self.landmarks): self.panels[pix].clear() return coord = self.landmarks.coordinates()[lix] is_target_label = self.landmarks[coord] # T, F or None data = lego_images.get_crop(coord) if is_target_label: yn, color = 'yes', 'green' elif is_target_label is None: yn, color = '?', 'grey' else: yn, color = 'no', 'red' ch = 'hjkl;'[pix] style = f'font-size: x-large; color: {color};' style2 = f'font-family: mono; color: grey;' caption = [ f'<span style="{style}">{yn}</span>', f'({lix + 1} of {self.length})', f'<span style="{style2}">{ch}</span>', ] caption = '<br>'.join(caption) caption = f'<div style="text-align: center;">{caption}</span>' self.panels[pix].update(data, caption) self.panels[pix].layout.border = f'5px solid {color}' def toggle_label(self, clix): if not 0 <= clix < len(self.landmarks): return coord = self.landmarks.coordinates()[clix] current = self.landmarks[coord] new = True if current is None else not current self.landmarks[coord] = new db.update(coord, new) def falsify_if_unset(self, clix): if not 0 <= clix < len(self.landmarks): return coord = self.landmarks.coordinates()[clix] current = self.landmarks[coord] new = False if current is None else current self.landmarks[coord] = new db.update(coord, new) def handle_event(self, event): key = event['key'] if key in 'hjkl;': pix = 'hjkl;'.index(key) self.toggle_label(self.clix + pix) elif key in ['[', 'ArrowLeft']: self.clix -= self.n_panels elif key in [']', 'ArrowRight']: self.clix += self.n_panels elif key in ['Enter']: for i in range(self.n_panels): self.falsify_if_unset(self.clix + i) self.clix += self.n_panels elif key == 'ArrowUp': self.padding += 5 elif key == 'ArrowDown': self.padding -= 5 else: self.c.value = f"You pressed: {key}" return self.clix = max(self.clix, 0) max_ix = len(self.landmarks) // self.n_panels * self.n_panels self.clix = min(max_ix, self.clix) self.render()