Commit 150f8fcb authored by Jan Pöppel's avatar Jan Pöppel
Browse files

added gui files

parent ccc6e866
graft primo_gui/primo_gui_react/build
\ No newline at end of file
# Experimental PRIMO Visualisation
This is a curated combination of different student projects aimed at implementing a visualization for Primo and graphical models in general. jpoeppel took features from different implementations and fixed some major bugs for this most stable version. Not all features there were implemented at some point are currently fully supported.
This implementation uses react.js frontend to render the graph in a webbrowser. Currently flask is used as a simple webserver that handles the communication between Python and Javascript.
## Installation
The easiest way to install the gui is by using the conda package available from the scs channel. Instructions for a local installation are below.
## Requirements:
### Python
The minimal Python requirements are listed in requirements.txt and can be easily installed using
pip install -r requirements.txt
### node js
The frontend in javascript has its own requirements which are defined in primo_gui_react/package.json .
These requirements can be installed using npm.
## Local installation
### Python
The Python backend can simply be installed used:
`python setup.py [install,develop]`
### Frontend
When checking out the project locally, you will need to build the frontend files. Use
`npm run build`
in the primo_gui_react folder for this. This will create a "build" folder with the html and js files which
the flask server will then serve.
## Start:
The visualisation can be run by using the script 'runPrimo_gui.py'. The basic usage is to specify a xbif or conf as an optional first argument. The gui will load the specified model and start the webserver. If no model was specified, it will start with an empty Bayesian Network.
## Additinal parameters:
* --port: Specifies the port to run at (Default: 8080)
* --debug: Starts the webserver in debug mode (Default: Disabled)
* --browser/--no-browser: Enables/disables starting a webbrowser directly. (Default: Enabled)
## Controls:
### Drawing a Graph
* Use the "Add Node" and "Add Edge" buttons in the sidebar to select the respective modes
* Nodes are added by clicking in the main graph view when "Add Node" is selected
* Edges are added by clicking and holding from the starting node and releasing on the target node when "Add Edge" is selected
* You can change a node's position when "Default" is selected, by clicking and dragging a node.
### Setting CPTs
* When adding new edges, a red exlamation mark will appear on the child node, indicating that its CPTs are not valid
* RIGHT-CLICK on the node when "Default" is selected to bring up the context menu
* Check "Show Info" to open the node's CPT, from which you an also change its name and the node's outcomes
* You can change any (changeable) value by DOUBLE-CLICKING on that value, e.g. the node's name.
* You need to confirm your changes for them to take effect.
### Delete Nodes / Edges
* Nodes or edges can be deleted by RIGHT-CLICKING the node/edge when "Default" is selected from the context menu.
### Moving and Zooming the Graph
* Moving:
* DRAG and DROP the Chart (DRAG a 'blank' zone not an element) when "Default" is selected
* Zooming in and out:
* Use the MOUSE-WHEEL for zooming and focusing
## Navigation Bar
### File
* Create a new Graph/load an existing graph (xbif format) or save your current graph.
### View
* Set viewing options (the nodes, their outcomes in the circles, their "legends" and info panels) for all nodes at once
### Inference Method
* Select an 'Inference Method':
* Go over 'Inference Method' to select a method
* A repeated CLICK on the (same) Method renews the calculation
* 'Inference Methods' in Discrete Baysian Network:
1. 'Variable Elimination'
2. 'Factor Tree'
3. 'Gibbs Sampling'
4. 'Metropolis Hasting'
* Info: No Method is choosen as default. Instead, an uniform distribution is used
which also may arise if an error is detected.
* Selecting sampling based methods (Gibbs Sampling and Metropolis Hastings) will open additional sampling options, where you can configure the number of samples to use and whether or not to replace all samples
### Visualisation
* The GUI can visualize Gibbs Sampling by showing the state of each drawn sample and if desired, the currently sampled node's Markov Blanket
* You can additionally (and in parallel to any inference method) visualize DSeparation.
## Running:
* When installed (e.g. using pip), the script runPrimo_gui.py will be added to the binary path, meaning that the gui can then be started by simply calling 'runPrimo_gui.py [Path_to_xbif]'
## Features "available" but not currently integrated in this version
- Visualizing Dynamic Bayesian Networks
- Showing Markov Equivalent Graphs for Bayesian Networks
- Performing value queries (most probable explanation)
These could be integrated with varying degrees of effort if desired.
Integrating DBNs may be the hardest as that implementation still used d3.js instead of react so fundamental changes
would be required to integrate it in this current version.
\ No newline at end of file
{% set data = load_setup_py_data(setup_file='../setup.py', from_recipe_dir=True) %}
package:
name: primo_gui
version: {{ data.get('version') }}
source:
path: ..
build:
noarch: python
number: 1
script: python setup.py install --single-version-externally-managed --record record.txt
requirements:
host:
- python
- setuptools
run:
- python
- primo >= 0.9.5
- flask
test:
imports:
- primo_gui
about:
home: https://gitlab.ub.uni-bielefeld.de/scs/primogui
summary: 'A visualisation for PRIMO that allows the creation, modification of and inference in Bayesian Networks.'
license: LGPLv3
from flask import Flask
app = Flask(__name__, static_url_path='',
static_folder='./primo_gui_react/build',
template_folder='./primo_gui_react/build')
from . import views
from primo2.inference.exact import FactorTree
from primo2.inference.exact import VariableElimination
import primo2.inference.mcmc as mcmc
from primo2.inference import dynamic
from primo2 import networks
import functools
import copy
class InferenceMethodsBN:
def __init__(self):
self._bn = networks.BayesianNetwork()
self._bn.name = 'bayesiannetwork'
self._evidence = {}
self._num_samples = 1000
self._full_change = False
self._current_inference_method = ""
self._inference = {
"Variable Elimination": None,
"Factor Tree": None,
"Gibbs Sampling": None,
"Metropolis Hastings": None,
"Visualise Samples": None,
}
self.__update()
@property
def current_inference_method(self):
return self._current_inference_method
@current_inference_method.setter
def current_inference_method(self, value):
self._current_inference_method = value
self.__update()
def __update(self):
if self.current_inference_method == "Variable Elimination":
self._inference.update({"Variable Elimination": functools.partial(VariableElimination.bucket_marginals,
self._bn, evidence=self._evidence)})
elif self.current_inference_method == "Factor Tree":
self._tree = FactorTree.create_jointree(self._bn)
self._tree.set_evidence(self._evidence)
self._inference.update({"Factor Tree": self._tree.marginals})
elif self.current_inference_method == "Gibbs Sampling":
self._mcmcGibbs = mcmc.MCMC(self._bn, transitionModel=mcmc.GibbsTransition(), numSamples=self._num_samples, fullChange=self._full_change)
initial_state = self._bn.get_sample(self._evidence)
self._sampleChain = list(self._mcmcGibbs.sampler.generate_markov_chain(self._bn, self._num_samples, initial_state, self._evidence))
self._inference.update({"Gibbs Sampling": functools.partial(self._mcmcGibbs.marginals, evidence=self._evidence, sampleChain=self._sampleChain)})
elif self.current_inference_method == "Metropolis Hastings":
self._mcmcMetropolis = mcmc.MCMC(self._bn, transitionModel=mcmc.MetropolisHastingsTransition(), numSamples=self._num_samples, fullChange=self._full_change)
initial_state = self._bn.get_sample(self._evidence)
self._sampleChain = list(self._mcmcMetropolis.sampler.generate_markov_chain(self._bn, self._num_samples, initial_state, self._evidence))
self._inference.update({"Metropolis Hastings": functools.partial(self._mcmcMetropolis.marginals, evidence=self._evidence, sampleChain=self._sampleChain)})
elif self.current_inference_method == "Visualise Samples":
self._mcmcGibbs = mcmc.MCMC(self._bn, transitionModel=mcmc.GibbsTransition(), numSamples=self._num_samples, fullChange=False)
initial_state = self._bn.get_sample(self._evidence)
self._sampledVars = []
self._sampleChain = list(self._mcmcGibbs.sampler.generate_markov_chain(self._bn, self._num_samples, initial_state, self._evidence, stepByStep=True, sampledVars=self._sampledVars))
self._inference.update({"Visualise Samples": functools.partial(self._mcmcGibbs.marginals, stepByStep=True, evidence=self._evidence, sampleChain=self._sampleChain, sampledVars=self._sampledVars)})
elif self.current_inference_method != "":
print("Unknown inference method: {}".format(self.current_inference_method))
def get_bn(self):
return self._bn
def set_bn(self, bn):
self._bn = copy.deepcopy(bn)
self._bn.name = 'bayesiannetwork'
self.__update()
def get_evidence(self):
return self._evidence
def set_evidence(self, evidence):
self._evidence = evidence
self.__update()
def set_num_samples(self, num_samples):
self._num_samples = num_samples
self.__update()
def set_full_change(self, full_change):
self._full_change = full_change
self.__update()
def get_inference(self):
return self._inference
def get_inference_types(self):
return list(self._inference.keys())
def infer(self, inference_method, nodes):
print("Infer method: ", inference_method)
return self._inference[inference_method](nodes)
class InferenceMethodsDBN:
def __init__(self):
self._dbn = networks.DynamicBayesianNetwork()
self._dbn.b0.name = 'dynamicbayesiannetwork_b0'
self._dbn.two_tbn.name = 'dynamicbayesiannetwork_2tbn'
self._evidence = {}
self.__update()
def __update(self):
self._pfb = dynamic.PriorFeedbackExact(self._dbn)
self._pfb.set_evidence(evidence=self._evidence)
self._se = dynamic.SoftEvidenceExact(self._dbn)
self._se.set_evidence(evidence=self._evidence)
self._inference = {
'Prior Feedback': self._pfb.marginals,
'Soft Evidence': self._se.marginals}
def get_dbn(self):
return self._dbn
def set_dbn(self, dbn):
self._dbn = copy.deepcopy(dbn)
self._dbn.b0.name = 'dynamicbayesiannetwork_b0'
self._dbn.two_tbn.name = 'dynamicbayesiannetwork_2tbn'
self.__update()
def get_b0(self):
return self._dbn.b0
def set_b0(self, b0):
self._dbn.b0 = copy.deepcopy(b0)
self._dbn.b0.name = 'dynamicbayesiannetwork_b0'
self.__update()
def get_tbn(self):
return self._dbn.two_tbn
def set_tbn(self, tbn):
self._dbn.two_tbn = copy.deepcopy(tbn)
self._dbn.two_tbn.name = 'dynamicbayesiannetwork_2tbn'
self.__update()
def get_evidence(self):
return self._evidence
def set_evidence(self, evidence):
self._evidence = evidence
self._pfb.set_evidence(evidence=self._evidence)
self._se.set_evidence(evidence=self._evidence)
def get_inference(self):
return self._inference
def get_inference_types(self):
return list(self._inference.keys())
def infer(self, inference_method, node):
return self._inference[inference_method]([node])
def get_t(self):
return self._pfb._t
def unroll(self):
self._pfb.unroll(evidence=self._evidence)
self._se.unroll(evidence=self._evidence)
def reset(self):
self._evidence = {}
self.__update()
import primo_gui.inferenceMethods as iM
class GuiGraph:
def __init__(self, graph_mode=None):
if graph_mode is None:
graph_mode = ["BN", ""]
self._width = 0
self._height = 0
self._node_radius = 50
self._graph = {}
self._dir = ""
self._nr = 0
# self._current_inference_method = ""
self._current_layout = ""
self._is_first_position_update = True
self._graph_mode = graph_mode
if self._graph_mode[0] == "BN":
self._inference_methods = iM.InferenceMethodsBN()
elif self._graph_mode[0] == "DBN":
self._inference_methods = iM.InferenceMethodsDBN()
@property
def width(self):
return self._width
@width.setter
def width(self, value):
self._width = value
@property
def height(self):
return self._height
@height.setter
def height(self, value):
self._height = value
@property
def graph(self):
return self._graph.copy()
@graph.setter
def graph(self, value):
self._graph = value.copy()
@property
def nr(self):
return self._nr
@nr.setter
def nr(self, value):
self._nr = value
def get_next_nr(self):
self._nr += 1
return self._nr
@property
def current_inference_method(self):
return self.inference_methods.current_inference_method
# @current_inference_method.setter
# def current_inference_method(self, value):
# self._current_inference_method = value
@property
def current_layout(self):
return self._current_layout
@current_layout.setter
def current_layout(self, value):
self._current_layout = value
@property
def inference_methods(self):
return self._inference_methods
@inference_methods.setter
def inference_methods(self, value):
self._inference_methods = value
@property
def is_first_position_update(self):
return self._is_first_position_update
@is_first_position_update.setter
def is_first_position_update(self, value):
self._is_first_position_update = value
@property
def node_radius(self):
return self._node_radius
@node_radius.setter
def node_radius(self, value):
self._node_radius = value
@property
def dir(self):
return self._dir
@dir.setter
def dir(self, value):
self._dir = value
@property
def graph_mode(self):
return self._graph_mode
@graph_mode.setter
def graph_mode(self, value):
self._graph_mode = value
def new_graph(self, graph_mode=None):
if graph_mode is None:
graph_mode = ["BN", ""]
self._graph_mode = graph_mode
self._graph = {}
self._nr = 0
# self._current_inference_method = ""
self._current_layout = ""
self._is_first_position_update = True
if self._graph_mode[0] == "BN":
self._inference_methods = iM.InferenceMethodsBN()
elif self._graph_mode[0] == "DBN":
self._inference_methods = iM.InferenceMethodsDBN()
self._graph = {}
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
## Available Scripts
In the project directory, you can run:
### `npm start`
Runs the app in the development mode.<br />
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
The page will reload if you make edits.<br />
You will also see any lint errors in the console.
### `npm test`
Launches the test runner in the interactive watch mode.<br />
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
### `npm run build`
Builds the app for production to the `build` folder.<br />
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.<br />
Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
### `npm run eject`
**Note: this is a one-way operation. Once you `eject`, you can’t go back!**
If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
## Learn More
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
To learn React, check out the [React documentation](https://reactjs.org/).
### Code Splitting
This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting
### Analyzing the Bundle Size
This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size
### Making a Progressive Web App
This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app
### Advanced Configuration
This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration
### Deployment
This section has moved here: https://facebook.github.io/create-react-app/docs/deployment
### `npm run build` fails to minify
This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify
{
"name": "primo_gui_react",
"homepage": ".",
"version": "0.1.0",
"private": true,
"dependencies": {
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.5.0",
"@testing-library/user-event": "^7.2.1",
"file-saver": "^2.0.2",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-scripts": "3.4.1"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"proxy": "http://localhost:5000"
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"