diff --git a/Local installation b/Local installation index 3be5553700d0bea57a10af9c69ba2cd2421ec951..9faf06c4eb8eece00df4a16373b3638ebdbbd424 100644 --- a/Local installation +++ b/Local installation @@ -1,6 +1,7 @@ # Install Spark: # Install scala + sudo apt install scala # Download Spark from: https://spark.apache.org/downloads.html tar xvf spark-3.2.1-bin-hadoop3.2.tgz @@ -16,6 +17,9 @@ sudo apt-get update sudo apt-get install -y mongodb-org # Start mongoDB + sudo service mongod start + # Check mongoDB status + sudo service mongod status diff --git a/VS2N.py b/VS2N.py index 9a63eae6e93288d0df661b65858dc0677cfc6295..13671d2a0edb617fb96a74a2c02c7905ce7203bf 100755 --- a/VS2N.py +++ b/VS2N.py @@ -3,319 +3,317 @@ Main VS2N class """ +import _thread +import hashlib +import importlib +import logging +import os +import sqlite3 +import time +import traceback +import webbrowser + import dash import dash_bootstrap_components as dbc import flask -import hashlib -from flask import Flask, request, render_template import flask_login -from werkzeug.serving import run_simple +from flask import Flask, render_template, request from werkzeug.middleware.dispatcher import DispatcherMiddleware +from werkzeug.serving import run_simple + from src.Global_Var import Global_Var from src.Modules.General.layout import layout -import os -import traceback -import sqlite3 -import importlib -import time -import _thread -import webbrowser -import logging - -# Print only errors in console --------------------------------------- -log = logging.getLogger('werkzeug') -log.setLevel(logging.ERROR) -# -------------------------------------------------------------------- -modulesNbr = 0 -mongo_exists = False -app_vis = None -users = dict() -g = Global_Var() -__name__ = "__main__" -url = g.config.VS2N_HOST -port = g.config.VS2N_PORT - -# Test port availability --------------------------------------------- -if(not g.testPort(port)): - port += 1 - print("Loading another instance of VS2N") -# -------------------------------------------------------------------- - -# Create flask app instance ------------------------------------------ -flask_app = Flask(__name__, template_folder=os.path.curdir + - "/src/templates", static_folder=os.path.curdir + "/src/static") -flask_app.secret_key = g.config.SECRET_KEY - -login_manager = flask_login.LoginManager() -login_manager.init_app(flask_app) -login_manager.session_protection = "strong" - -# Flask user preperation --------------------------------------------- - -flask_app.config['LOGIN_DISABLED'] = False - -try: - conn = sqlite3.connect("info.db") - users = dict([u for u in conn.execute("SELECT * FROM users")]) - conn.close() -except Exception as e: - # If users database is not generated, disable login - flask_app.config['LOGIN_DISABLED'] = True class User(flask_login.UserMixin): - pass - -# BDD connection ----------------------------------------------------- -mongo_exists = g.mongoConnect() -# -------------------------------------------------------------------- - -# Login manager ------------------------------------------------------ - - -@login_manager.unauthorized_handler -def unauthorized_handler(): - """ Redirect any unauthorized access to login screen. - - Returns: - redirection to login - """ - return flask.redirect(flask.url_for('login')) - - -@flask_app.errorhandler(404) -def page_not_found(e): - """ Handle the 404 error. - - Returns: - redirection to login - """ - return flask.redirect(flask.url_for('login')) - - -@login_manager.user_loader -def user_loader(email): - """ Load user from email address. - - Args: - email (String): user email - - Returns: - user instance - """ - try: - users[email] - except Exception as e: - print("exception : user_loader") - return None - - user = User() - user.id = email - return user - - -@login_manager.request_loader -def request_loader(request): - """ Process request for authentication. - - Args: - request - - Returns: - user instance - """ - try: - email = request.form.get('email') - email = email if email == None else hashlib.sha512( - bytes(email, encoding='utf-8')).hexdigest() - users[email] - except Exception as e: - return - user = User() - user.id = email - - user.is_authenticated = hashlib.sha512( - bytes(request.form['password'], encoding='utf-8')).hexdigest() == users[email] - return user - -# -------------------------------------------------------------------- - -# VS2N Rooting ------------------------------------------------------- - - -@flask_app.route('/login', methods=['GET', 'POST']) -def login(): - """ Handle requests to '/login' page. + pass - Returns: - redirection to login if not connected otherwise to home screen. - """ - if flask_app.config['LOGIN_DISABLED']: - return flask.redirect(flask.url_for('home')) - else: - if flask.request.method == 'GET': - return render_template('login.html') +class VS2N(): + + def __init__(self): + # Print only errors in console --------------------------------------- + log = logging.getLogger('werkzeug') + log.setLevel(logging.ERROR) + # -------------------------------------------------------------------- + self.modulesNbr = 0 + self.mongo_exists = False + self.app_vis = None + self.users = dict() + self.g = Global_Var() + self.url = self.g.config.VS2N_HOST + self.port = self.g.config.VS2N_PORT + + # Test port availability --------------------------------------------- + if(not self.g.testPort(self.port)): + self.port += 1 + print("Loading another instance of VS2N") + # -------------------------------------------------------------------- + + # Create flask app instance ------------------------------------------ + self.flask_app = Flask(__name__, template_folder=os.path.curdir + + "/src/templates", static_folder=os.path.curdir + "/src/static") + self.flask_app.secret_key = self.g.config.SECRET_KEY + + # Flask user preperation --------------------------------------------- + + self.flask_app.config['LOGIN_DISABLED'] = False - email = hashlib.sha512( - bytes(flask.request.form['email'], encoding='utf-8')).hexdigest() try: - if users[email] == hashlib.sha512(bytes(flask.request.form['password'], encoding='utf-8')).hexdigest(): - user = User() - user.id = email - flask_login.login_user(user) - return flask.redirect(flask.url_for('home')) + conn = sqlite3.connect("info.db") + self.users = dict([u for u in conn.execute("SELECT * FROM users")]) + conn.close() except Exception as e: - print("Authentication failed !") - pass - - return flask.redirect(flask.url_for('login')) - - -@flask_app.route('/logout') -def logout(): - """ Logout from VS2N. - - Returns: - redirection to login screen. - """ - if flask_app.config['LOGIN_DISABLED']: - return flask.redirect(flask.url_for('home')) - else: - flask_login.logout_user() - return flask.redirect(flask.url_for('login')) - - -@flask_app.route('/') -def main(): - """ Handle incoming requests to VS2N and check authentication status. - - Returns: - redirection to home if user is authenticated otherwise to login screen. - """ - if flask_app.config['LOGIN_DISABLED']: - return flask.redirect(flask.url_for('home')) - else: - if not flask_login.current_user.is_authenticated: - return render_template('login.html') - else: - return flask.redirect(flask.url_for('home')) - - -@flask_app.route('/home') -@flask_login.login_required -def home(): - """ Get existing modules and check MongoDB availability. - - Returns: - main screen if everything is ok, otherwise show exception. - """ - global mongo_exists - - # init all variables - g.__init__() - # if MongoDB exist - if mongo_exists: - m = os.listdir(os.path.curdir + "/src/Modules") - simulations = [] - g.db = g.client.list_database_names() - - # Load existing simulations - for bdd in g.db: - if bdd != "local" and bdd != "admin" and bdd != "config": - simulations.append(bdd) - i = 1 - - # get modules names - modules = [] - for module in m: - if ".pyc" not in module and "__" not in module and "General" not in module: - modules.append(module.rsplit(".", 1)[0]) - i = i + 1 - sparkVersion = g.createSparkSession() - # return main screen with list of modules and existing simulations - return render_template('index.html', modules=modules, Simulations=simulations, mongodb=g.client.server_info()["version"], vs2n=g.config.VS2N_VERSION, spark=sparkVersion) - else: - mongo_exists = g.mongoConnect() - return render_template('exception.html') - - -def Module(n, g): - """ Helper function to execute each module sparks pre processing (if exist). - - Args: - n (String): module name - g (Global_Var): reference to access global variables - """ - importlib.import_module("src.Modules." + n + ".spark").init(g) - - -def LoadingModules(): - """ Run spark operation for the selected modules - """ - try: - # Spark & MongoDB tasks launch - g.CreateIndexes(g.name) - for module in g.modules: - _thread.start_new_thread(Module, (module, g,)) - - except Exception as e: - print("LoadingModules:" + str(e)) - - -@flask_app.route('/processVis', methods=['POST']) -@flask_login.login_required -def processing(): - """ Start processing and load modules into dashboard. - """ - g.name = request.get_json()["name"] - g.modules = request.get_json()["modules"] - g.updateInterval = float(request.get_json()["updateInterval"]) - - g.modules.append("General") - g.modulesNbr = len(g.modules) - - LoadingModules() + # If users database is not generated, disable login + self.flask_app.config['LOGIN_DISABLED'] = True + + self.login_manager = flask_login.LoginManager() + self.login_manager.init_app(self.flask_app) + self.login_manager.session_protection = "strong" + + # BDD connection ----------------------------------------------------- + if __name__ == '__main__': + self.mongo_exists = self.g.mongoConnect() + # -------------------------------------------------------------------- + + # Create dash app instance ------------------------------------------- + if __name__ == '__main__': + self.app_vis = dash.Dash( + __name__, + requests_pathname_prefix='/vis/', + external_stylesheets=[dbc.themes.BOOTSTRAP, dbc.icons.FONT_AWESOME], + suppress_callback_exceptions=True, + title="VS2N") + self.app_vis.enable_dev_tools(debug=self.g.config.DEBUG) + + # Login manager ------------------------------------------------------ + + @self.login_manager.unauthorized_handler + def unauthorized_handler(): + """ Redirect any unauthorized access to login screen + + Returns: + redirection to login + """ + return flask.redirect(flask.url_for('login')) + + + @self.flask_app.errorhandler(404) + def page_not_found(e): + """ Handle the 404 error + + Returns: + redirection to login + """ + return flask.redirect(flask.url_for('login')) + + + @self.login_manager.user_loader + def user_loader(email): + """ Load user from email address + + Args: + email (String): user email + + Returns: + user instance + """ + try: + self.users[email] + except Exception as e: + print("exception : user_loader") + return None + + user = User() + user.id = email + return user + + + @self.login_manager.request_loader + def request_loader(request): + """ Process request for authentication + + Args: + request + + Returns: + user instance + """ + try: + email = request.form.get('email') + email = email if email == None else hashlib.sha512( + bytes(email, encoding='utf-8')).hexdigest() + self.users[email] + except Exception as e: + return + user = User() + user.id = email + + user.is_authenticated = hashlib.sha512( + bytes(request.form['password'], encoding='utf-8')).hexdigest() == self.users[email] + return user + + # -------------------------------------------------------------------- + + # VS2N Rooting ------------------------------------------------------- + + + @self.flask_app.route('/login', methods=['GET', 'POST']) + def login(): + """ Handle requests to '/login' page + + Returns: + redirection to login if not connected otherwise to home screen + """ + if self.flask_app.config['LOGIN_DISABLED']: + return flask.redirect(flask.url_for('home')) + else: + if flask.request.method == 'GET': + return render_template('login.html') + + email = hashlib.sha512( + bytes(flask.request.form['email'], encoding='utf-8')).hexdigest() + try: + if self.users[email] == hashlib.sha512(bytes(flask.request.form['password'], encoding='utf-8')).hexdigest(): + user = User() + user.id = email + flask_login.login_user(user) + return flask.redirect(flask.url_for('home')) + except Exception as e: + print("Authentication failed !") + pass + + return flask.redirect(flask.url_for('login')) + + + @self.flask_app.route('/logout') + def logout(): + """ Logout from VS2N. + + Returns: + redirection to login screen. + """ + if self.flask_app.config['LOGIN_DISABLED']: + return flask.redirect(flask.url_for('home')) + else: + flask_login.logout_user() + return flask.redirect(flask.url_for('login')) - while (g.modulesNbr != 0): - time.sleep(1) - continue - # Loading Modules to Dashboard - layout().load(app_vis, g) - return "done" + @self.flask_app.route('/') + def main(): + """ Handle incoming requests to VS2N and check authentication status -# Create dash app instance ------------------------------------------- + Returns: + redirection to home if user is authenticated otherwise to login screen + """ + if self.flask_app.config['LOGIN_DISABLED']: + return flask.redirect(flask.url_for('home')) + else: + if not flask_login.current_user.is_authenticated: + return render_template('login.html') + else: + return flask.redirect(flask.url_for('home')) + + + @self.flask_app.route('/home') + @flask_login.login_required + def home(): + """ Get existing modules and check MongoDB availability + + Returns: + main screen if everything is ok, otherwise show exception + """ + + # if MongoDB exist + if self.mongo_exists: + m = os.listdir(os.path.curdir + "/src/Modules") + simulations = [] + self.g.db = self.g.client.list_database_names() + + # Load existing simulations + for bdd in self.g.db: + if bdd != "local" and bdd != "admin" and bdd != "config": + simulations.append(bdd) + i = 1 + + # get modules names + modules = [] + for module in m: + if ".pyc" not in module and "__" not in module and "General" not in module: + modules.append(module.rsplit(".", 1)[0]) + i = i + 1 + sparkVersion = self.g.createSparkSession() + # return main screen with list of modules and existing simulations + return render_template('index.html', modules=modules, Simulations=simulations, mongodb=self.g.client.server_info()["version"], vs2n=self.g.config.VS2N_VERSION, spark=sparkVersion) + else: + self.mongo_exists = self.g.mongoConnect() + return render_template('exception.html') + + @self.flask_app.route('/processVis', methods=['POST']) + @flask_login.login_required + def processing(): + """ Start processing and load modules into dashboard + """ + self.g.name = request.get_json()["name"] + self.g.modules = request.get_json()["modules"] + self.g.updateInterval = float(request.get_json()["updateInterval"]) + + self.g.modules.append("General") + self.g.modulesNbr = len(self.g.modules) + + self.LoadingModules() + + while (self.g.modulesNbr != 0): + time.sleep(1) + continue + + # Loading Modules to Dashboard + layout().load(self.app_vis, self.g) + + def Module(self, n, g): + """ Helper function to execute each module sparks pre processing (if exist) + + Args: + n (String): module name + g (Global_Var): reference to access global variables + """ + importlib.import_module("src.Modules." + n + ".spark").init(g) + + + def LoadingModules(self): + """ Run spark operation for the selected modules + """ + try: + # Spark & MongoDB tasks launch + self.g.CreateIndexes(self.g.name) + for module in self.g.modules: + _thread.start_new_thread(self.Module, (module, self.g,)) -app_vis = dash.Dash( - __name__, - requests_pathname_prefix='/vis/', - external_stylesheets=[dbc.themes.BOOTSTRAP], - suppress_callback_exceptions=True, - title="VS2N") + except Exception as e: + print("LoadingModules:" + str(e)) -app_vis.enable_dev_tools(debug=g.config.DEBUG) -# Start VS2N --------------------------------------------------------- + # Start VS2N --------------------------------------------------------- -def start_VS2N(): - """ Start the virtual server on the web browser . - Returns: - [type]: [description] - """ - print("Starting VS2N") - app_vis.serve_reload_hash + def start_VS2N(self): + """ Start the virtual server on the web browser - webbrowser.open("http://"+url+":"+str(port)) - return app_vis.server + Returns: + Dash server + """ + print("Starting VS2N") + self.app_vis.serve_reload_hash + webbrowser.open("http://"+self.url+":"+str(self.port)) + return self.app_vis.server -application = DispatcherMiddleware(flask_app, { - '/vis': start_VS2N()}) + # Start server ------------------------------------------------------- -# Start server ------------------------------------------------------- if __name__ == '__main__': try: - run_simple(url, port, application, use_debugger=g.config.DEBUG) + vs2n = VS2N() + application = DispatcherMiddleware(vs2n.flask_app, {'/vis': vs2n.start_VS2N()}) + run_simple(vs2n.url, vs2n.port, application, use_debugger=vs2n.g.config.DEBUG) except Exception as e: - traceback.print_exc() \ No newline at end of file + traceback.print_exc() diff --git a/__init__.py b/__init__.py new file mode 100755 index 0000000000000000000000000000000000000000..0519ecba6ea913e21689ec692e81e9e4973fbf73 --- /dev/null +++ b/__init__.py @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Global_Var.py b/src/Global_Var.py index ce8d739856f5660c0e50e2576f6bed3570d7c9cc..747e8cb3474dcd68a908450b0d34de2a36c34df8 100755 --- a/src/Global_Var.py +++ b/src/Global_Var.py @@ -1,15 +1,17 @@ """ This class contains global variables and functions. """ -from pyspark.sql import SparkSession -from pymongo import MongoClient +import socket +import traceback from datetime import timedelta -from pyspark import SparkConf -from config import config + import numpy as np import pymongo -import socket -import time +from pymongo import MongoClient +from pyspark import SparkConf +from pyspark.sql import SparkSession + +from config import config class Global_Var(): @@ -27,10 +29,12 @@ class Global_Var(): updateInterval = 1.0 # 1 second stepMax = 0 Max = 0 + nbrClasses = 0 # General network information ------------------------------------ LayersNeuronsInfo = [] Layer_Neuron = None + NeuronsSize = {} NeuronsNbr = 0 LayersNbr = 0 Dataset = "" @@ -46,9 +50,8 @@ class Global_Var(): """ self.stepMax = 0 self.Max = 0 - self.LayersNeuronsInfo = [] - self.NeuronsSize = {"x":0,"y":0} + self.NeuronsSize = {"x": 0, "y": 0} self.Layer_Neuron = None self.NeuronsNbr = 0 self.LayersNbr = 0 @@ -81,11 +84,11 @@ class Global_Var(): authMechanism='SCRAM-SHA-1') self.MONGODBURL = config().MONGODBURL - self.client.server_info() # will throw an exception if mongodb is not detected + self.client.server_info() # will throw an exception if mongodb is not detected self.db = self.client.list_database_names() return 1 - except Exception as e: - print("mongoConnect:" + str(e)) + except Exception: + print("mongoConnect:" + traceback.format_exc()) return 0 def checkExistance(self, app, component): @@ -117,29 +120,33 @@ class Global_Var(): if ('labels' in self.db.list_collection_names()): col = pymongo.collection.Collection(self.db, 'labels') col.create_index([("T", 1)]) - print("Labels index done") + if self.config.DEBUG: + print("Labels index done") if ('spikes' in self.db.list_collection_names()): col = pymongo.collection.Collection(self.db, 'spikes') col.create_index([("T", 1)]) col.create_index([("i.L", 1)]) col.create_index([("i.N", 1)]) - print("Spikes index done") - + if self.config.DEBUG: + print("Spikes index done") + if ('potential' in self.db.list_collection_names()): col = pymongo.collection.Collection(self.db, 'potential') col.create_index([("T", 1)]) col.create_index([("L", 1)]) col.create_index([("N", 1)]) - print("Potential index done") - + if self.config.DEBUG: + print("Potential index done") + if ('synapseWeight' in self.db.list_collection_names()): col = pymongo.collection.Collection(self.db, 'synapseWeight') col.create_index([("T", 1)]) col.create_index([("C", 1)]) col.create_index([("L", 1)]) col.create_index([("To", 1)]) - print("Synapses index done") + if self.config.DEBUG: + print("Synapses index done") def getLabelTime(self, step, value): """ Return a string that represent time . @@ -175,12 +182,13 @@ class Global_Var(): return x, y - def createHeatMap(self, x, y, data, depth): + def createHeatMap(self, x, y, data, depth, rotation): """ Create a heatmap from a set of synapses values . Args: x , y (int): shape 2D dimensions data (Array): synapses values + rotation: if to rotate the heatmap 90° Returns: Array: Heatmap vector @@ -189,22 +197,24 @@ class Global_Var(): heatmap = np.zeros((x, y)) heatmap[:] = -1 data = data.to_numpy() - - if(depth == 0): # single depth + if(depth == 0): # single depth for d in data: - heatmap[int(d[0])][int(d[1])] = d[3] - else: # multiple dimensions + if rotation: + heatmap[int(d[0])][int(d[1])] = d[3] + else: + heatmap[int(d[1])][int(d[0])] = d[3] + else: # multiple dimensions for d in data: if(d[2] == 0): - #if(heatmap[int(d[0])][int(d[1])] == -1): - heatmap[int(d[0])][int(d[1])] = d[3] - #else: - # heatmap[int(d[0])][int(d[1])] = np.mean(heatmap[int(d[0])][int(d[1])]+d[3]) + if rotation: + heatmap[int(d[0])][int(d[1])] = d[3] + else: + heatmap[int(d[1])][int(d[0])] = d[3] return heatmap - - except Exception as e: - print("createHeatMap: "+str(e)) + + except Exception: + print("createHeatMap: " + traceback.format_exc()) def createVerticalHeatMap(self, data): """ Create a vertical heat map from a given data array . @@ -246,7 +256,7 @@ class Global_Var(): heatMapsZ = [(v * 100)/maxHeatMaps for v in heatMapsZ] return [heatMapsZ, heatMapsZLabel] else: - return [heatMapsZ,heatMapsZ] + return [heatMapsZ, heatMapsZ] def norm(data, Max): """ Normalize data to be between 0 and 1 . @@ -275,14 +285,13 @@ class Global_Var(): self.sparkSession = SparkSession.builder.config(conf=conf) \ .config('spark.jars.packages', 'org.mongodb.spark:mongo-spark-connector_2.12:2.4.4') \ .getOrCreate() - + if not self.config.DEBUG: self.sparkSession.sparkContext.setLogLevel("Error") return self.sparkSession.version - except Exception as e: - print("createSparkSession:" + str(e)) + except Exception: + print("createSparkSession:" + traceback.format_exc()) return "" - def testPort(self, port): """Test if a given port is currently in use . diff --git a/src/Modules/General/callbacks.py b/src/Modules/General/callbacks.py index 4aa190e2689ff57baf385cd0a58a90e1b3293ca2..38c1a476e1af7b8de5e22067e918f74569da6f88 100755 --- a/src/Modules/General/callbacks.py +++ b/src/Modules/General/callbacks.py @@ -4,14 +4,13 @@ """ from collections import deque -import dash import pymongo +import traceback from bson.json_util import dumps from bson.json_util import loads import plotly.graph_objects as go -from dash.exceptions import PreventUpdate +from dash import (no_update, Input, Output, State, ALL, callback_context) from plotly.subplots import make_subplots -from dash.dependencies import Input, Output, State, MATCH from src.templates.callbacksOp import callbacksOp class callbacks(callbacksOp): @@ -208,8 +207,8 @@ class callbacks(callbacksOp): return fig - except Exception as e: - print("processGeneralGraph "+str(e)) + except Exception: + print("processGeneralGraph " + traceback.format_exc()) def processLabelInfoTreemap(data): """ Return a Treemap visualization of actual inputs @@ -252,8 +251,8 @@ class callbacks(callbacksOp): uirevision='no reset of zoom', margin={'l': 0, 'r': 0, 't': 30, 'b': 0}, )} - except Exception as e: - print("processLabelInfoTreemap:"+str(e)) + except Exception: + print("processLabelInfoTreemap:"+ traceback.format_exc()) # ---------------------------------------------------- # Callbacks @@ -286,7 +285,7 @@ class callbacks(callbacksOp): array of outputs that are selected in the callback """ try: - context = dash.callback_context.triggered[0]['prop_id'].split('.')[ + context = callback_context.triggered[0]['prop_id'].split('.')[ 0] # update interval value if changed if(g.updateInterval != float(updateInterval)): @@ -330,7 +329,7 @@ class callbacks(callbacksOp): super.clearData() clearGraphs = not clearGraphs else: - if context == "btn-next" and dash.callback_context.triggered[0]['value'] != None: + if context == "btn-next" and callback_context.triggered[0]['value'] != None: if(sliderValue < g.stepMax): sliderValue = sliderValue + 1 @@ -364,8 +363,8 @@ class callbacks(callbacksOp): return [super.label, sliderValue, 1, g.stepMax, sliderValue, clearGraphs, g.updateInterval] - except Exception as e: - print("progress:" + str(e)) + except Exception: + print("progress:" + traceback.format_exc()) # Callback to controle play/stop button @app.callback( [Output("btnControle", "children"), Output("btnControle", "className"),Output("vis-update", "disabled")], @@ -374,6 +373,7 @@ class callbacks(callbacksOp): """ This is the callback function. It is called when play/stop button is clicked. Args: + visUpdateInterval : interval instance that will cause this function to be called each step playButton (int): number of clicks on the start/stop button sliderValue (int): value of the slider playButtonText (String): text on the start/stop button @@ -381,7 +381,7 @@ class callbacks(callbacksOp): Returns: array of outputs that are selected in the callback """ - if dash.callback_context.triggered[0]['prop_id'].split('.')[0] == "btnControle": + if callback_context.triggered[0]['prop_id'].split('.')[0] == "btnControle": if playButtonText == "Start": if(int(g.stepMax) <= sliderValue): super.visStopped = True @@ -394,13 +394,13 @@ class callbacks(callbacksOp): super.visStopped = True return ["Start", "btn btn-success", True] else: - raise PreventUpdate + return no_update else: if(int(g.stepMax) <= sliderValue): super.visStopped = True return ["Start", "btn btn-success", True] else: - raise PreventUpdate + return no_update # Callback to handle general graph content @app.callback( @@ -418,14 +418,14 @@ class callbacks(callbacksOp): generalGraphSwitchIsOn (bool): general graph switch value Raises: - PreventUpdate: in case we don't want to update the content we rise this execption + no_update: in case we don't want to update the content we rise this execption Returns: content of the graph that contains general information on the network activity """ if generalGraphSwitchIsOn: if len(super.xAxisLabel) > 0 and "["+g.getLabelTime(g.updateInterval, sliderValue)+","+g.getLabelTime(g.updateInterval, sliderValue+1)+"]" == super.xAxisLabel[-1]: - raise PreventUpdate + return no_update if(not super.visStopped): generalData = GeneralModuleData( @@ -442,7 +442,7 @@ class callbacks(callbacksOp): else: if(sliderValue > g.stepMax): - raise PreventUpdate + return no_update else: generalData = GeneralModuleData( int(sliderValue)*float(updateInterval), generalGraphFilter, generalLayerFilter) @@ -450,7 +450,7 @@ class callbacks(callbacksOp): generalData, int(sliderValue), generalGraphFilter, generalLayerFilter) return [generalGraph] else: - raise PreventUpdate + return no_update # Callback to handle label graph content @app.callback( @@ -495,7 +495,7 @@ class callbacks(callbacksOp): labelData) return [labelInfoTreemap] else: - raise PreventUpdate + return no_update # Callback to update the speed of visualization @app.callback( @@ -513,12 +513,12 @@ class callbacks(callbacksOp): try: if(speedValue < 0.25): speedValue = 0.25 - if (speedValue > 5): - speedValue = 5 + if (speedValue > 120): + speedValue = 120 return [speedValue * 1000,speedValue] - except Exception as e: - print("speedControle:" + str(e)) + except Exception: + print("speedControle:" + traceback.format_exc()) # Callback to handle information tab (open or close) @app.callback( @@ -535,15 +535,109 @@ class callbacks(callbacksOp): if information tab should be opened or closed """ try: - if dash.callback_context.triggered[0]["value"] != None: + if callback_context.triggered[0]["value"] != None: return [not isTabOpen] else: return [isTabOpen] - except Exception as e: - print("informationTabController:" + str(e)) + except Exception: + print("informationTabController:" + traceback.format_exc()) + + # Callback to handle the 2D view spiking visualization + @app.callback( + Output("cytoscape-compound", "elements"),Output('spikes_info', 'children'),Output({"index": ALL, "type": '2DView-heatmap'},'figure'),Output({"index": ALL, "type": 'SpikesActivityPerInput'},'figure'), + Input("vis-update", "n_intervals"),Input("v-step", "children"),Input('cytoscape-compound', 'mouseoverNodeData'), + State("interval", "value"),State('cytoscape-compound', 'elements'),State("2DViewLayerFilter", "value")) + def animation2DView(visUpdateInterval,sliderValue, mouseOverNodeData, updateInterval, elements, Layer2DViewFilter): + """ Function called each step to update the 2D view - except Exception as e: - print("Done loading:"+str(e)) + Args: + sliderValue (int): value of the slider + updateInterval (int): update Interval (s) + visUpdateInterval : interval instance that will cause this function to be called each step + mouseOverNodeData : contains data of the hovered node + elements : nodes description + heatmapData : heatmap data + Layer2DViewFilter : selected layers + + Returns: + if information tab should be opened or closed + """ + try: + elements = super.Spikes2D + matrix = {} + indices = {} + if callback_context.triggered[0]['prop_id'].split('.')[0] in ["v-step","vis-update"]: + super.SpikesActivityPerInput = {i:[[0 for j in range(g.nbrClasses+1)] for i in range(g.Layer_Neuron[i])] for i in g.Layer_Neuron if i != "Input"} + for element in elements: + if element["data"]['spiked'] != -1: + element["data"]["spiked"] = 0 + element["data"]["spikes"] = 0 + + spikes = getSpike(int(sliderValue)*float(updateInterval), g.updateInterval,Layer2DViewFilter,True) + + for layer in Layer2DViewFilter: + #neurons = [[0 for j in range(g.nbrClasses)] for i in range(g.Layer_Neuron[layer])] + try: + layerSpikes = [list(list(list(s.values())[0].values())[0].values())[0] for s in spikes if list(s.keys())[0] == layer] + except Exception: + layerSpikes = [] + + if spikes and layerSpikes: + maxSpike = max(layerSpikes) + for spike in spikes: + if list(spike.keys())[0] == layer: + # update the spikes neurons + i = 0 + for element in elements: + if element["data"]['spiked'] != -1: + if (element["data"]["id"] == layer+str(list(list(spike.values())[0].keys())[0])) and (element["data"]["label"] == str(list(list(spike.values())[0].keys())[0])): + spk = list(list(list(spike.values())[0].values())[0].values())[0] + element["data"]["spiked"] = round(spk / maxSpike,2) + element["data"]["spikes"] = spk + super.AccumulatedSpikes2D[layer][int(element["data"]["label"])] += spk + super.SpikesActivityPerInput[layer][list(list(spike.values())[0].keys())[0]][list(list(list(spike.values())[0].values())[0].keys())[0]] = spk + i+=1 + + matrix[layer] = super.toMatrix(super.AccumulatedSpikes2D[layer]) + indices[layer] = super.toMatrix([i for i in range(0,len(super.AccumulatedSpikes2D[layer]))]) + + + + if len(Layer2DViewFilter) != len(super.AccumulatedSpikes2D): + for layer in super.AccumulatedSpikes2D: + if layer not in matrix: + matrix[layer] = [] + indices[layer] = [] + + heatmaps = [{"data":[go.Heatmap(z = matrix[layer], colorscale= 'Reds', customdata = indices[layer], hovertemplate=('Neuron: %{customdata} <br>Spikes: %{z} <extra></extra>'),xgap=4,ygap=4)],"layout":{"xaxis":dict(showgrid = False, zeroline = False),"yaxis":dict(autorange = 'reversed',scaleanchor = 'x',showgrid = False, zeroline = False),"margin":{'l': 0, 'r': 0, 't': 5, 'b': 0},"uirevision":'no reset of zoom', "hoverlabel_align": 'right'}} for layer in super.AccumulatedSpikes2D] + + SpikesActivityPerInput = [{"data":[go.Heatmap(z=super.SpikesActivityPerInput[layer], colorscale= 'Reds',hovertemplate=('Class: %{x} <br>Neuron: %{y} <br>Spikes: %{z} <extra></extra>'),xgap=4,ygap=4)],"layout":{"xaxis":dict(title="Class",tickmode="array",zeroline = False,tickvals=[i for i in range(g.nbrClasses+1)]),"yaxis":dict(title="Neuron",zeroline = False),"margin":{'r': 0,'t': 0},"uirevision":'no reset of zoom'}} for layer in super.SpikesActivityPerInput] + return [elements,[],heatmaps,SpikesActivityPerInput] + else: + + try: + for layer in Layer2DViewFilter: + matrix[layer] = super.toMatrix(super.AccumulatedSpikes2D[layer]) + indices[layer] = super.toMatrix([i for i in range(0,len(super.AccumulatedSpikes2D[layer]))]) + + if len(Layer2DViewFilter) != len(super.AccumulatedSpikes2D): + for layer in super.AccumulatedSpikes2D: + if layer not in matrix: + matrix[layer] = [] + indices[layer] = [] + + heatmaps = [{"data":[go.Heatmap(z = matrix[layer], colorscale= 'Reds', customdata = indices[layer], hovertemplate=('Neuron: %{customdata} <br>Spikes: %{z} <extra></extra>'),xgap=4,ygap=4)],"layout":{"xaxis":dict(showgrid = False, zeroline = False),"yaxis":dict(autorange = 'reversed',scaleanchor = 'x',showgrid = False, zeroline = False),"margin":{'l': 0, 'r': 0, 't': 5, 'b': 0},"uirevision":'no reset of zoom', "hoverlabel_align": 'right'}} for layer in super.AccumulatedSpikes2D] + + SpikesActivityPerInput = [{"data":[go.Heatmap(z=super.SpikesActivityPerInput[layer], colorscale= 'Reds',hovertemplate=('Class: %{x} <br>Neuron: %{y} <br>Spikes: %{z} <extra></extra>'),xgap=4,ygap=4)],"layout":{"xaxis":dict(title="Class",tickmode="array",zeroline = False,tickvals=[i for i in range(g.nbrClasses+1)]),"yaxis":dict(title="Neuron",zeroline = False),"margin":{'r': 0,'t': 0},"uirevision":'no reset of zoom'}} for layer in super.SpikesActivityPerInput] + return [elements,f"Neuron {mouseOverNodeData['label']} : {mouseOverNodeData['spikes']}" if 'spikes' in mouseOverNodeData else "", heatmaps,SpikesActivityPerInput] + except Exception: + print("OnHover:"+traceback.format_exc()) + return no_update + except Exception: + print("animation2DViewController:" + traceback.format_exc()) + + except Exception: + print("Done loading:"+traceback.format_exc()) try: @@ -577,9 +671,9 @@ class callbacks(callbacksOp): for f in filter: if f == "Spikes": if res == []: - res = [getSpike(timestamp, g.updateInterval,layers)] + res = [getSpike(timestamp, g.updateInterval,layers,False)] else: - res.append(getSpike(timestamp, g.updateInterval,layers)) + res.append(getSpike(timestamp, g.updateInterval,layers,False)) if f == "Synapses": if res == []: res = [getSynapse(timestamp, g.updateInterval,layers)] @@ -621,7 +715,7 @@ class callbacks(callbacksOp): }}, {"$group": {"_id": "$L", "C": {"$sum": 1}, "G": {"$max": "$G"}}}, {"$sort": {"_id": 1}} - ]) + ], allowDiskUse = True) # ToJson --------------------------------------------- labels = loads(dumps(labels)) @@ -631,40 +725,47 @@ class callbacks(callbacksOp): for i in labels: Max = max(Max, i["G"]) - L = dict({i["_id"]: 0 for i in labels}) - if not labels: return None - for i in labels: - L[i["_id"]] = L[i["_id"]] + i["C"] + L = dict({i["_id"]: i["C"] for i in labels}) return [L, Max] - def getSpike(timestamp, interval, layer): + def getSpike(timestamp, interval, layer, perNeuron): """ Get spikes activity in a given interval. Args: timestamp (int): timestamp value interval (int): interval value layers (array): array of selected layers + perNeuron (boolean): return global or perNeuron Spikes Returns: array contains spikes """ # MongoDB--------------------- col = pymongo.collection.Collection(g.db, 'spikes') - spikes = col.aggregate([ + + if perNeuron: + spikes = col.aggregate([ + {"$match": {"$and": [{"T": {'$gt': timestamp, '$lte': (timestamp+interval)}},{"i.L": {'$in': layer}}]}}, + {"$group": {"_id": {"L":"$i.L","N":"$i.N","Input":"$Input"},"spikes": {"$sum":1}}},{"$sort": {"_id": 1}} + ], allowDiskUse = True) + else: + spikes = col.aggregate([ {"$match": {"$and": [{"T": {'$gt': timestamp, '$lte': (timestamp+interval)}},{"i.L": {'$in': layer}}]}}, {"$group": {"_id": "$i.L","spikes": {"$sum":1}}},{"$sort": {"_id": 1}} - ]) - - # ---------------------------- + ], allowDiskUse = True) # ToJson---------------------- spikes = loads(dumps(spikes)) # ---------------------------- - spikes = {s["_id"]:s for s in spikes} + if perNeuron: + spikes = [{s["_id"]["L"]:{s["_id"]["N"]:{s["_id"]["Input"]:s["spikes"]}}} for s in spikes] + else: + spikes = {s["_id"]:s for s in spikes} + if not spikes: return None @@ -686,7 +787,7 @@ class callbacks(callbacksOp): synapse = col.aggregate([ {"$match": {"$and": [{"T": {'$gt': timestamp, '$lte': (timestamp+interval)}},{"L": {'$in': layer}}]}}, {"$group": {"_id": "$L","synapseUpdate": {"$sum":1}}},{"$sort": {"_id": 1}} - ]) + ], allowDiskUse = True) # ---------------------------- @@ -715,7 +816,7 @@ class callbacks(callbacksOp): potential = col.aggregate([ {"$match": {"$and": [{"T": {'$gt': timestamp, '$lte': (timestamp+interval)}},{"L": {'$in': layer}}]}}, {"$group": {"_id": "$L","potential": {"$sum":1}}},{"$sort": {"_id": 1}} - ]) + ], allowDiskUse = True) # ---------------------------- # ToJson---------------------- @@ -764,5 +865,5 @@ class callbacks(callbacksOp): return min(100,round((loss*100) / len(spikes), 2)) # --------------------------------------------------------------------- - except Exception as e: - print("Helper functions and MongoDB operations: "+str(e)) + except Exception: + print("Helper functions and MongoDB operations: "+ traceback.format_exc()) \ No newline at end of file diff --git a/src/Modules/General/layout.py b/src/Modules/General/layout.py index 72f7c1b5ef54fd7b35cfb41a75bff47e219d7a10..db02ae245e43c2c78e97128431e90463f7f1e8ca 100755 --- a/src/Modules/General/layout.py +++ b/src/Modules/General/layout.py @@ -2,6 +2,8 @@ """ import importlib +import traceback +import math import dash_daq as daq from collections import deque import dash_cytoscape as cyto @@ -28,12 +30,13 @@ class layout(layoutOp): MaxSpike = dict() MaxPotential = dict() MaxSynapse = dict() + AccumulatedSpikes2D = [] + SpikesActivityPerInput = [] + Spikes2D = dict() # LabelPie Data -------------------------------------------------- Label = [[], []] Max = 0 # ---------------------------------------------------------------- - Nodes = [] - Edges = [] tabs = [] label = " " visStopped = True @@ -43,26 +46,29 @@ class layout(layoutOp): # 2D view -------------------------------------------------------- - def generate2DView(self, Nodes, Edges, g): + def generate2DView(self, g): """ Generates a 2D View of the neural network Args: - Nodes (list): neurons list - Edges (list): synapses list g (Global_Var): reference to access global variables """ + Nodes = [] # Create the neurones and layers for L in g.LayersNeuronsInfo: - Nodes.append({ - 'data': {'id': L["layer"], 'label': L["layer"]} - }) + Nodes.append({'data': {'id': L["layer"], 'label': L["layer"], 'spiked': -1}}) for i in range(L["neuronNbr"]): - Nodes.append( - {'data': {'id': L["layer"]+str(i), 'label': str(i), 'parent': L["layer"]}, 'position': {'x': 25*i, 'y': 0}}) + Nodes.append({'data': {'id': L["layer"]+str(i), 'label': str(i), 'parent': L["layer"], 'spiked': 0.0, 'spikes': 0},'position': {'x': (i % int(math.sqrt(L["neuronNbr"]))) * 70, 'y': (i // int(math.sqrt(L["neuronNbr"]))) * 70},'height': 20,'width': 20}) # Add connections - - + return Nodes + + def toMatrix(self, l): + """ 1D array to 2D + """ + n = int(math.sqrt(len(l))) + Matrix = [l[i:i+n] for i in range(0, len(l), n)] + return Matrix + def clearData(self): """ Clear the data when moved forward or backward for more than one step """ @@ -73,12 +79,13 @@ class layout(layoutOp): self.PotentialGraphY.clear() self.xAxisLabel.clear() self.Label.clear() - self.MaxPotential = dict() - self.MaxSpike = dict() - self.MaxSynapse = dict() + self.MaxPotential.clear() + self.MaxSpike.clear() + self.MaxSynapse.clear() + self.Spikes2D = self.generate2DView(self.g) + self.AccumulatedSpikes2D = {i:[0 for n in self.Spikes2D if n["data"]["spiked"] != -1 and i == n["data"]["parent"]] for i in self.g.Layer_Neuron if i != "Input"} + self.SpikesActivityPerInput = {i:[[0 for j in range(self.g.nbrClasses+1)] for i in range(self.g.Layer_Neuron[i])] for i in self.g.Layer_Neuron if i != "Input"} self.Max = 0 - self.Nodes = [] - self.Edges = [] def Vis(self): """ Create layer components @@ -102,9 +109,6 @@ class layout(layoutOp): "Accuracy", style={"width": "25%", "fontWeight": "500"}), html.Td(str(self.g.Accuracy)+" %", style={"width": "25%"})]) ] - # Generate 2D View ------------------------------------------- - self.generate2DView(self.Nodes, self.Edges, self.g) - # Tabs content ----------------------------------------------- info_vis = dbc.Card( dbc.CardBody([dbc.Card([dbc.CardHeader( @@ -126,13 +130,15 @@ class layout(layoutOp): html.Div([html.Div([ html.Div([ html.Div([ - html.Div([daq.PowerButton( - id="general-graph-switch", - on='True', - size=30, - color="#28a745", - style={"marginLeft": "10px"} - ), + html.Div([daq.PowerButton( + id="general-graph-switch", + on='True', + size=30, + color="#28a745", + style={"marginLeft": "10px"} + ), + html.P("Graphs: ", style={ + "textAlign": "start", "marginLeft": "10px", "marginTop": "4px"}), # Graphs filter dcc.Dropdown( id='GeneralGraphFilter', @@ -140,65 +146,142 @@ class layout(layoutOp): 'label': "Neurons potential", 'value': "Potentials"}], value=["Spikes","Synapses","Potentials"], multi=True, - style={"minWidth": "20%", "marginLeft": "10px", "textAlign": "start"}), + style={"minWidth": "20%", "marginLeft": "5px", "textAlign": "start"}), # Layers filter + html.P("Layers: ", style={ + "textAlign": "start", "marginLeft": "20px", "marginTop": "4px"}), dcc.Dropdown( id='GeneralLayerFilter', options=[{'label': str(i), 'value': str(i)} for i in ( - i for i in self.g.Layer_Neuron if ("Input" not in i and "pool" not in i))], + i for i in self.g.Layer_Neuron if i != "Input")], value=[str(i) for i in ( - i for i in self.g.Layer_Neuron if ("Input" not in i and "pool" not in i))], + i for i in self.g.Layer_Neuron if i != "Input")], multi=True, - style={"minWidth": "20%", "marginLeft": "15px", "textAlign": "start"})], className="d-flex", style={"paddingLeft": "20px", 'width': '100%'}) + style={"minWidth": "20%","marginLeft": "5px", "textAlign": "start"})], className="d-flex", style={"paddingLeft": "20px", 'width': '100%'}) ], className="col-12") ], className="d-flex"), html.Div([dcc.Graph(id='general-graph', config={"displaylogo": False})])], className="col-lg-9 col-sm-12 col-xs-12" if(self.g.labelsExistance) else "col-lg-12 col-sm-12 col-xs-12"), html.Div([ html.Div([ - html.P("Inputs", style={ - "textAlign": "start", "marginLeft": "20px", "marginTop": "4px"}), daq.PowerButton( id="label-graph-switch", on='True', size=30, color="#28a745", - style={"marginLeft": "10px"} - )], className="d-flex"), + style={"marginLeft": "20px"} + ), + html.P("Inputs", style={"textAlign": "start", "marginLeft": "10px", "marginTop": "4px"})], className="d-flex"), dcc.Graph(id='label-graph', config={"displaylogo": False})], className="col-lg-3 col-sm-12 col-xs-12") if(self.g.labelsExistance) else []], className="row") ], style={"padding": "5px"})), label="General information", value="General information"), + # 2D view dcc.Tab(dbc.Card( dbc.CardBody([ - + html.Div([ + # Layers filter + html.P("Layers: ", style={ + "textAlign": "start", "marginRight": "10px", "marginTop": "4px"}), + dcc.Dropdown( + id='2DViewLayerFilter', + options=[{'label': str(i), 'value': str(i)} for i in ( + i for i in self.g.Layer_Neuron if i != "Input")], + value=[str(i) for i in ( + i for i in self.g.Layer_Neuron if i != "Input")], + multi=True, + style={"minWidth": "80%", "textAlign": "start"}), + ], style={"textAlign": "start", },className="d-flex col-lg-12 col-sm-12 col-xs-12"), + html.Div([ + html.Div([html.P("Accumulated Spikes", style={"margin":"0px"})]), + # Accumulated Spikes HeatMap + dcc.Tabs([dcc.Tab(dbc.Card(dbc.CardBody([ + dcc.Graph(id={"type":"2DView-heatmap","index":i}, config={"displaylogo": False}) + ])),label=i, value='2Dview-'+str(x)) for x, i in enumerate(self.g.Layer_Neuron) if i != "Input"],value="2Dview-1"), + ], style={"textAlign": "start", }, className="col-lg-3 col-sm-12 col-xs-12") + , html.Div( [ + html.Div([html.P("2D Space", style={"margin":"0px"})]), + html.Div([ cyto.Cytoscape( id='cytoscape-compound', - layout={'name': 'grid'}, + layout={'name': 'preset'}, + boxSelectionEnabled=False, style={'width': '100%', 'height': '100%'}, stylesheet=[ { 'selector': 'node', - 'style': {'content': 'data(label)'} + 'style': {'label': 'data(label)'} + }, + { + 'selector': '[spiked <= 1.0]', + 'style': { + 'background-color': 'rgb(227,70,70)', + 'height': 45, + 'width': 45 + } }, { - 'selector': '.layers', - 'style': {'width': 5} + 'selector': '[spiked < 0.8]', + 'style': { + 'background-color': 'rgb(227,100,100)', + 'height': 40, + 'width': 40 + } + }, + { + 'selector': '[spiked < 0.6]', + 'style': { + 'background-color': 'rgb(227,130,130)', + 'height': 35, + 'width': 35 + } + }, + { + 'selector': '[spiked < 0.4]', + 'style': { + 'background-color': 'rgb(227,160,160)', + 'height': 30, + 'width': 30 + } + }, + { + 'selector': '[spiked < 0.2]', + 'style': { + 'background-color': 'rgb(227,190,190)', + 'height': 25, + 'width': 25 + } + }, + { + 'selector': '[spiked = 0.0]', + 'style': { + 'background-color': 'rgb(199,197,197)', + 'height': 20, + 'width': 20 + } }, { - 'selector': '.neurons', - 'style': {'line-style': 'dashed'} + 'selector': '[spiked = -1]', + 'style': { + 'background-color': 'rgb(227,227,227)' + } } ], - elements=self.Nodes + self.Edges - ) + elements=self.Spikes2D + )],style={'position': 'absolute','width': '100%','height': '100%','z-index': 999,"background": "rgba(68, 71, 99, 0.05)"}), + html.P(id="spikes_info", style={"padding": "8px","margin":"0px"}) - ], style={"background": "rgb(227, 245, 251)", "height": "60vh", "textAlign": "start", "padding": "0px", "width":"70%"}), - html.Div( - [ - - ], style={"height": "60vh", "textAlign": "start", "padding": "0px", "width":"30%"}) - ])), label="2D view", value="2Dview")], id="tabinfo", value="General information"), + ], style={ "height": "50vh","textAlign": "start", "padding": "0px"},className="col-lg-5 col-sm-12 col-xs-12"), + + # 3D destribution + html.Div([ + html.Div([html.P("Spikes Activity Per Input", style={"margin":"0px"})]), + dcc.Tabs([dcc.Tab(dbc.Card(dbc.CardBody([ + dcc.Graph(id={"type":"SpikesActivityPerInput","index":i}, config={"displaylogo": False}) + ])),label=i, value='SpikesActivityPerInput-'+str(x)) for x, i in enumerate(self.g.Layer_Neuron) if i != "Input"],value="SpikesActivityPerInput-1") + ], style={"textAlign": "start", },className="col-lg-4 col-sm-12 col-xs-12") + + ], className="row")), label="2D view", value="2Dview")], id="tabinfo", value="General information"), ] ), @@ -212,8 +295,8 @@ class layout(layoutOp): try: tabs.append(dcc.Tab(importlib.import_module( ".Modules."+m+".layout", package="src").layout().load(self.app, self.g), label=m)) - except Exception as e: - print("Tabs appending:"+str(e)) + except Exception: + print("Tabs appending:"+traceback.format_exc()) # App layout self.app.layout = html.Div([ @@ -232,16 +315,16 @@ class layout(layoutOp): dcc.Tabs(tabs, id="tabs"), # Control Layout dbc.Row([ - html.Div([dbc.Button("<", id="btn-back", className="btn btn-default", style={"marginTop": "12px", "fontWeight": "500", "backgroundColor": "rgb(68, 71, 99)"}), + html.Div([dbc.Button(html.I(className="fa-solid fa-angle-left"), id="btn-back", className="btn btn-default", style={"marginTop": "12px", "fontWeight": "500", "backgroundColor": "rgb(68, 71, 99)"}), dbc.Button("Start", id="btnControle", className="btn btn-success", style={ "marginTop": "12px", "marginLeft": "5px", "width": "100px", "fontWeight": "500"}), - dbc.Button(">", id="btn-next", className="btn btn-default", style={"marginTop": "12px", "marginLeft": "5px", "fontWeight": "500", "backgroundColor": "rgb(68, 71, 99)"})], className="col-md-4 col-sm-12 col-lg-4"), + dbc.Button(html.I(className="fa-solid fa-angle-right"), id="btn-next", className="btn btn-default", style={"marginTop": "12px", "marginLeft": "5px", "fontWeight": "500", "backgroundColor": "rgb(68, 71, 99)"})], className="col-md-4 col-sm-12 col-lg-4"), html.Div([ html.Div([ html.Span("Update Speed (s)", className="input-group-text") ], className="input-group-prepend"), - dbc.Input(type="number", id="speed", value=1, min=0.25, max=5, step=0.25, style={ + dbc.Input(type="number", id="speed", value=1, min=0.25, max=120, step=0.25, style={ "width": "30%", "textAlign": "center"}) ], className="input-group col-md-12 col-sm-12 col-lg-4", style={"height": "38px", "paddingTop": "12px"}), diff --git a/src/Modules/General/spark.py b/src/Modules/General/spark.py index cb1cf8608e1bbfffb0898436a5d1b857f0b19f0c..eb175ab21d45eba707a0eedd9a5c2eb12e83e735 100755 --- a/src/Modules/General/spark.py +++ b/src/Modules/General/spark.py @@ -56,6 +56,8 @@ class spark(sparkOp): if ('spikes' in self.g.db.list_collection_names()): M = max(M, pymongo.collection.Collection( self.g.db, 'spikes').find_one(sort=[("T", -1)])["T"]) + self.g.nbrClasses = pymongo.collection.Collection( + self.g.db, 'spikes').find_one(sort=[("Input", -1)])["Input"] if ('potential' in self.g.db.list_collection_names()): M = max(M, pymongo.collection.Collection( self.g.db, 'potential').find_one(sort=[("T", -1)])["T"]) @@ -69,8 +71,8 @@ class spark(sparkOp): else: print("No labels") - except Exception as e: - print("MongoError:" + str(e)) + except Exception: + print("GlobalSparkMongoError:" + traceback.format_exc()) pass self.g.Max = M self.g.stepMax = int(M/self.g.updateInterval)+1 @@ -78,22 +80,22 @@ class spark(sparkOp): try: data = next(col, None) self.g.Accuracy = data["Acc"] - except Exception as e: + except Exception: self.g.Accuracy = "--" print("No accuracy recorded") pass try: self.g.finalLabels = data["NLabel"] - except Exception as e: + except Exception: print("No output neuron classes recorded") self.g.finalLabels = None pass # ------------------------------------------------------------ - print("done ", self.MODULE_NAME) + if self.g.config.DEBUG: + print("done ", self.MODULE_NAME) - except Exception as e: - print("Error:" + str(e)) - traceback.print_exc() + except Exception: + print("GlobalSpark:" + traceback.format_exc()) pass diff --git a/src/Modules/MultiLayer/callbacks.py b/src/Modules/MultiLayer/callbacks.py index 89f40c7496202fd719aa1415de7ccf277b8a4eb9..d9065d6384c2b142e02eb4912838db2d2a38037f 100755 --- a/src/Modules/MultiLayer/callbacks.py +++ b/src/Modules/MultiLayer/callbacks.py @@ -11,6 +11,7 @@ import plotly.graph_objects as go from dash import dcc, html from bson.json_util import dumps, loads from dash.exceptions import PreventUpdate +import traceback from dash.dependencies import ALL, MATCH, Input, Output, State from src.templates.callbacksOp import callbacksOp @@ -49,11 +50,11 @@ class callbacks(callbacksOp): super.clearData() return [display, clear] - except Exception as e: - print("display:"+str(e)) + except Exception: + print("display:" + traceback.format_exc()) - except Exception as e: - print("CallbackName:" + str(e)) + except Exception: + print("CallbackName:" + traceback.format_exc()) # ---------------------------------------------------------------- # Callback related Data retreiving functions @@ -67,5 +68,5 @@ class callbacks(callbacksOp): print("Add MongoDB functions here") - except Exception as e: - print("Data process loading:"+str(e)) \ No newline at end of file + except Exception: + print("Data process loading:" + traceback.format_exc()) \ No newline at end of file diff --git a/src/Modules/MultiLayer/layout.py b/src/Modules/MultiLayer/layout.py index 07af6ad30c26f69c9c9333038917f5ec0bef8ba5..275cf2cb3ab237def4a0309bddd261907640bfcc 100755 --- a/src/Modules/MultiLayer/layout.py +++ b/src/Modules/MultiLayer/layout.py @@ -3,6 +3,7 @@ from collections import deque from dash import dcc, html +import traceback import dash_bootstrap_components as dbc from .callbacks import callbacks from src.templates.layoutOp import layoutOp @@ -49,5 +50,5 @@ class layout(layoutOp): # callbacks(self.app, self.g) # Return the Layer return layer - except Exception as e: - print("MultiLayer:" + str(e)) \ No newline at end of file + except Exception: + print("MultiLayer:" + traceback.format_exc()) \ No newline at end of file diff --git a/src/Modules/MultiLayer/spark.py b/src/Modules/MultiLayer/spark.py index cf2195eef34f09798c0a44436bc3b292068b1847..835b30725336698c947e8f934e5f1caebf513e4c 100755 --- a/src/Modules/MultiLayer/spark.py +++ b/src/Modules/MultiLayer/spark.py @@ -54,7 +54,6 @@ class spark(sparkOp): self.g.modules = [module for module in self.g.modules if module != MODULE_NAME] print("done ", MODULE_NAME) - except Exception as e: - print("Error:" + str(e)) - traceback.print_exc() + except Exception: + print("Error:" + traceback.format_exc()) pass \ No newline at end of file diff --git a/src/Modules/Neuron/callbacks.py b/src/Modules/Neuron/callbacks.py index 766871b1b508916cfeb56d4f025de3de238e3dd8..eb92f737e47bada1c5c1b5032e48e6ea877a108b 100755 --- a/src/Modules/Neuron/callbacks.py +++ b/src/Modules/Neuron/callbacks.py @@ -6,14 +6,15 @@ import dash import pymongo import statistics +import traceback import dash_daq as daq from collections import deque import plotly.graph_objects as go from dash import dcc, html from bson.json_util import dumps, loads -from dash.exceptions import PreventUpdate from dash.dependencies import ALL, MATCH, Input, Output, State from src.templates.callbacksOp import callbacksOp +from dash import no_update class callbacks(callbacksOp): """ Callbacks class """ @@ -101,8 +102,8 @@ class callbacks(callbacksOp): if(len(graphsArea) == 0): graphsArea.append(addSpaceHolder(app)) return graphsArea - except Exception as e: - print("InsertRemoveNewElement: " + str(e)) + except Exception: + print("InsertRemoveNewElement: " + traceback.format_exc()) try: @@ -121,8 +122,8 @@ class callbacks(callbacksOp): return [[{'label': str(i), 'value': str(i)} for i in range(g.Layer_Neuron[selectedLayer])], ""] else: return [[], ""] - except Exception as e: - print("NeuronSelection: " + str(e)) + except Exception: + print("NeuronSelection: " + traceback.format_exc()) try: # Callback to handle filtering when 3D view clicked @@ -140,8 +141,8 @@ class callbacks(callbacksOp): return [super.SpikesSameClass(filterClass, g), {"visibility": "hidden"}] else: return [super.SpikesSameClass(filterClass["points"][0], g), {"visibility": "visible"}] - except Exception as e: - print("NeuronFreqFilter: " + str(e)) + except Exception: + print("NeuronFreqFilter: " + traceback.format_exc()) try: # Callback to handle clearing content when received a clear signal @@ -165,8 +166,8 @@ class callbacks(callbacksOp): super.clearData(existingIndexes) return [displayContent, clearGraphs] - except Exception as e: - print("clearProcess: "+str(e)) + except Exception: + print("clearProcess: "+ traceback.format_exc()) try: # Callback to handle spikes per interval graph and spike class representation @app.callback([Output({"index": MATCH, "layer": MATCH, "neuron": MATCH, "type": "FreqGraph"}, "figure"), @@ -184,7 +185,7 @@ class callbacks(callbacksOp): isOnAcc (bool): whether spikes class graph is active or not Raises: - PreventUpdate: in case we don't want to update the content we rise this execption + no_update: in case we don't want to update the content we rise this execption Returns: three graphs of the selected neuron @@ -212,7 +213,7 @@ class callbacks(callbacksOp): return output else: - raise PreventUpdate + return no_update else: if(sliderValue == 0): data = getSpikesOfNeuron( @@ -228,7 +229,7 @@ class callbacks(callbacksOp): return output else: - raise PreventUpdate + return no_update else: # after adding to the screen if(selectedItem["index"] not in super.xAxisSpikeNbrLabel): @@ -247,9 +248,9 @@ class callbacks(callbacksOp): return output else: - raise PreventUpdate - except Exception as e: - print("processSpikeRelatedGraphs: " + str(e)) + return no_update + except Exception: + print("processSpikeRelatedGraphs: " + traceback.format_exc()) try: # Callback to handle neuron potential graph @@ -266,7 +267,7 @@ class callbacks(callbacksOp): isOn (bool): whether this graph is active or not Raises: - PreventUpdate: in case we don't want to update the content we rise this execption +return no_update: in case we don't want to update the content we rise this execption Returns: neuron potential graph of the selected neuron @@ -292,7 +293,7 @@ class callbacks(callbacksOp): output = [neuronPotentialDrawGraph(selectedItem["index"], data, super.xAxisPotentialGraph[selectedItem["index"]], super.xAxisPotentialLabel[selectedItem["index"]], super.yAxisPotentialGraph, isOn)] return output else: - raise PreventUpdate + return no_update else: if(sliderValue == 0): data = getPotentialOfNeuron( @@ -307,7 +308,7 @@ class callbacks(callbacksOp): return output else: - raise PreventUpdate + return no_update else: # after adding to the screen if(selectedItem["index"] not in super.xAxisPotentialLabel): @@ -325,9 +326,9 @@ class callbacks(callbacksOp): return output else: - raise PreventUpdate - except Exception as e: - print("processPotential: " + str(e)) + return no_update + except Exception: + print("processPotential: " + traceback.format_exc()) try: @@ -359,7 +360,7 @@ class callbacks(callbacksOp): color="#28a745", style={"marginLeft": "10px","marginRight": "10px"} ), - html.Div("−", className="btn-danger btn-circle", style={"fontWeight": "500","fontSize":"16px"}, id={"index": str(index), "type": "DeleteComponent"}),html.Div([html.Span(layer+" N: "+neuron, style={"fontSize": "13px", "paddingTop": "10px", "marginRight": "10px"}, className="badge alert-info")], className="d-flex", style={"direction": "rtl", "width": "100%"})], className="d-flex", style={"height": "35px"}), + html.Button(html.I(className="fa-solid fa-trash"), style={"fontWeight": "500","fontSize":"16px", "marginLeft": "10px","color":"red","backgroundColor":"#00110000","border":"0"}, id={"index": str(index), "type": "DeleteComponent"}),html.Div([html.Span(layer+" N: "+neuron, style={"fontSize": "13px", "paddingTop": "10px", "marginRight": "10px"}, className="badge alert-info")], className="d-flex", style={"direction": "rtl", "width": "100%"})], className="d-flex", style={"height": "35px"}), dcc.Graph(id={"index": str(index), "type": "FreqGraph", "layer": layer, "neuron": neuron}, style={ "width": "100%", "height": "290px"}, className="col-12", animate=False, config={"displaylogo": False}), ], className="col-lg-4 col-sm-12 col-xs-12" if(g.finalLabels != None) else "col-lg-6 col-sm-12 col-xs-12"), @@ -386,7 +387,7 @@ class callbacks(callbacksOp): color="#28a745", style={"marginLeft": "10px"})], className="row", style={"height": "35px"}), dcc.Graph(id={"index": str(index), "type": "AccuracyGraph", "layer": layer, "neuron": neuron}, style={ - "width": "100%", "height": "290px"}, className="col-12", animate=False, config={"displaylogo": False})], className="col-lg-4 col-sm-6 col-xs-6" if(g.finalLabels != None) else "", style={} if(g.finalLabels != None) else {'display': 'none'})],className="row")], style={"background": "rgb(242, 248, 255)", "paddingBottom": "10px", "marginBottom": "10px", "margin": "5px", "borderRadius": "10px"}, id="VisComponent"+str(index)) + "width": "100%", "height": "290px"}, className="col-12", animate=False, config={"displaylogo": False})], className="col-lg-4 col-sm-6 col-xs-6" if(g.finalLabels != None) else "", style={} if(g.finalLabels != None) else {'display': 'none'})],className="row")], style={"background": "rgb(242, 248, 255)", "paddingBottom": "10px", "marginBottom": "10px", "padding": "5px", "borderRadius": "10px"}, id="VisComponent"+str(index)) def addSpaceHolder(app): """ Adds a space holder area when no graphs are selected. @@ -492,8 +493,8 @@ class callbacks(callbacksOp): paper_bgcolor= "rgba(255, 255, 255,0)", plot_bgcolor= "rgba(255, 255, 255,0)" )} - except Exception as e: - print("spikeCountDrawGraph: "+str(e)) + except Exception: + print("spikeCountDrawGraph: "+ traceback.format_exc()) def neuronPotentialDrawGraph(index, data, xAxis, xAxisLabel, yAxisList, isOn): """ Create scatter plot for selected neuron, that contains @@ -595,8 +596,8 @@ class callbacks(callbacksOp): paper_bgcolor= "rgba(255, 255, 255,0)", plot_bgcolor= "rgba(255, 255, 255,0)" )} - except Exception as e: - print("neuronPotentialDrawGraph: "+str(e)) + except Exception: + print("neuronPotentialDrawGraph: "+ traceback.format_exc()) def classDrawGraph(index, data, isOn, labels): """ Create scatter polar plot for selected neuron, that represents @@ -690,8 +691,8 @@ class callbacks(callbacksOp): margin={'l': 0, 'r': 0, 't': 25, 'b': 30}, ) } - except Exception as e: - print("classDrawGraph: "+str(e)) + except Exception: + print("classDrawGraph: "+ traceback.format_exc()) # ------------------------------------------------------------ # MongoDB operations @@ -753,5 +754,5 @@ class callbacks(callbacksOp): return None return FreqForNeuron - except Exception as e: - print("Helper functions: "+str(e)) \ No newline at end of file + except Exception: + print("Helper functions: "+ traceback.format_exc()) \ No newline at end of file diff --git a/src/Modules/Neuron/layout.py b/src/Modules/Neuron/layout.py index b0679f189c213a9f237e1bf14ccd273c3d7e485d..747874158c968582ddaa89f99a63a8da39a201a1 100755 --- a/src/Modules/Neuron/layout.py +++ b/src/Modules/Neuron/layout.py @@ -4,6 +4,7 @@ import pymongo from itertools import product from collections import deque +import traceback from .callbacks import callbacks from bson.json_util import dumps from bson.json_util import loads @@ -22,7 +23,6 @@ class layout(layoutOp): xAxisPotentialGraph = dict() yAxisSpikeNbrGraph = dict() yAxisPotentialGraph = dict() - SpikePerNeuron = None def clearData(self, indexes): """ Clear the data when moved forward or backward for more than one step @@ -56,8 +56,8 @@ class layout(layoutOp): """ try: self.clearData([]) - print("neuron-vis") - self.SpikePerNeuron = self.getSpikePerNeuron(self.g) + if self.g.config.DEBUG: + print("neuron-vis") layer = dbc.Card( dbc.CardBody( [ @@ -75,7 +75,7 @@ class layout(layoutOp): options=[], multi=False, style={'width': '150px', "marginLeft": "10px", "textAlign": "start"}), - dbc.Button("+", id="AddComponentNeuron", n_clicks=0, style={ + dbc.Button(html.I(className="fa-solid fa-plus"), id="AddComponentNeuron", n_clicks=0, style={ "fontWeight": "500", "marginLeft": "20px", "height": "36px", "backgroundColor": "rgb(68, 71, 99)", "borderColor": "rgb(68, 71, 99)"}), html.Div(id='clear-Neuron', children="False", style={'display': 'none'}), html.Div(id='display-Neuron', children="False", style={'display': 'none'}) ], className="d-flex"), html.Div(id={'type': "GraphsAreaNeuron"}, children=[html.Div(id={'type': "OutputNeurons"}, children=[dcc.Graph(id="SpikePerNeuronFreq", figure=self.SpikePerNeuron3D(self.g), config={"displaylogo": False}, className="col-6"), @@ -87,8 +87,8 @@ class layout(layoutOp): callbacks(self,self.app, self.g) # Return the Layer return layer - except Exception as e: - print("NeuronLayer: " + str(e)) + except Exception: + print("NeuronLayer: " + traceback.format_exc()) # ---------------------------------------------------------------- # Helper functions @@ -113,7 +113,7 @@ class layout(layoutOp): 'title': 'No Labels detected'}} else: - data = self.SpikePerNeuron + data = self.getSpikePerNeuron(self.g) total = 0 @@ -122,11 +122,9 @@ class layout(layoutOp): xx = [N["i"]["N"] for N in data] yy = [(count["count"]/total) for count in data] - zz = [int(c["Label"]) for i, c in product(data, g.finalLabels) if ( - int(c["N"]) == int(i["i"]["N"]) and c["L"] == i["i"]["L"])] + zz = [int(c["Label"]) for i, c in product(data, g.finalLabels) if (int(c["N"]) == int(i["i"]["N"]) and c["L"] == i["i"]["L"])] labels = list(dict.fromkeys(zz)) - items = [[[item[0], item[1], item[2]] for item in zip(xx, yy, zz) if item[2] == x] for x in labels] @@ -179,13 +177,11 @@ class layout(layoutOp): return {'data': [], 'layout': {'margin': {'l': 0, 'r': 0, 't': 30, 'b': 0}}} else: - data = self.SpikePerNeuron - - data = [d for d, l in product(data, g.finalLabels) if ( - int(l["N"]) == d['i']['N'] and int(l["Label"]) == filteredClass["z"])] + data = self.getSpikePerNeuron(self.g) + data = [d for d, l in product(data, g.finalLabels) + if (l["N"] == d['i']['N'] and l["Label"] == filteredClass["z"])] xx = [N["i"]["N"] for N in data] - yy = [(count["count"]) for count in data] - + yy = [count["count"] for count in data] graph = {'data': [go.Bar( x=[x for x in range(len(xx))], y=yy, diff --git a/src/Modules/Neuron/spark.py b/src/Modules/Neuron/spark.py index 89dd65c52e621af4013f8fac958657cbc9748b8c..54dafba7cc68f4572e9155fd13dca0fa29a63f9d 100755 --- a/src/Modules/Neuron/spark.py +++ b/src/Modules/Neuron/spark.py @@ -53,9 +53,9 @@ class spark(sparkOp): if(not self.DOCUMENT_NAME in self.g.db.list_collection_names()): print(self.DOCUMENT_NAME, "not found") self.g.modules = [module for module in self.g.modules if module != self.MODULE_NAME] - print("done ", self.MODULE_NAME) + if self.g.config.DEBUG: + print("done ", self.MODULE_NAME) - except Exception as e: - print("Error:" + str(e)) - traceback.print_exc() + except Exception: + print("Error:" + traceback.format_exc()) pass diff --git a/src/Modules/Synapse/callbacks.py b/src/Modules/Synapse/callbacks.py index 6d73376175a3a3322817a0bae883610728ef8a3e..8e66f73f913ba0201d9f385ca6c9bd497a375977 100755 --- a/src/Modules/Synapse/callbacks.py +++ b/src/Modules/Synapse/callbacks.py @@ -3,20 +3,17 @@ Dash callbacks are the responsible on updating graphs each step. """ -import time from dash.dependencies import Input, Output, State, MATCH, ALL import dash import pymongo +import traceback from dash import dcc, html import dash_daq as daq from bson.json_util import loads -from bson.json_util import dumps -from dash.exceptions import PreventUpdate +from dash import no_update import plotly.graph_objects as go from plotly.subplots import make_subplots from collections import deque -import statistics -from itertools import groupby import numpy as np import pandas as pd from src.templates.callbacksOp import callbacksOp @@ -44,10 +41,8 @@ class callbacks(callbacksOp): [Input("AddComponentSynapse", "n_clicks"), Input( {"type": "DeleteComponent", "index": ALL}, "n_clicks")], [State({"type": "GraphsAreaSynapse"}, "children"), State({"type": "GlobalHeatMapAreaSynapse"}, "children"), - State("LayerFilterSynapse", "value"), State( - "NeuronFilterSynapse", "value"), State("HeatMapX", "value"), - State("HeatMapY", "value")]) - def InsertRemoveNewElement(addButtonClick, deleteButtonsClick, graphsArea, GlobalHeatmap, selectedLayer, selectedNeuron, HeatMapX, HeatMapY): + State("LayerFilterSynapse", "value"), State("NeuronFilterSynapse", "value"),State("heatmapEditInfo", "data")]) + def InsertRemoveNewElement(addButtonClick, deleteButtonsClick, graphsArea, GlobalHeatmap, selectedLayer, selectedNeuron, heatmapEditInfo): """ Insert of remove element. Args: @@ -57,15 +52,15 @@ class callbacks(callbacksOp): GlobalHeatmap (list): list of GLobalHeatma component children selectedLayer (String): name of the selected layer selectedNeuron (int): id of selected neuron - HeatMapX (int): X dimension for heatmaps - HeatMapY (int): Y dimension for heatmaps + heatmapEditInfo (Array): heatmap edit stats Returns: the updated content of 'GraphsAreaSynapse' and 'GlobalHeatMapAreaSynapse' """ context = dash.callback_context.triggered - if "AddComponentSynapse" in context[0]['prop_id']: + if context[0]['value'] == 0: + selectedNeuron = None # Add item if(selectedLayer != None and (selectedNeuron != None and len(selectedNeuron) != 0)): if len(selectedNeuron) > 0: @@ -78,7 +73,7 @@ class callbacks(callbacksOp): else: return [graphsArea, GlobalHeatmap] else: - data = None + data = pd.DataFrame({}) if selectedLayer != None: if(selectedLayer in super.globalHeatMap): data = super.globalHeatMap[selectedLayer] @@ -86,7 +81,7 @@ class callbacks(callbacksOp): data = self.getGlobalSynapseWeights(g,selectedLayer) super.globalHeatMap[selectedLayer] = data - return [graphsArea, self.AddGlobalHeatmap(g, selectedLayer, data, HeatMapX, HeatMapY) if not data.empty else []] + return [graphsArea, self.AddGlobalHeatmap(g, selectedLayer, data, heatmapEditInfo) if not data.empty else []] else: if(context[0]['prop_id'] != "."): # Delete item @@ -119,8 +114,8 @@ class callbacks(callbacksOp): if(len(graphsArea) == 0): graphsArea.append(self.addSpaceHolder(g,app)) return [graphsArea, GlobalHeatmap] - except Exception as e: - print("InsertRemoveNewElement: " + str(e)) + except Exception: + print("InsertRemoveNewElement: "+ traceback.format_exc()) try: # Callback to get neurons of seleced layer @@ -138,8 +133,8 @@ class callbacks(callbacksOp): return [[{'label': str(i), 'value': str(i)} for i in range(g.Layer_Neuron[selectedLayer])], ""] else: return [[], ""] - except Exception as e: - print("NeuronSelection: " + str(e)) + except Exception: + print("NeuronSelection: " + traceback.format_exc()) try: # Callback to handle clearing content when received a clear signal @@ -163,8 +158,8 @@ class callbacks(callbacksOp): super.clearData(existingIndexes) return [displayContent, clearGraphs] - except Exception as e: - print("clearProcess: "+str(e)) + except Exception: + print("clearProcess: "+ traceback.format_exc()) try: # Callback to handle spikes per interval graph and spike class representation @@ -173,9 +168,8 @@ class callbacks(callbacksOp): [Input("display-Synapse", "children")], [State("v-step", "children"), State("interval", "value"), State({"index": MATCH, "layer": MATCH, "neuron": MATCH, "type": "SynapseFreqGraph"}, "id"), State({"index": MATCH, "layer": MATCH, "neuron": MATCH, "type": 'Synapse-frequency-switch'}, 'on'), - State({"index": MATCH, "layer": MATCH, "neuron": MATCH, "type": 'Synapse-HeatMap-switch'}, 'on'), - State("HeatMapX", "value"), State("HeatMapY", "value")]) - def processSynapseData(displayContent, sliderValue, updateInterval, selectedItem, isOnSynapseFreqGraph, isOnHeatmap, heatMapX, heatMapY): + State({"index": MATCH, "layer": MATCH, "neuron": MATCH, "type": 'Synapse-HeatMap-switch'}, 'on'),State("heatmapEditInfo", "data")]) + def processSynapseData(displayContent, sliderValue, updateInterval, selectedItem, isOnSynapseFreqGraph, isOnHeatmap, heatmapEditInfo): """ Create two graphs using neuron synapses ('SynapseFreqGraph' and 'Heatmap') Args: @@ -185,11 +179,10 @@ class callbacks(callbacksOp): selectedItem (list): selected neuron isOnSynapseFreqGraph (bool): whether SynapseFreq graph is active or not isOnHeatmap (bool): whether Heatmap graph is active or not - heatMapX (int): X dimension for heatmaps - heatMapY (int): Y dimension for heatmaps + heatmapEditInfo (Array): heatmap edit stats Raises: - PreventUpdate: in case we don't want to update the content we rise this execption + no_update: in case we don't want to update the content we rise this execption Returns: synapse frequency graph and heatmap content (data and layout) @@ -210,11 +203,11 @@ class callbacks(callbacksOp): output = [self.synapseFreqDrawGraph(g, selectedItem["index"], data, super.xAxisSynapseFreqGraph[selectedItem["index"]], super.xAxisLabel[selectedItem["index"]], super.yAxisSynapseFreqGraph, super.HeatMapSynapseFreqGraph, isOnSynapseFreqGraph), - self.heatMap(g, selectedItem["index"], data, super.heatmaps, isOnHeatmap, heatMapX, heatMapY)] + self.heatMap(g, selectedItem["index"], data, super.heatmaps, isOnHeatmap, heatmapEditInfo)] return output else: - raise PreventUpdate + return no_update else: if(sliderValue == 0): data = self.getSynapseWeights(int(sliderValue)*float(updateInterval), g, selectedItem) @@ -226,11 +219,11 @@ class callbacks(callbacksOp): output = [self.synapseFreqDrawGraph(g, selectedItem["index"], data, super.xAxisSynapseFreqGraph[selectedItem["index"]], super.xAxisLabel[selectedItem["index"]], super.yAxisSynapseFreqGraph, super.HeatMapSynapseFreqGraph, isOnSynapseFreqGraph), - self.heatMap(g, selectedItem["index"], data, super.heatmaps, isOnHeatmap, heatMapX, heatMapY)] + self.heatMap(g, selectedItem["index"], data, super.heatmaps, isOnHeatmap, heatmapEditInfo)] return output else: - raise PreventUpdate + return no_update else: # after adding to the screen if(selectedItem["index"] not in super.xAxisLabel): @@ -246,13 +239,13 @@ class callbacks(callbacksOp): output = [self.synapseFreqDrawGraph(g, selectedItem["index"], data, super.xAxisSynapseFreqGraph[selectedItem["index"]], super.xAxisLabel[selectedItem["index"]], super.yAxisSynapseFreqGraph, super.HeatMapSynapseFreqGraph, isOnSynapseFreqGraph), - self.heatMap(g, selectedItem["index"], data, super.heatmaps, isOnHeatmap, heatMapX, heatMapY)] + self.heatMap(g, selectedItem["index"], data, super.heatmaps, isOnHeatmap, heatmapEditInfo)] return output else: - raise PreventUpdate - except Exception as e: - print("processSynapseData: " + str(e)) + return no_update + except Exception: + print("processSynapseData: "+ traceback.format_exc()) try: @@ -273,9 +266,37 @@ class callbacks(callbacksOp): return [not isTabOpen] else: return [isTabOpen] - except Exception as e: - print("GlobalHeatMapTab: " + str(e)) + except Exception: + print("GlobalHeatMapTab: "+ traceback.format_exc()) + + try: + # Callback to handle heatmap flipping if needed + @app.callback([Output("heatmapEditInfo", "data"),Output("AddComponentSynapse", "n_clicks")],[Input('FlipHeatmapH', 'n_clicks'),Input('FlipHeatmapV', 'n_clicks'),Input('RotateHeatmap', 'n_clicks'),State("heatmapEditInfo", "data")]) + def HeatmapFlip(FlipHeatmapH, FlipHeatmapV, RotateHeatmap, heatmapEditInfo): + """ Function called when flip button clicked + + Args: + FlipHeatmapH (int): number of clicks on the FlipHeatmapH button + FlipHeatmapV (int): number of clicks on the FlipHeatmapV button + RotateHeatmap (int): number of clicks on the RotateHeatmap button + heatmapEditInfo (Array): heatmap edit stats + Returns: + if global heatmaps tab should be opened or closed + """ + context = dash.callback_context.triggered + + if "FlipHeatmapH" in context[0]['prop_id']: + heatmapEditInfo[0] = not heatmapEditInfo[0] + elif "FlipHeatmapV" in context[0]['prop_id']: + heatmapEditInfo[1] = not heatmapEditInfo[1] + else: + heatmapEditInfo[2] = not heatmapEditInfo[2] + + return [heatmapEditInfo,0] + except Exception: + print("HeatmapFlip: "+ traceback.format_exc()) + try: # ------------------------------------------------------------ @@ -302,7 +323,7 @@ class callbacks(callbacksOp): id={"index": str(index), "layer": layer, "neuron": neuron, "type": "Synapse-frequency-switch"}, on='True', size=25, color="#28a745", style={"marginLeft": "10px"}), - html.Div("−", className="btn-danger btn-circle", style={"fontWeight": "500","fontSize":"16px", "marginLeft": "10px"}, id={"index": str(index), "type": "DeleteComponent"}), + html.Button(html.I(className="fa-solid fa-trash"), style={"fontWeight": "500","fontSize":"16px", "marginLeft": "10px","color":"red","backgroundColor":"#00110000","border":"0"}, id={"index": str(index), "type": "DeleteComponent"}), html.Div([html.Span(layer+" N: "+neuron, style={"fontSize": "13px", "paddingTop": "10px", "marginRight": "10px"}, className="badge alert-info")], className="d-flex", style={"direction": "rtl", "width": "100%"})], className="d-flex", style={"height": "35px"}), dcc.Graph(id={"index": str(index), "type": "SynapseFreqGraph", "layer": layer, "neuron": neuron}, style={ "width": "100%", "height": "290px"}, className="col-12", animate=False, config={"displaylogo": False}), @@ -318,7 +339,7 @@ class callbacks(callbacksOp): )], className="d-flex", style={"height": "35px", "direction": "rtl", "paddingRight": "30px"}), dcc.Graph(id={"index": str(index), "type": "Heatmap", "layer": layer, "neuron": neuron}, style={"width": "100%", "height": "290px"}, className="col-12", animate=False, config={"displaylogo": False}), ], className="col-lg-6 col-sm-12 col-xs-12") - ], className="row", style={"margin": "10px"}) + ], className="row", style={"padding": "10px"}) ], style={"background": "rgb(242, 248, 255)", "paddingBottom": "10px", "marginBottom": "10px", "margin": "5px", "borderRadius": "10px"}, @@ -340,15 +361,14 @@ class callbacks(callbacksOp): id="spaceHolder") - def AddGlobalHeatmap(self, g, selectedLayer, data, heatMapX, heatMapY): + def AddGlobalHeatmap(self, g, selectedLayer, data, heatmapEditInfo): """ Add global heatmap for selected layer. Args: g (Global_Var): reference to access global variables selectedLayer (String): layer name data (dataframe): synapses weights of all neurons in the layer - heatMapX (int): X dimension for heatmaps - heatMapY (int): Y dimension for heatmaps + heatmapEditInfo (Array): heatmap edit stats Returns: html component that contains the global heatmaps @@ -379,11 +399,14 @@ class callbacks(callbacksOp): zmin=0, zmax=1, z=g.createHeatMap( - heatMapX, heatMapY, data[data.To == index],depth), + heatMapX, heatMapY, data[data.To == index],depth, heatmapEditInfo[2]), colorscale='jet', name=str(index)), row=xx, col=yy) - fig.update_yaxes(autorange="reversed", row=xx, col=yy) + if heatmapEditInfo[0]: + fig.update_xaxes(autorange="reversed") + if heatmapEditInfo[1]: + fig.update_yaxes(autorange="reversed") fig.update_layout(height=80, width=80) index += 1 @@ -553,10 +576,10 @@ class callbacks(callbacksOp): plot_bgcolor= "rgba(255, 255, 255,0)", xaxis={'title': 'Step'} )} - except Exception as e: - print("synapseFreqDrawGraph: "+str(e)) + except Exception: + print("synapseFreqDrawGraph: "+ traceback.format_exc()) - def heatMap(self, g, index, data, heatmaps, isOn, heatMapX, heatMapY): + def heatMap(self, g, index, data, heatmaps, isOn, heatmapEditInfo): """ Create heatmap for selected neuron synapses. Args: @@ -565,51 +588,51 @@ class callbacks(callbacksOp): data (list): data to be added to the graph heatmaps (list): list of heatmaps created isOn (bool): whether this graph is active or not - heatMapX (int): X dimension for heatmaps - heatMapY (int): Y dimension for heatmaps + heatmapEditInfo (Array): heatmap edit stats Returns: heatmap content (data and layout) """ try: - heatMapWithIndexs = [] layout = go.Layout( margin={'l': 0, 'r': 0, 't': 0, 'b': 25}, uirevision='no reset of zoom', - yaxis={"autorange": "reversed"}, - xaxis={'showticklabels': False,"ticks":"inside"}, + xaxis={'showticklabels': False,"ticks":"inside","autorange": "reversed"} if heatmapEditInfo[0] else {'showticklabels': False,"ticks":"inside"}, + yaxis={"autorange": "reversed"} if heatmapEditInfo[1] else {}, width=400, height=300, paper_bgcolor= "rgba(255, 255, 255,0)", plot_bgcolor= "rgba(255, 255, 255,0)") if isOn: if not data.empty: + heatMapX = data["x"].max() + 1 + heatMapY = data["y"].max() + 1 + + # update heatmap in heatmaps + if (str(index) not in heatmaps) or (type(heatmaps[str(index)]) == type(None)): + heatmaps[str(index)] = [np.zeros((heatMapX,heatMapY)),heatMapX,heatMapY] + depth = data["C"].max() data = data.sort_values(["T"]) data = data.groupby(["C","x","y"], as_index=False).agg({"To":"last","T":"max","L":"last","V":"last"}) - # update heatmap in heatmaps - if str(index) not in heatmaps: - heatmaps[str(index)] = np.zeros( - (heatMapX, heatMapY)) for X,Y,V,C in zip(data["x"],data["y"],data["V"],data["C"]): try: # TODO: multi-D - heatmaps[str(index)][X][Y] = V + heatmaps[str(index)][0][X][Y] = V heatMapWithIndexs.append([X, Y, C, V]) - except Exception as e: - print("heatMap1: "+str(e)) - + except Exception: + print("heatMap1: "+ traceback.format_exc()) return {'data': [ go.Heatmap( - z=g.createHeatMap(heatMapX, heatMapY, pd.DataFrame(heatMapWithIndexs), depth), + z=g.createHeatMap(heatMapX, heatMapY, pd.DataFrame(heatMapWithIndexs), depth, heatmapEditInfo[2]), zmin=0, zmax=1, colorscale="jet")], 'layout': layout} else: if str(index) not in heatmaps: - heatmaps[str(index)] = np.empty((heatMapX, heatMapY)) + heatmaps[str(index)] = None return {'data': [ go.Heatmap( @@ -623,12 +646,12 @@ class callbacks(callbacksOp): go.Heatmap( zmin=0, zmax=1, - z=heatmaps[str(index)] if str( + z=heatmaps[str(index)][0] if str( index) in heatmaps else [], colorscale="jet")], 'layout': layout} - except Exception as e: - print("heatMap2: "+str(e)) + except Exception: + print("heatMap2: "+ traceback.format_exc()) # ------------------------------------------------------------ # MongoDB operations @@ -656,7 +679,7 @@ class callbacks(callbacksOp): {"L": {'$in': layer}}, {"To": {'$in': neuron}}, {"T": {'$gt': timestamp, '$lte': (timestamp+g.updateInterval)}} ]} - }]) + }], allowDiskUse = True) # ToDF---------------------- SynapseWeight = pd.DataFrame(list(SynapseWeight)) @@ -680,7 +703,7 @@ class callbacks(callbacksOp): # MongoDB--------------------- col = pymongo.collection.Collection(g.db, 'synapseWeightFinal') - globalSynapseWeights = col.aggregate([{"$match": { "_id.L": {"$eq": layer}}}]) + globalSynapseWeights = col.aggregate([{"$match": { "_id.L": {"$eq": layer}}}], allowDiskUse = True) # ToDF---------------------- globalSynapseWeights = pd.DataFrame(list(globalSynapseWeights)) @@ -695,5 +718,5 @@ class callbacks(callbacksOp): # ---------------------------- return globalSynapseWeights - except Exception as e: - print("Data process loading: "+str(e)) \ No newline at end of file + except Exception: + print("Data process loading: "+ traceback.format_exc()) \ No newline at end of file diff --git a/src/Modules/Synapse/layout.py b/src/Modules/Synapse/layout.py index 7d253987e418c3c90278cbb1a930d928e3384426..11863c87257158471128f4ffcff784bcc1c42409 100755 --- a/src/Modules/Synapse/layout.py +++ b/src/Modules/Synapse/layout.py @@ -3,6 +3,7 @@ from collections import deque from dash import dcc, html +import traceback import dash_bootstrap_components as dbc from .callbacks import callbacks from src.templates.layoutOp import layoutOp @@ -55,6 +56,7 @@ class layout(layoutOp): [ html.Div(id="synapse-vis", children=[ html.Div([ + dcc.Store(id="heatmapEditInfo",data=[False,False,False]), dcc.Dropdown( id='LayerFilterSynapse', options=[{'label': str(i), 'value': str(i)} for i in ( @@ -63,18 +65,16 @@ class layout(layoutOp): dcc.Dropdown( id='NeuronFilterSynapse', options=[], multi=False, style={'width': '150px', "marginLeft": "10px", "textAlign": "start"}), - dbc.Button("+", id="AddComponentSynapse", n_clicks=0, style={ + dbc.Button(html.I(className="fa-solid fa-plus"), id="AddComponentSynapse", n_clicks=0, style={ "fontWeight": "500", "marginLeft": "20px", "height": "36px", "backgroundColor":"rgb(68, 71, 99)","borderColor":"rgb(68, 71, 99)"}), - html.Div([ - dbc.Input(type="number",id="HeatMapX",value=self.g.NeuronsSize["x"]+1, min=0, step=1, - style={"width":"80px","marginLeft":"10px","textAlign":"center"}), - html.P("X",style={"padding":"5px"}), - dbc.Input(type="number",id="HeatMapY",value=self.g.NeuronsSize["y"]+1, min=0, step=1, - style={"width":"80px","textAlign":"center"})], className="d-flex",style={"marginLeft":"3px"}) - ,html.Div(id='clear-Synapse',children="False", style={'display': 'none'}) - ,html.Div(id='display-Synapse',children="False", style={'display': 'none'}) - ], className="d-flex"), + html.P("Heatmap Editor:", style={"textAlign": "start", "marginLeft": "20px", "marginTop": "4px"}), + dbc.Button(html.I(className="fa-solid fa-left-right"), id="FlipHeatmapH", n_clicks=0, style={"fontWeight": "500", "marginLeft": "20px", "height": "36px","backgroundColor":"rgb(68, 71, 99)","borderColor":"rgb(68, 71, 99)", "color":"white"}), + dbc.Button(html.I(className="fa-solid fa-up-down"), id="FlipHeatmapV", n_clicks=0, style={"fontWeight": "500", "marginLeft": "20px", "height": "36px","backgroundColor":"rgb(68, 71, 99)","borderColor":"rgb(68, 71, 99)", "color":"white"}), + dbc.Button(html.I(className="fa-solid fa-rotate"), id="RotateHeatmap", n_clicks=0, style={"fontWeight": "500", "marginLeft": "20px", "height": "36px","backgroundColor":"rgb(68, 71, 99)","borderColor":"rgb(68, 71, 99)", "color":"white"}), + html.Div(id='clear-Synapse',children="False", style={'display': 'none'}), + html.Div(id='display-Synapse',children="False", style={'display': 'none'}) + ], className="d-flex", style={"paddingBottom": "10px"}), dbc.Card( dbc.CardBody([dbc.Card([dbc.CardHeader( dbc.Button( "Layer HeatMap", color="none", id="group-GlobalHeatMapAreaSynapse-toggle", @@ -93,5 +93,5 @@ class layout(layoutOp): callbacks(self,self.app,self.g) # Return the Layer return layer - except Exception as e: - print("SynapseLayer: " + str(e)) \ No newline at end of file + except Exception: + print("SynapseLayer: " + traceback.format_exc()) \ No newline at end of file diff --git a/src/Modules/Synapse/spark.py b/src/Modules/Synapse/spark.py index aef9ded093a26020097bf2a774c2d856bac4a514..c6de5e96170568b22bf46cadfa44a67e6b640687 100755 --- a/src/Modules/Synapse/spark.py +++ b/src/Modules/Synapse/spark.py @@ -34,7 +34,7 @@ class spark(sparkOp): self.g.createSparkSession() # -------------------------------------------------- col = pymongo.collection.Collection(self.g.db, self.DOCUMENT_NAME) - globalSynapseWeights = col.aggregate([{ "$sort": { "T": 1 } },{"$group" : { "_id" : {"To":'$To', "C":'$C', "index":'$index', "L":'$L'}, "T" : { "$last": '$T'},"V" : { "$last": '$V'} } }]) + globalSynapseWeights = col.aggregate([{ "$sort": { "T": 1 } },{"$group" : { "_id" : {"To":'$To', "C":'$C', "index":'$index', "L":'$L'}, "T" : { "$last": '$T'},"V" : { "$last": '$V'} } }], allowDiskUse = True) # Data save into MongoDB --------------------------------- col = pymongo.collection.Collection(self.g.db, self.OUTPUT_DOCUMENT_NAME) @@ -59,12 +59,12 @@ class spark(sparkOp): # get dimensions for the heatmap col = pymongo.collection.Collection(self.g.db, 'synapseWeightFinal') - globalSynapseWeights = pd.DataFrame(list(col.aggregate([{"$group": {"_id":None, "x":{"$max":"$_id.index.x"}, "y":{"$max":"$_id.index.y"}}}]))) + globalSynapseWeights = pd.DataFrame(list(col.aggregate([{"$group": {"_id":None, "x":{"$max":"$_id.index.x"}, "y":{"$max":"$_id.index.y"}}}], allowDiskUse = True))) self.g.NeuronsSize = {"x":globalSynapseWeights["x"].max(),"y":globalSynapseWeights["y"].max()} - - print("done", self.MODULE_NAME) - except Exception as e: - print("Error:" + str(e)) - traceback.print_exc() + if self.g.config.DEBUG: + print("done", self.MODULE_NAME) + + except Exception: + print("Error:" + traceback.format_exc()) pass \ No newline at end of file diff --git a/src/static/css/app.css b/src/static/css/app.css index 84e0d9b84bc80642a94116909e30b03d3a63d0d0..ece06d339e9887aa08be7dabc4f33e38df434ba3 100755 --- a/src/static/css/app.css +++ b/src/static/css/app.css @@ -23,14 +23,14 @@ background-color: red !important; background-color: rgb(219, 232, 246) !important; } .row-sim{ - background-color: rgb(242, 248, 255); + border: 1px solid rgba(0,0,0,.125); margin: 30px; border-radius: 10px; text-align: center; padding: 50px; } .panel{ - background-color: rgb(242, 248, 255); + background-color: rgba(68, 71, 99, 0.2); margin: 5%; border-radius: 10px; text-align: center; @@ -41,27 +41,32 @@ background-color: red !important; background-color: #dc3545; color: white; font-weight: 500; - width: 100%; + width: 100px; + height: 100px; margin-bottom: 5px; + margin-right: 5px; border:none; } .btn-module:focus{ background-color: #d42b3c; color: white; - width: 100%; + width: 100px; + height: 100px; border:none; box-shadow: #d42b3c; } .btn-module:hover{ background-color: #d42b3c; color: white; - width: 100%; + width: 100px; + height: 100px; border:none; } .btn-module:active{ background-color: #d42b3c; color: white; - width: 100%; + width: 100px; + height: 100px; border:none; } .btn-group-vertical{ diff --git a/src/templates/index.html b/src/templates/index.html index 01ee2b45dd2801637b7def107f3e19fd1753a7d0..61fd8e2b66888a7b3d6d9d9c43588cda6981e354 100755 --- a/src/templates/index.html +++ b/src/templates/index.html @@ -33,17 +33,17 @@ <div class="col-10 m-5"> <div class="card"> <div class="card-body"> - VS2N version : <strong style="color: rgb(68, 71, 99);">{{ vs2n }}</strong><br><br> - MongoDB version : <strong style="color: rgb(68, 71, 99);">{{ mongodb }}</strong><br><br> - Apache Spark version : <strong style="color: rgb(68, 71, 99);">{{ spark }}</strong> + VS2N : <strong style="color: rgb(68, 71, 99);">{{ vs2n }}</strong><br><br> + MongoDB : <strong style="color: rgb(68, 71, 99);">{{ mongodb }}</strong><br><br> + Apache Spark : <strong style="color: rgb(68, 71, 99);">{{ spark }}</strong> </div> </div> </div> </div> -</div> + <div class="row d-flex justify-content-center"> <div class="col-md-10 col-lg-5 col-sm-10 row-sim"> - <h5 style="text-align: start;color: rgb(68, 71, 99);">Settings</h5> + <h5 style="text-align: start;">Settings</h5> <div class="input-group" style="padding-top: 18px;"> <div class="input-group-append" style="width: 180px;"> <span class="input-group-text" style="width: 180px;">Simulation</span> @@ -66,9 +66,8 @@ </div> <div class="col-md-10 col-lg-5 col-sm-10 row-sim"> - <h5 style="text-align: start;color: rgb(68, 71, 99);">Modules</h5> - <div class="col-12 btn-group-vertical" data-toggle="buttons" style="padding-left: 0px;padding-right: 0px;"> - + <h5 style="text-align: start;">Modules</h5> + <div class="col-12" data-toggle="buttons" style="padding-left: 0px;padding-right: 0px;"> {% for module in modules %} <button type="button" class="btn btn-primary btn-module" data-value="{{ module }}" @@ -80,23 +79,12 @@ </div> </div> </div> - <div class="loading" style="text-align: center;visibility: hidden;"> - <h5 style="padding-bottom: 5px;">Pre-processing, please wait ...</h5> - <div class="spinner-grow spinner-grow-sm" style="color: rgb(68, 71, 99)" role="status"></div> - <div class="spinner-grow spinner-grow-sm" style="color: rgb(68, 71, 99)" role="status"></div> - <div class="spinner-grow spinner-grow-sm" style="color: rgb(68, 71, 99)" role="status"></div> - <div class="spinner-grow spinner-grow-sm" style="color: rgb(68, 71, 99)" role="status"></div> - <div class="spinner-grow spinner-grow-sm" style="color: rgb(68, 71, 99)" role="status"></div> - <div class="spinner-grow spinner-grow-sm" style="color: rgb(68, 71, 99)" role="status"></div> - <div class="spinner-grow spinner-grow-sm" style="color: rgb(68, 71, 99)" role="status"></div> - <div class="spinner-grow spinner-grow-sm" style="color: rgb(68, 71, 99)" role="status"></div> - <div class="spinner-grow spinner-grow-sm" style="color: rgb(68, 71, 99)" role="status"></div> - <div class="spinner-grow spinner-grow-sm" style="color: rgb(68, 71, 99)" role="status"></div> - </div> <div style="width:100%;text-align:center;margin-bottom: 25px;margin-top: 10px;"> <a id="btn_start" class="btn btn-success" style="color: white;width:50%;font-weight:500;" href="#" role="button"> Start Analysis</a> </div> - </div> + <div class="loading" style="text-align: center;visibility: hidden;"> + <h5 style="padding-bottom: 8px;">Pre-processing, please wait ...</h5> + <div class="spinner-border" style="color: rgb(68, 71, 99)" role="status"></div> + </div> </body> - </html> \ No newline at end of file diff --git a/tests/Modules/test_General_callbacks.py b/tests/Modules/test_General_callbacks.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tests/Modules/test_General_layout.py b/tests/Modules/test_General_layout.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tests/Modules/test_General_spark.py b/tests/Modules/test_General_spark.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tests/Modules/test_Neuron_callbacks.py b/tests/Modules/test_Neuron_callbacks.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tests/Modules/test_Neuron_layout.py b/tests/Modules/test_Neuron_layout.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tests/Modules/test_Neuron_spark.py b/tests/Modules/test_Neuron_spark.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tests/Modules/test_Synapse_callbacks.py b/tests/Modules/test_Synapse_callbacks.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tests/Modules/test_Synapse_layout.py b/tests/Modules/test_Synapse_layout.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tests/Modules/test_Synapse_spark.py b/tests/Modules/test_Synapse_spark.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100755 index 0000000000000000000000000000000000000000..0519ecba6ea913e21689ec692e81e9e4973fbf73 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000000000000000000000000000000000000..0243da429ccbfaab1a617e71f8987c71ac9c5439 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,24 @@ +import pytest +from VS2N import flask_app + +@pytest.fixture +def flask_app(): + yield flask_app + +#@pytest.fixture +#def dashApp(): +# # Init dash app +# app_vis = dash.Dash( +# __name__, +# requests_pathname_prefix='/vis/', +# external_stylesheets=[VS2N.dbc.themes.BOOTSTRAP, VS2N.dbc.icons.FONT_AWESOME], +# suppress_callback_exceptions=True, +# title="VS2N") +# +# app_vis.enable_dev_tools(debug=VS2N.g.config.DEBUG) +# +# # Loading modules +# +# request = {'name': 'csnn-23-3-2023-16:32:48', 'modules': ['Neuron', 'Synapse'], 'updateInterval': '1'} +# +# yield app_vis \ No newline at end of file diff --git a/tests/test_Global_var.py b/tests/test_Global_var.py new file mode 100644 index 0000000000000000000000000000000000000000..214fecb347260be2a898a284c50e2b6095f7aa44 --- /dev/null +++ b/tests/test_Global_var.py @@ -0,0 +1,4 @@ +from flask import Flask + +def test_test(flask_app: Flask): + assert isinstance(flask_app,Flask) \ No newline at end of file