{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "tags": []
   },
   "source": [
    "# Stimuli delivery\n",
    "\n",
    "This interface use [Brython](https://brython.info) and the [Radian framework](https://radiant-framework.readthedocs.io/en/latest/) as backend for do the web development in a Python style, this interface inherits all features from [Radian](https://radiant-framework.readthedocs.io/en/latest/) and extends the utilities with an specific ones."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Bare minimum"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from bci_framework.extensions.stimuli_delivery import StimuliAPI\n",
    "\n",
    "# Brython modules\n",
    "from browser import document, html\n",
    "from browser.widgets.dialog import InfoDialog\n",
    "\n",
    "# Main class must inherit `StimuliAPI`\n",
    "class StimuliDelivery(StimuliAPI):\n",
    "    def __init__(self, *args, **kwargs):\n",
    "        # Initialize `StimuliAPI` class\n",
    "        super().__init__(*args, **kwargs)  # very importante line ;)\n",
    "        document.clear()\n",
    "\n",
    "        # -------------------------------------------------------------\n",
    "        # main brython code\n",
    "        document.select_one('body') <= html.H3('Hello world')\n",
    "        \n",
    "        button = html.BUTTON('click me')\n",
    "        button.bind('click', lambda evt: InfoDialog('Hello', 'world'))\n",
    "        document.select_one('body') <= button\n",
    "        # -------------------------------------------------------------\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    # Create and run the server\n",
    "    StimuliDelivery()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src='images/stimuli_bare_minimum.png'></img>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Stimuli area  and Dashboard\n",
    "\n",
    "One of the main features is the possibility to make configurable experiments, in favor of this philosophy, by default they are builded both areas `self.stimuli_area` and `self.dashboard`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# -------------------------------------------------------------\n",
    "# main brython code\n",
    "\n",
    "# Create a division for the stimuli_area and the dashboard\n",
    "self.stimuli_area <= html.H3('Stimuli area')\n",
    "self.dashboard <= html.H3('Dashboard')\n",
    "\n",
    "# Insert a cross in the middle of the stimuli area\n",
    "self.show_cross()\n",
    "\n",
    "# This area is used for external event processord\n",
    "self.show_synchronizer()\n",
    "# -------------------------------------------------------------"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The `self.stimuli_area` at left attemp to be used to display stimuli and the `self.dashboard` on the right is for widgets and configurations."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src='images/areas.png'></img>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Widgets\n",
    "\n",
    "All widgets and styles they are part of [material-components-web](https://github.com/material-components/material-components-web) with a custom framework implementation designed to display widgets and get values.\n",
    "\n",
    "All widgets are available trought the `Widgets` submodule located in the module `bci_framework.extensions.stimuli_delivery.utils`.\n",
    "\n",
    "``` python \n",
    "from bci_framework.extensions.stimuli_delivery.utils import Widgets as w\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The following styles are used for all examples"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "flex = {'margin-bottom': '15px', 'display': 'flex', }\n",
    "flex_title = {'margin-top': '50px', 'margin-bottom': '10px', 'display': 'flex', }"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Typography"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# -------------------------------------------------------------\n",
    "# main brython code\n",
    "\n",
    "self.dashboard <= w.label('headline1', typo='headline1', style=flex)\n",
    "self.dashboard <= w.label('headline2', typo='headline2', style=flex)\n",
    "self.dashboard <= w.label('headline3', typo='headline3', style=flex)\n",
    "self.dashboard <= w.label('headline4', typo='headline4', style=flex)\n",
    "self.dashboard <= w.label('headline5', typo='headline5', style=flex)\n",
    "self.dashboard <= w.label('headline6', typo='headline6', style=flex)\n",
    "self.dashboard <= w.label('body1', typo='body1', style=flex)\n",
    "self.dashboard <= w.label('body2', typo='body2', style=flex)\n",
    "self.dashboard <= w.label('subtitle1', typo='subtitle1', style=flex)\n",
    "self.dashboard <= w.label('subtitle2', typo='subtitle2', style=flex)\n",
    "self.dashboard <= w.label('caption', typo='caption', style=flex)\n",
    "self.dashboard <= w.label('button', typo='button', style=flex)\n",
    "self.dashboard <= w.label('overline', typo='overline', style=flex)\n",
    "# -------------------------------------------------------------"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src='images/stimuli_typography.png'></img>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Buttons"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "    # -------------------------------------------------------------\n",
    "    # main brython code\n",
    "    self.dashboard <= w.label('Buttons', typo='headline4', style=flex_title)\n",
    "    self.dashboard <= w.button('Button 1', style=flex, on_click=lambda: setattr(document.select_one('#for_button'), 'html', 'Button 1 pressed!'))\n",
    "    self.dashboard <= w.button('Button 2', style=flex, on_click=self.on_button2)\n",
    "    self.dashboard <= w.label(f'', id='for_button', typo=f'body1', style=flex)\n",
    "    # -------------------------------------------------------------\n",
    "\n",
    "def on_button2(self):\n",
    "    document.select_one('#for_button').html = 'Button 2 pressed!'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src='images/stimuli_buttons.png'></img>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Switch"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "    # -------------------------------------------------------------\n",
    "    # main brython code\n",
    "    self.dashboard <= w.label('Switch', typo='headline4', style=flex_title)\n",
    "    self.dashboard <= w.switch('Switch 1', checked=True, on_change=self.on_switch, id='my_swicth')\n",
    "    self.dashboard <= w.label(f'', id='for_switch', typo=f'body1', style=flex)\n",
    "    # -------------------------------------------------------------\n",
    "    \n",
    "def on_switch(self, value):\n",
    "    # value = self.widgets.get_value('my_swicth')\n",
    "    document.select_one('#for_switch').html = f'Switch Changed: {value}'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src='images/stimuli_switch.png'></img>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Checkbox"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "    # -------------------------------------------------------------\n",
    "    # main brython code\n",
    "    self.dashboard <= w.label('Checkbox', typo='headline4', style=flex_title)\n",
    "    self.dashboard <= w.checkbox('Checkbox', options=[[f'chb-{i}', False] for i in range(4)], on_change=self.on_checkbox, id='my_checkbox')\n",
    "    self.dashboard <= w.label(f'', id='for_checkbox', typo=f'body1', style=flex)\n",
    "    # -------------------------------------------------------------\n",
    "\n",
    "def on_checkbox(self):\n",
    "    value = w.get_value('my_checkbox')\n",
    "    document.select_one('#for_checkbox').html = f'Checkbox Changed: {value}'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src='images/stimuli_checkbox.png'></img>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Radios"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "    # -------------------------------------------------------------\n",
    "    # main brython code\n",
    "    self.dashboard <= w.label('Radios', typo='headline4', style=flex_title)\n",
    "    self.dashboard <= w.radios('Radios', options=[[f'chb-{i}', f'chb-{i}'] for i in range(4)], on_change=self.on_radios, id='my_radios')\n",
    "    self.dashboard <= w.label(f'', id='for_radios', typo=f'body1', style=flex)\n",
    "    # -------------------------------------------------------------\n",
    "    \n",
    "def on_radios(self):\n",
    "    value = w.get_value('my_radios')\n",
    "    document.select_one('#for_radios').html = f'Radios Changed: {value}'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src='images/stimuli_radios.png'></img>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Select"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "    # -------------------------------------------------------------\n",
    "    # main brython code\n",
    "    self.dashboard <= w.label('Select', typo='headline4', style=flex)\n",
    "    self.dashboard <= w.select('Select', [[f'sel-{i}', f'sel-{i}'] for i in range(4)], on_change=self.on_select, id='my_select')\n",
    "    self.dashboard <= w.label(f'', id='for_select', typo=f'body1', style=flex)\n",
    "    # -------------------------------------------------------------\n",
    "    \n",
    "def on_select(self, value):\n",
    "    # value = w.get_value('my_select')\n",
    "    document.select_one('#for_select').html = f'Select Changed: {value}'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src='images/stimuli_select.png'></img>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Sliders"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "    # -------------------------------------------------------------\n",
    "    # main brython code\n",
    "    # Slider\n",
    "    self.dashboard <= w.label('Slider', typo='headline4', style=flex)\n",
    "    self.dashboard <= w.slider('Slider', min=1, max=10, step=0.1, value=5, on_change=self.on_slider, id='my_slider')\n",
    "    self.dashboard <= w.label(f'', id='for_slider', typo=f'body1', style=flex)\n",
    "\n",
    "    # Slider range\n",
    "    self.dashboard <= w.label('Slider range', typo='headline4', style=flex)\n",
    "    self.dashboard <= w.range_slider('Slider range', min=0, max=20, value_lower=5, value_upper=15, step=1, on_change=self.on_slider_range, id='my_range')\n",
    "    self.dashboard <= w.label(f'', id='for_range', typo=f'body1', style=flex)    \n",
    "    # -------------------------------------------------------------\n",
    "        \n",
    "def on_slider(self, value):\n",
    "    # value = w.get_value('my_slider')\n",
    "    document.select_one('#for_slider').html = f'Slider Changed: {value}'\n",
    "\n",
    "def on_slider_range(self, value):\n",
    "    # value = w.get_value('my_slider')\n",
    "    document.select_one('#for_range').html = f'Range Changed: {value}'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src='images/stimuli_sliders.png'></img>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Sound"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Tones\n",
    "\n",
    "The `Tone` library allows playing single notes using the javascript `AudioContext` backend, the `duration` and the `gain` can also be configured."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from bci_framework.extensions.stimuli_delivery.utils import Tone as t\n",
    "\n",
    "    # -------------------------------------------------------------\n",
    "    # main brython code\n",
    "    duration = 100\n",
    "    gain = 0.5\n",
    "\n",
    "    self.dashboard <= w.button('f#4', on_click=lambda: t('f#4', duration, gain), style={'margin': '0 15px'})\n",
    "    self.dashboard <= w.button('D#0', on_click=lambda: t('D#0', duration, gain), style={'margin': '0 15px'})\n",
    "    self.dashboard <= w.button('B2', on_click=lambda: t('B2', duration, gain), style={'margin': '0 15px'})\n",
    "    # -------------------------------------------------------------"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src='images/stimuli_tones1.png'></img>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Audio files\n",
    "    Not implemented yet"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Pipelines\n",
    "\n",
    "Pipelines consist of the controlled execution of methods with asynchronous timeouts.\n",
    "\n",
    "Let's assume that we have 4 view methods, each method could be a step needed to build a trial."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def view1(self, s1, r1):\n",
    "    print(f'On view1: {s1=}, {r1=}')\n",
    "\n",
    "def view2(self, s1, r1):\n",
    "    print(f'On view2: {s1=}, {r1=}')\n",
    "\n",
    "def view3(self, s1, r1):\n",
    "    print(f'On view3: {s1=}, {r1=}')\n",
    "\n",
    "def view4(self, s1, r1):\n",
    "    print(f'On view4: {s1=}, {r1=}\\n')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The first rule is that all methods will have the same arguments, these arguments consist of the needed information to build a single trial."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now, we need the `trials`, for example, here we define 3 trials (notice the arguments):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "trials = [\n",
    "    {'s1': 'Hola',  # Trial 1\n",
    "           'r1': 91,\n",
    "     },\n",
    "\n",
    "    {'s1': 'Mundo',  # Trial 2\n",
    "     'r1': 85,\n",
    "     },\n",
    "\n",
    "    {'s1': 'Python',  # Trial 3\n",
    "     'r1': 30,\n",
    "     },\n",
    "]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And the `pipeline` consists of a list of sequential methods with a respective timeout `(method, timeout)`, if the `timeout` is a number then this will indicate the milliseconds until the next method call. If the `timeout` is a list, then a random (with uniform distribution) number between that range will be generated on each trial."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pipeline_trial = [\n",
    "    (self.view1, 500),\n",
    "    (self.view2, [500, 1500]),\n",
    "    (self.view3, w.get_value('slider')),\n",
    "    (self.view4, w.get_value('range')),\n",
    "]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Finally, our pipeline can be executed with the method `self.run_pipeline`: "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "self.run_pipeline(pipeline_trial, trials)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "On view1: s1=Hola, r1=91\n",
    "On view2: s1=Hola, r1=91\n",
    "On view3: s1=Hola, r1=91\n",
    "On view4: s1=Hola, r1=91\n",
    "\n",
    "On view1: s1=Mundo, r1=85\n",
    "On view2: s1=Mundo, r1=85\n",
    "On view3: s1=Mundo, r1=85\n",
    "On view4: s1=Mundo, r1=85\n",
    "\n",
    "On view1: s1=Python, r1=30\n",
    "On view2: s1=Python, r1=30\n",
    "On view3: s1=Python, r1=30\n",
    "On view4: s1=Python, r1=30"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Recording EEG automatically\n",
    "\n",
    "If there is a current EEG streaming, the stimuli delivery can be configured to automatically start and stop the EEG recording with the methods `self.start_record()` and `self.stop_record()` respectively."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Send Markers\n",
    "\n",
    "The markers are used to synchronize events. The `self.send_marker` method is available all the time to stream markers through the streaming platform."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "self.send_marker(\"MARKER\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-warning\">\n",
    "\n",
    "The method _self.send_marker_ **works on the delivery views**, so, if you have not an active remote presentation the markers will never send.\n",
    "    \n",
    "</div>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Hardware-based event synchonization\n",
    "\n",
    "In case that there is needed maximum precision about markers synchronization is possible to [attach an external input](https://doi.org/10.3389/fninf.2020.00002) directly to the analog inputs of OpenBCI.  \n",
    "The method `self.show_synchronizer()` do the trick, and is possible to configure the duration of the event with the `blink` argument in `self.send_marker`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "self.send_marker('RIGHT', blink=100)\n",
    "self.send_marker('LEFT', blink=200)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Fixation cross\n",
    "\n",
    " The fixation cross merely serves to center the subject's gaze at the center of the screen. We can control the presence of this mark with the methods:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "self.show_cross()\n",
    "self.hide_cross()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Send annotations\n",
    "\n",
    "In the same way that `markers`, the anotations (as defined in the EDF file format) can be streamed with the `self.send_annotation` method."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "self.send_annotationn('Data record start')\n",
    "self.send_annotationn('The subject yawn', duration=5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Feedbacks\n",
    "\n",
    "The `feedbacks`are used to comunicate the [Data analysis](70-data_analysis.ipynb) and [Data visualizations](75-data_visualizations.ipynb) with the Stimuli Delivery platform. For this purpose, there is a predefined stream channel called `feedback`. The stimuli delivery is only able to read feedbacks, this is useful to develop neurofeedback applications.\n",
    "\n",
    "The asynchronous handler can be configured with the `self.listen_feedbacks` method: "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "self.listen_feedbacks(self.on_feedback)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The target method requires the argument `command`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def on_feedback(self, value):\n",
    "    ..."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
