Skip to content

Commit

Permalink
WID-223: create global context as singleton and observer to hold shar…
Browse files Browse the repository at this point in the history
…ed and reactive state between viewers and operations
  • Loading branch information
davidbacter01 committed Sep 12, 2023
1 parent 4c5a6d1 commit 2eba9ff
Show file tree
Hide file tree
Showing 3 changed files with 160 additions and 18 deletions.
28 changes: 10 additions & 18 deletions tvbwidgets/ui/connectivity_ipy/connectivity_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
from tvb.datatypes.connectivity import Connectivity
from tvbwidgets.ui.base_widget import TVBWidget
from tvbwidgets.ui.connectivity_ipy.outputs_3d import PyVistaOutput
from tvbwidgets.ui.connectivity_ipy.operations import ConnectivityOperations
from tvbwidgets.ui.connectivity_ipy.config import ConnectivityConfig
from tvbwidgets.ui.connectivity_ipy.global_context import CONTEXT

DROPDOWN_KEY = 'dropdown'

Expand Down Expand Up @@ -83,14 +85,20 @@ def __draw_connectivity(self):
def on_change(change):
if change['type'] == 'change' and change['name'] == 'value':
matrix = self.connectivity.weights if change['new'] == 'weights' else self.connectivity.tract_lengths
CONTEXT.matrix = change['new']
self.__show_plot(matrix)

dropdown = ipywidgets.Dropdown(
options=[('Tracts', 'tracts'), ('Weights', 'weights')],
value='weights',
options=CONTEXT.MATRIX_OPTIONS,
value=CONTEXT.matrix,
description='Matrix:'
)
dropdown.observe(on_change)

def on_ctx_change(value):
dropdown.value = value

CONTEXT.observe(on_ctx_change, 'matrix')
self.widgets_map[DROPDOWN_KEY] = dropdown
self.children = (dropdown, *self.children)

Expand Down Expand Up @@ -200,22 +208,6 @@ def _extract_edges(self):
return numpy.array(edges_coords)


class ConnectivityOperations(ipywidgets.VBox, TVBWidget):
def add_datatype(self, datatype):
"""
Currently not supported
"""
pass

def __init__(self, connectivity, **kwargs):
super().__init__(layout=self.DEFAULT_BORDER, **kwargs)
children = [
ipywidgets.HTML(
value=f'Placeholder text for operations on Connectivity-{connectivity.number_of_regions}'
)]
self.children = children


class ConnectivityViewers(ipywidgets.Accordion):
def __init__(self, connectivity, **kwargs):
super().__init__(**kwargs)
Expand Down
69 changes: 69 additions & 0 deletions tvbwidgets/ui/connectivity_ipy/global_context.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# -*- coding: utf-8 -*-
#
# "TheVirtualBrain - Widgets" package
#
# (c) 2022-2023, TVB Widgets Team
#
from typing import Callable


class SingletonMeta(type):
_instances = {}

def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]


class GlobalContext(metaclass=SingletonMeta):
MATRIX_OPTIONS = [('Tracts', 'tracts'), ('Weights', 'weights')]
_observed_state = dict()

def __init__(self):
self._matrix = 'weights'

@property
def matrix(self):
return self._matrix

@matrix.setter
def matrix(self, value):
old_value = self._matrix
self._matrix = value
if old_value == value:
return
try:
observers = self._observed_state['matrix']
for observer in observers:
observer(value)
except KeyError:
pass

def observe(self, observer_func, value_observed):
# type: (Callable[[any], any], str) -> None
"""
Method to register an observer for the specified value.
When the specified value changes (if the value is set to the same as previous,
observers are not triggered), all registered observers ar called
with the new value passed as param.
"""
try:
observers_list = self._observed_state[value_observed]
observers_list.append(observer_func)
except KeyError:
self._observed_state[value_observed] = [observer_func]

def remove_observer(self, observer_func, value_observed):
# type: (Callable[[any], any], str) -> None
"""
Unregister a registered observer.
"""
try:
observers_list = self._observed_state[value_observed]
observers_list.remove(observer_func)
except KeyError:
pass


CONTEXT = GlobalContext()
81 changes: 81 additions & 0 deletions tvbwidgets/ui/connectivity_ipy/operations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# -*- coding: utf-8 -*-
#
# "TheVirtualBrain - Widgets" package
#
# (c) 2022-2023, TVB Widgets Team
#
import ipywidgets
from tvbwidgets.ui.base_widget import TVBWidget
from tvbwidgets.ui.connectivity_ipy.global_context import CONTEXT


class ConnectivityOperations(ipywidgets.VBox, TVBWidget):
def add_datatype(self, datatype):
"""
Currently not supported
"""
pass

def __init__(self, connectivity, **kwargs):
super().__init__(layout={**self.DEFAULT_BORDER, 'width': '50%'}, **kwargs)
self.connectivity = connectivity
selector = self.__get_node_selector()
children = [
ipywidgets.HTML(
value=f'<h3>Operations for Connectivity-{connectivity.number_of_regions}</h3>'
), selector]
self.children = children
self.regions_checkboxes = []

def __get_node_selector(self):
left_children = []
right_children = []
region_labels = self.connectivity.region_labels
# print('region labels: ', region_labels)
for region in region_labels:
label = str(region)
selector = ipywidgets.Checkbox(value=False, description=label, layout={'width': 'max-content'},
indent=False)
if label.startswith('l'):
left_children.append(selector)
else:
right_children.append(selector)

self.regions_checkboxes = [*left_children, *right_children]

left = ipywidgets.VBox(children=(ipywidgets.HTML('<p>Left hemisphere</p>'),
ipywidgets.VBox(children=left_children,
)),
layout={'width': '50%', 'align-items': 'start'}
)
right = ipywidgets.VBox(children=(ipywidgets.HTML('<p>Right hemisphere</p>'),
ipywidgets.VBox(children=right_children,
)),
layout={'width': '50%', 'align-items': 'start'}
)
matrix_dropdown = ipywidgets.Dropdown(
options=CONTEXT.MATRIX_OPTIONS,
value=CONTEXT.matrix,
description='Matrix:'
)

def on_change(value):
CONTEXT.matrix = value['new']

matrix_dropdown.observe(on_change, 'value')

def on_ctx_change(value):
matrix_dropdown.value = value

CONTEXT.observe(on_ctx_change, 'matrix')

container = ipywidgets.VBox(children=(matrix_dropdown,
ipywidgets.HBox(children=(left, right))))
accordion = ipywidgets.Accordion(children=[container], selected_index=None,
layout={'height': '50vh'})
accordion.set_title(0, 'Regions selector')
return accordion

@property
def selected_regions(self):
return map(lambda x: x.description, filter(lambda x: x.value, self.regions_checkboxes))

0 comments on commit 2eba9ff

Please sign in to comment.