Source code for tvb.interfaces.web.controllers.spatial.surface_stimulus_controller

# -*- coding: utf-8 -*-
#
#
# TheVirtualBrain-Framework Package. This package holds all Data Management, and 
# Web-UI helpful to run brain-simulations. To use it, you also need to download
# TheVirtualBrain-Scientific Package (for simulators). See content of the
# documentation-folder for more details. See also http://www.thevirtualbrain.org
#
# (c) 2012-2023, Baycrest Centre for Geriatric Care ("Baycrest") and others
#
# This program is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software Foundation,
# either version 3 of the License, or (at your option) any later version.
# This program is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
# PARTICULAR PURPOSE.  See the GNU General Public License for more details.
# You should have received a copy of the GNU General Public License along with this
# program.  If not, see <http://www.gnu.org/licenses/>.
#
#
#   CITATION:
# When using The Virtual Brain for scientific publications, please cite it as explained here:
# https://www.thevirtualbrain.org/tvb/zwei/neuroscience-publications
#
#

"""
.. moduleauthor:: Paula Popa <paula.popa@codemart.ro>
.. moduleauthor:: Bogdan Neacsa <bogdan.neacsa@codemart.ro>
.. moduleauthor:: Ionel Ortelecan <ionel.ortelecan@codemart.ro>
"""

import json
import cherrypy
import numpy

from tvb.adapters.creators.stimulus_creator import *
from tvb.adapters.datatypes.h5.patterns_h5 import StimuliSurfaceH5
from tvb.adapters.forms.equation_forms import get_form_for_equation
from tvb.adapters.forms.equation_plot_forms import EquationTemporalPlotForm, EquationSpatialPlotForm
from tvb.core.adapters.abcadapter import ABCAdapter
from tvb.core.entities.load import try_get_last_datatype, load_entity_by_gid
from tvb.core.neocom import h5
from tvb.interfaces.web.controllers import common
from tvb.interfaces.web.controllers.autologging import traced
from tvb.interfaces.web.controllers.decorators import expose_page, expose_json, expose_fragment
from tvb.interfaces.web.controllers.spatial.base_spatio_temporal_controller import SpatioTemporalController

LOAD_EXISTING_URL = SpatioTemporalController.build_path('/spatial/stimulus/surface/load_surface_stimulus')
RELOAD_DEFAULT_PAGE_URL = SpatioTemporalController.build_path('/spatial/stimulus/surface/reload_default')
CHUNK_SIZE = 20

KEY_TMP_FORM = "temporal-form"


[docs]@traced class SurfaceStimulusController(SpatioTemporalController): """ Control layer for defining Stimulus entities on a cortical surface. """ # These 4 strings are used on client-side to set onchange events on form fields SURFACE_FIELD = 'set_surface' SPATIAL_FIELD = 'set_spatial' TEMPORAL_FIELD = 'set_temporal' DISPLAY_NAME_FIELD = 'set_display_name' SPATIAL_PARAMS_FIELD = 'set_spatial_param' TEMPORAL_PARAMS_FIELD = 'set_temporal_param' base_url = '/spatial/stimulus/surface'
[docs] @cherrypy.expose def set_surface(self, **param): current_surface_stim = common.get_from_session(KEY_SURFACE_STIMULUS) surface_form_field = SurfaceStimulusCreatorForm().surface surface_form_field.fill_from_post(param) current_surface_stim.surface = surface_form_field.value self._reset_focal_points(current_surface_stim)
[docs] @cherrypy.expose def set_spatial_param(self, **param): current_surface_stim = common.get_from_session(KEY_SURFACE_STIMULUS) eq_param_form_class = get_form_for_equation(type(current_surface_stim.spatial)) eq_param_form = eq_param_form_class() eq_param_form.fill_from_trait(current_surface_stim.spatial) eq_param_form.fill_from_post(param) eq_param_form.fill_trait(current_surface_stim.spatial)
[docs] @cherrypy.expose def set_temporal_param(self, **param): current_surface_stim = common.get_from_session(KEY_SURFACE_STIMULUS) eq_param_form_class = get_form_for_equation(type(current_surface_stim.temporal)) eq_param_form = eq_param_form_class() eq_param_form.fill_from_trait(current_surface_stim.temporal) eq_param_form.fill_from_post(param) eq_param_form.fill_trait(current_surface_stim.temporal)
[docs] @cherrypy.expose def set_display_name(self, **param): display_name_form_field = StimulusSurfaceSelectorForm().display_name display_name_form_field.fill_from_post(param) if display_name_form_field.value is not None: current_surface_stim = common.get_from_session(KEY_SURFACE_STIMULUS) current_surface_stim.display_name = display_name_form_field.value
[docs] def step_1(self): """ Used for generating the interface which allows the user to define a stimulus. """ current_surface_stim = common.get_from_session(KEY_SURFACE_STIMULUS) project_id = common.get_current_project().id surface_stim_selector_form = self.algorithm_service.prepare_adapter_form( form_instance=StimulusSurfaceSelectorForm(), project_id=common.get_current_project().id) surface_stim_selector_form.surface_stimulus.data = current_surface_stim.gid.hex surface_stim_creator_form = self.algorithm_service.prepare_adapter_form( form_instance=SurfaceStimulusCreatorForm(), project_id=common.get_current_project().id) if not hasattr(current_surface_stim, 'surface') or not current_surface_stim.surface: default_surface_index = try_get_last_datatype(project_id, SurfaceIndex, SurfaceStimulusCreatorForm.get_filters()) if default_surface_index is None: common.set_error_message(self.MSG_MISSING_SURFACE) current_surface_stim.surface = uuid.uuid4() else: current_surface_stim.surface = uuid.UUID(default_surface_index.gid) surface_stim_creator_form.fill_from_trait(current_surface_stim) surface_stim_selector_form.display_name.data = current_surface_stim.display_name template_specification = dict(title="Spatio temporal - Surface stimulus") template_specification['surfaceStimulusSelectForm'] = self.render_spatial_form(surface_stim_selector_form) template_specification['surfaceStimulusCreateForm'] = self.render_spatial_form(surface_stim_creator_form) self.plotted_equation_prefixes = { self.SURFACE_FIELD: surface_stim_creator_form.surface.name, self.DISPLAY_NAME_FIELD: surface_stim_selector_form.display_name.name } template_specification['mainContent'] = 'spatial/stimulus_surface_step1_main' template_specification['baseUrl'] = self.base_url template_specification['spatialFieldsPrefixes'] = json.dumps(self.plotted_equation_prefixes) template_specification['next_step_url'] = self.build_path('/spatial/stimulus/surface/step_1_submit') template_specification['definedFocalPoints'] = current_surface_stim.focal_points_triangles.tolist() template_specification = self._add_extra_fields_to_interface(template_specification) return self.fill_default_attributes(template_specification)
[docs] def step_2(self): """ Used for generating the interface which allows the user to define a stimulus. """ current_surface_stim = common.get_from_session(KEY_SURFACE_STIMULUS) template_specification = dict(title="Spatio temporal - Surface stimulus") surface_stim_selector_form = self.algorithm_service.prepare_adapter_form( form_instance=StimulusSurfaceSelectorForm(), project_id=common.get_current_project().id) surface_stim_selector_form.display_name.data = current_surface_stim.display_name surface_stim_selector_form.surface_stimulus.data = current_surface_stim.gid.hex template_specification['surfaceStimulusSelectForm'] = self.render_adapter_form(surface_stim_selector_form) template_specification['mainContent'] = 'spatial/stimulus_surface_step2_main' template_specification['next_step_url'] = self.build_path('/spatial/stimulus/surface/step_2_submit') template_specification['loadExistentEntityUrl'] = LOAD_EXISTING_URL template_specification['resetToDefaultUrl'] = RELOAD_DEFAULT_PAGE_URL template_specification['surfaceGID'] = current_surface_stim.surface.hex template_specification[common.KEY_PARAMETERS_CONFIG] = False template_specification['definedFocalPoints'] = current_surface_stim.focal_points_triangles.tolist() plotted_equation_prefixes = { self.DISPLAY_NAME_FIELD: surface_stim_selector_form.display_name.name } template_specification['baseUrl'] = self.base_url template_specification['spatialFieldsPrefixes'] = json.dumps(plotted_equation_prefixes) template_specification.update(self.display_surface(current_surface_stim.surface.hex)) return self.fill_default_attributes(template_specification)
[docs] def do_step(self, step_idx, from_step=None): """ Go to the step given by :param step_idx. In case the next step is the create one (3), we want to remain on the same step as before so that is handled differently depending on the :param from_step. """ if int(step_idx) == 1: return self.step_1() if int(step_idx) == 2: return self.step_2() if int(step_idx) == 3: if self.create_stimulus(): common.set_info_message("Successfully created a new stimulus.") if from_step == 2: return self.step_2() return self.step_1()
def _reset_focal_points(self, surface_stimuli): surface_stimuli.focal_points_triangles = numpy.array([], dtype=int) def _reset_session_stimuli(self): new_surface_stim = SurfaceStimulusCreatorModel() new_surface_stim.temporal = SurfaceStimulusCreatorForm.default_temporal.instance new_surface_stim.spatial = SurfaceStimulusCreatorForm.default_spatial.instance self._reset_focal_points(new_surface_stim) common.add2session(KEY_SURFACE_STIMULUS, new_surface_stim) common.add2session(KEY_TMP_FORM, EquationTemporalPlotForm())
[docs] @expose_page def step_1_submit(self, next_step, do_reset=0, **kwargs): """ Any submit from the first step should be handled here. Update the context then go to the next step as required. In case a reset is needed create a clear context. """ if int(do_reset) == 1: self._reset_session_stimuli() return self.do_step(next_step)
[docs] @expose_page def step_2_submit(self, next_step, **kwargs): """ Any submit from the second step should be handled here. Update the context and then do the next step as required. """ current_surface_stim = common.get_from_session(KEY_SURFACE_STIMULUS) submited_focal_points = kwargs['defined_focal_points'] current_surface_stim.focal_points_triangles = numpy.array(json.loads(submited_focal_points)) return self.do_step(next_step, 2)
[docs] def create_stimulus(self): """ Creates a stimulus from the given data. """ try: current_surface_stim = common.get_from_session(KEY_SURFACE_STIMULUS) surface_stimulus_creator = ABCAdapter.build_adapter_from_class(SurfaceStimulusCreator) self.operation_service.fire_operation(surface_stimulus_creator, common.get_logged_user(), common.get_current_project().id, view_model=current_surface_stim) common.set_important_message("The operation for creating the stimulus was successfully launched.") except (NameError, ValueError, SyntaxError): common.set_error_message("The operation failed due to invalid parameter input.") return False except Exception as ex: common.set_error_message(ex) return False return True
[docs] @expose_page def load_surface_stimulus(self, surface_stimulus_gid, from_step): """ Loads the interface for the selected surface stimulus. """ surface_stim_index = load_entity_by_gid(surface_stimulus_gid) surface_stim_h5_path = h5.path_for_stored_index(surface_stim_index) existent_surface_stim = SurfaceStimulusCreatorModel() with StimuliSurfaceH5(surface_stim_h5_path) as surface_stim_h5: surface_stim_h5.load_into(existent_surface_stim) existent_surface_stim.surface = uuid.UUID(surface_stim_index.fk_surface_gid) existent_surface_stim.display_name = surface_stim_index.user_tag_1 common.add2session(KEY_SURFACE_STIMULUS, existent_surface_stim) return self.do_step(from_step)
[docs] @expose_page def reload_default(self, from_step): """ Just reload default data as if stimulus is None. from_step: not actually used here since when the user selects None from the stimulus entities select we want to take him back to step 1 always. Kept just for compatibility with the normal load entity of a stimulus where we want to stay in the same page. """ self._reset_session_stimuli() return self.do_step(1)
[docs] @expose_json def view_stimulus(self, focal_points): """ Just create the stimulus to view the actual data, don't store to db. Hold the entity in session without the surface, so the next time you need data just get from that one. """ try: current_surface_stim = common.get_from_session(KEY_SURFACE_STIMULUS) current_surface_stim.focal_points_triangles = numpy.array(json.loads(focal_points)) min_time = common.get_from_session(KEY_TMP_FORM).min_tmp_x.value or 0 max_time = common.get_from_session(KEY_TMP_FORM).max_tmp_x.value or 100 stimuli_surface = SurfaceStimulusCreator().prepare_stimuli_surface_from_view_model(current_surface_stim, True) stimuli_surface.configure_space() time = numpy.arange(min_time, max_time, 1) time = time[numpy.newaxis, :] stimuli_surface.configure_time(time) current_surface_stim._temporal_pattern = stimuli_surface.temporal_pattern current_surface_stim._spatial_pattern = stimuli_surface.spatial_pattern data = [] max_value = numpy.max(stimuli_surface()) min_value = numpy.min(stimuli_surface()) for i in range(min(CHUNK_SIZE, stimuli_surface.temporal_pattern.shape[1])): step_data = stimuli_surface(i).tolist() data.append(step_data) result = {'status': 'ok', 'max': max_value, 'min': min_value, 'data': data, "time_min": min_time, "time_max": max_time, "chunk_size": CHUNK_SIZE} return result except (NameError, ValueError, SyntaxError): return {'status': 'error', 'errorMsg': "Could not generate stimulus data. Some of the parameters hold invalid characters."} except Exception as ex: return {'allSeries': 'error', 'errorMsg': ex}
[docs] def fill_default_attributes(self, template_specification): """ Add some entries that are used in both steps then fill the default required attributes. """ template_specification['loadExistentEntityUrl'] = LOAD_EXISTING_URL template_specification['resetToDefaultUrl'] = RELOAD_DEFAULT_PAGE_URL return super(SurfaceStimulusController, self).fill_default_attributes(template_specification, subsection='surfacestim')
[docs] @expose_json def get_stimulus_chunk(self, chunk_idx): """ Get the next chunk of the stimulus data. """ stimulus = common.get_from_session(KEY_SURFACE_STIMULUS) chunk_idx = int(chunk_idx) data = [] for idx in range(chunk_idx * CHUNK_SIZE, min((chunk_idx + 1) * CHUNK_SIZE, stimulus.temporal_pattern.shape[1]), 1): data.append(stimulus(idx).tolist()) return data
[docs] @expose_fragment('spatial/equation_displayer') def get_temporal_equation_chart(self, **form_data): """ Returns the HTML which contains the chart in which is plotted the temporal equation. """ try: temporal_form = EquationTemporalPlotForm() if form_data: temporal_form.fill_from_post(form_data) common.add2session(KEY_TMP_FORM, temporal_form) min_x, max_x, ui_message = self.get_x_axis_range(temporal_form.min_tmp_x.value, temporal_form.max_tmp_x.value) equation = common.get_from_session(KEY_SURFACE_STIMULUS).temporal series_data, display_ui_message = equation.get_series_data(min_range=min_x, max_range=max_x) all_series = self.get_series_json(series_data, "Temporal") if display_ui_message: ui_message = self.get_ui_message(["temporal"]) return {'allSeries': all_series, 'prefix': 'temporal', 'message': ui_message} except NameError as ex: self.logger.exception(ex) return {'allSeries': None, 'errorMsg': "Incorrect parameters for equation passed."} except SyntaxError as ex: self.logger.exception(ex) return {'allSeries': None, 'errorMsg': "Some of the parameters hold invalid characters."} except Exception as ex: self.logger.exception(ex) return {'allSeries': None, 'errorMsg': ex}
[docs] @expose_fragment('spatial/equation_displayer') def get_spatial_equation_chart(self, **form_data): """ Returns the HTML which contains the chart in which is plotted the spatial equation. """ try: spatial_form = EquationSpatialPlotForm() if form_data: spatial_form.fill_from_post(form_data) min_x, max_x, ui_message = self.get_x_axis_range(spatial_form.min_space_x.value, spatial_form.max_space_x.value) equation = common.get_from_session(KEY_SURFACE_STIMULUS).spatial series_data, display_ui_message = equation.get_series_data(min_range=min_x, max_range=max_x) all_series = self.get_series_json(series_data, "Spatial") ui_message = '' if display_ui_message: ui_message = self.get_ui_message(["spatial"]) return {'allSeries': all_series, 'prefix': 'spatial', 'message': ui_message} except NameError as ex: self.logger.exception(ex) return {'allSeries': None, 'errorMsg': "Incorrect parameters for equation passed."} except SyntaxError as ex: self.logger.exception(ex) return {'allSeries': None, 'errorMsg': "Some of the parameters hold invalid characters."} except Exception as ex: self.logger.exception(ex) return {'allSeries': None, 'errorMsg': ex}
def _add_extra_fields_to_interface(self, input_list): """ The fields that have to be added to the existent adapter interface should be added in this method. """ input_list['spatialPlotInputList'] = self.render_adapter_form(EquationSpatialPlotForm()) input_list['temporalPlotInputList'] = self.render_adapter_form(EquationTemporalPlotForm()) return input_list