Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,15 @@ FROM frodriguez4600/jupyter-neuron:v7.8.0
ARG INSTALLATION_FOLDER=/home/jovyan/work/NetPyNE-UI
ARG NETPYNE_VERSION=development
ARG WORKSPACE_VERSION=nov2020
ARG JUPYTER_GEPPETTO_VERSION=development
ARG PYGEPPETTO_VERSION=development
ARG GEPPETTO_VERSION=development
ARG BUILD_ARGS=""

USER $NB_USER

ENV INSTALLATION_FOLDER=$INSTALLATION_FOLDER
ENV NETPYNE_VERSION=$NETPYNE_VERSION
ENV WORKSPACE_VERSION=$WORKSPACE_VERSION
ENV JUPYTER_GEPPETTO_VERSION=$JUPYTER_GEPPETTO_VERSION
ENV PYGEPPETTO_VERSION=$PYGEPPETTO_VERSION
ENV GEPPETTO_VERSION=$GEPPETTO_VERSION
ENV BUILD_ARGS=$BUILD_ARGS

# Install openmpi for parallel simulations
Expand All @@ -30,10 +28,12 @@ RUN pip install -r requirements.txt
COPY --chown=1000:1000 . .
WORKDIR ${INSTALLATION_FOLDER}/utilities

RUN python install.py ${BUILD_ARGS}
RUN npm install --global yarn
RUN npm install --global yalc
RUN python install.py ${BUILD_ARGS} --geppetto ${GEPPETTO_VERSION}

WORKDIR ${INSTALLATION_FOLDER}

RUN pip install -r requirements-test.txt
RUN pytest tests/backend
# RUN pytest tests/backend
CMD /bin/bash -c "jupyter notebook --NotebookApp.default_url=/geppetto --NotebookApp.token='' --library=netpyne_ui --NotebookApp.disable_check_xsrf=True"
5 changes: 4 additions & 1 deletion utilities/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,10 @@ def main(netpyne_branch, workspace_branch, geppetto_branch=None, skipNpm=False,
if development:
# install geppetto meta
if os.path.exists(os.path.join(WEBAPP_DIR, '.yalc')):
execute(cmd=['ln', '-s', os.path.expanduser('~') + '/.yalc', '.yalc'], cwd=WEBAPP_DIR)
execute(cmd=['rm', '-rf', '.yalc'], cwd=WEBAPP_DIR)
execute(cmd=['ln', '-s', os.path.expanduser('~') + '/.yalc', '.yalc'], cwd=WEBAPP_DIR)
else:
execute(cmd=['ln', '-s', os.path.expanduser('~') + '/.yalc', '.yalc'], cwd=WEBAPP_DIR)
execute(cmd=['ls'], cwd=WEBAPP_DIR)
execute(cmd=['bash', 'geppetto_ui.sh'], cwd=WEBAPP_DIR)
execute(cmd=['yarn'], cwd=WEBAPP_DIR)
Expand Down
2 changes: 1 addition & 1 deletion webapp/Utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ const Utils = {
}

// skip the list element, e.g. "E"!
console.debug(`Skip ${item} at ${nextObject.label}`);
// console.debug(`Skip ${item} at ${nextObject.label}`);
skipped = true;
} else {
skipped = false;
Expand Down
7 changes: 6 additions & 1 deletion webapp/components/NetPyNE.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const styles = ({ zIndex }) => ({
},
topbar: {
position: 'relative',
zIndex: zIndex.drawer + 1,
zIndex: zIndex.drawer,
},
content: {
flexGrow: 1,
Expand All @@ -46,6 +46,11 @@ const TIMEOUT = 10000;
const EXPERIMENT_POLL_INTERVAL = 1000;

class NetPyNE extends React.Component {
constructor (props) {
super(props);
this.openPythonCallDialog = this.openPythonCallDialog.bind(this);
}

componentDidMount () {
GEPPETTO.on(GEPPETTO.Events.Error_while_exec_python_command, this.openPythonCallDialog, this);

Expand Down
30 changes: 16 additions & 14 deletions webapp/components/experiments/ExperimentEdit.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import ParameterMenu from './ParameterMenu';
import useStyles from './ExperimentEditStyle';
import * as ExperimentHelper from './ExperimentHelper';
import DialogBox from '../general/DialogBox';
import workerCode from './workers/processExperimentData';

const RANGE_VALUE = 0;
const SUPPORTED_TYPES = [REAL_TYPE.INT, REAL_TYPE.FLOAT, REAL_TYPE.STR, REAL_TYPE.BOOL];
const MAX_TRIALS = 100;
Expand Down Expand Up @@ -191,13 +193,13 @@ const ExperimentEdit = (props) => {
const [experimentNameError, setExperimentNameError] = useState('');
const [selectionParams, setSelectionParams] = useState([]);
const [trialNumberErrorDialogOpen, setTrialNumberErrorDialogOpen] = useState({ condition: false, number: 1 });
const [paramsCounter, setParamsCounter] = useState(0);

// Existing Experiment.
const [experiment, setExperiment] = useState(null);
const experiments = useSelector((state) => state.experiments.experiments);

let numberOfTrials = 1;
// const dispatch = useDispatch();
const validateParameter = (param) => {
let updatedParam = param;
if (param.type === LIST) {
Expand Down Expand Up @@ -253,20 +255,20 @@ const ExperimentEdit = (props) => {
const getParameters = () => {
ExperimentsApi.getParameters()
.then((params) => {
const flattened = Utils.flatten(params);
const paramKeys = Object.keys(flattened);

const filteredKeys = paramKeys.filter((key) => {
// TODO: avoid to fetch field twice!
const field = Utils.getMetadataField(`netParams.${key}`);
if (field && SUPPORTED_TYPES.includes(field.type)) {
return true;
// eslint-disable-next-line prefer-template
// eslint-disable-next-line no-undef
const worker = new Worker(workerCode);
worker.onmessage = function (e) {
switch (e.data.resultMessage) {
case 'OK':
setSelectionParams(e.data.params.results);
worker.terminate();
break;
default:
console.error('worker processing metadata for autocomplete not working.');
}
return false;
});

console.debug(`Size before ${paramKeys.length}, after: ${filteredKeys.length}`);
setSelectionParams(filteredKeys);
};
worker.postMessage({ message: 'process', params: { data: params, metadata: window.metadata } });
});
};

Expand Down
105 changes: 105 additions & 0 deletions webapp/components/experiments/workers/processExperimentData.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
const workerCode = () => {
// eslint-disable-next-line no-undef
self.onmessage = function (event) {
// The object that the web page sent is stored in the event.data property.
const Utils = {
getMetadataField (key, field = null) {
if (key === undefined) {
return null;
}

let currentObject;
let nextObject = event.data.params.metadata;
let skipped = false;

// eslint-disable-next-line no-restricted-syntax
key.split('.').forEach((item) => {
if (
currentObject != null
&& currentObject?.container === true
&& !(item in nextObject)
) {
if (skipped) {
return null;
}

// skip the list element, e.g. "E"!
// console.debug(`Skip ${item} at ${nextObject.label}`);
skipped = true;
} else {
skipped = false;

if (item in nextObject) {
currentObject = nextObject[item];
if ('children' in currentObject) {
nextObject = currentObject.children;
}
} else {
currentObject = null;
}
}
});

if (currentObject) {
return field ? currentObject[field] : currentObject;
}

return null;
},

flatten (obj, path = '') {
if (!(obj instanceof Object)) {
// eslint-disable-next-line no-new-object
const newObj = new Object();
newObj[path.replace(/\.$/g, '')] = obj;
return newObj;
}

return Object.keys(obj).reduce(
(output, key) => (obj instanceof Array
? Object.assign(output, Utils.flatten(obj[key], `${path}[${key}].`))
: Object.assign(output, Utils.flatten(obj[key], `${path + key}.`))),
{},
);
},
};

const REAL_TYPE = {
INT: 'int',
FLOAT: 'float',
BOOL: 'bool',
STR: 'str',
FUNC: 'func',
DICT: 'dict',
DICT_DICT: 'dict(dict)',
};
const SUPPORTED_TYPES = [
REAL_TYPE.INT,
REAL_TYPE.FLOAT,
REAL_TYPE.STR,
REAL_TYPE.BOOL,
];
const { data } = event.data.params;
const flattened = Utils.flatten(data);
const paramKeys = Object.keys(flattened);
const filteredKeys = paramKeys.filter((key) => {
// TODO: avoid to fetch field twice!
const field = Utils.getMetadataField(`netParams.${key}`);
if (field && SUPPORTED_TYPES.includes(field.type)) {
return true;
}
return false;
});

// eslint-disable-next-line no-undef
postMessage({ resultMessage: 'OK', params: { results: filteredKeys } });
};
};

let code = workerCode.toString();
code = code.substring(code.indexOf('{') + 1, code.lastIndexOf('}'));

const blob = new Blob([code], { type: 'application/javascript' });
const workerScript = window.URL.createObjectURL(blob);

export default workerScript;
50 changes: 32 additions & 18 deletions webapp/components/general/ControlPanelTreeItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import Shuffle from '@material-ui/icons/Shuffle';
import { ChromePicker } from 'react-color';
import { useDispatch, useSelector } from 'react-redux';
import { experimentLabelColor } from '../../theme';
import { changeInstanceColor } from '../../redux/actions/general';
import { changeInstanceColor, selectInstances } from '../../redux/actions/general';

const useStyles = makeStyles((theme) => ({
networkItem: {
Expand Down Expand Up @@ -62,26 +62,39 @@ const ControlPanelTreeItem = (props) => {
setColor(_color.rgb);
};

const getRandomColor = () => ({
r: parseFloat((Math.random() * 255).toFixed(2)),
g: parseFloat((Math.random() * 255).toFixed(2)),
b: parseFloat((Math.random() * 255).toFixed(2)),
a: 1,
});

const generateRandomColor = (event, nodeId) => {
const newInstances = instances.filter((instance) => !(instance.instancePath.startsWith(nodeId)));
const randomColor = {
r: parseFloat((Math.random() * 255).toFixed(2)),
g: parseFloat((Math.random() * 255).toFixed(2)),
b: parseFloat((Math.random() * 255).toFixed(2)),
a: 1,
};
const children = window.Instances.getInstance(nodeId).getChildren().map((instance) => instance.getInstancePath());
// const newInstances = instances.filter((instance) => !(instance.instancePath.startsWith(nodeId)));
const newInstances = instances.filter((instance) => {
let condition = true;
children.forEach((child) => {
if (instance.instancePath.startsWith(child)) {
condition = false;
}
});
return condition;
});

newInstances.push({
instancePath: nodeId,
color: {
r: randomColor.r / 255,
g: randomColor.g / 255,
b: randomColor.b / 255,
a: randomColor.a,
},
children.forEach((child) => {
const randomColor = getRandomColor();
newInstances.push({
instancePath: child,
color: {
r: randomColor.r / 255,
g: randomColor.g / 255,
b: randomColor.b / 255,
a: randomColor.a,
},
});
});
dispatch(changeInstanceColor(newInstances));
setColor(randomColor);
};

const changeVisibility = (event, nodeId) => {
Expand Down Expand Up @@ -126,6 +139,7 @@ const ControlPanelTreeItem = (props) => {
onNodeSelect,
onVisibilityClick,
children,
disableRandom,
...other
} = props;

Expand Down Expand Up @@ -153,7 +167,7 @@ const ControlPanelTreeItem = (props) => {
<IconButton onClick={(event) => changeVisibility(event, nodeId)}>
{ visibility ? <Visibility /> : <VisibilityOff /> }
</IconButton>
<IconButton onClick={(event) => generateRandomColor(event, nodeId)}><Shuffle /></IconButton>
<IconButton disabled={disableRandom} onClick={(event) => generateRandomColor(event, nodeId)}><Shuffle /></IconButton>
<IconButton onClick={() => setShowColorPicker(true)}><ColorLens /></IconButton>
{
showColorPicker
Expand Down
21 changes: 10 additions & 11 deletions webapp/components/general/ExperimentControlPanel.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
/* eslint-disable no-nested-ternary */
import * as React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { makeStyles } from '@material-ui/core/styles';
import Box from '@material-ui/core/Box';
import Typography from '@material-ui/core/Typography';
import TextField from '@material-ui/core/TextField';
import TreeView from '@material-ui/lab/TreeView';
import TreeItem from '@material-ui/lab/TreeItem';
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import ChevronRightIcon from '@material-ui/icons/ChevronRight';
import ControlPanelTreeItem from './ControlPanelTreeItem';
import { experimentLabelColor } from '../../theme';

import { MODEL_STATE } from '../../constants';
import { selectInstances } from '../../redux/actions/general';

const useStyles = makeStyles(() => ({
header: {
Expand All @@ -24,9 +23,11 @@ const useStyles = makeStyles(() => ({

const ExperimentControlPanel = (props) => {
const classes = useStyles();
const dispatch = useDispatch();
const instances = useSelector((state) => state.general.instances);
const [filter, setFilter] = React.useState('');
const onNodeSelect = (nodeId) => {
console.log(`Node with id ${nodeId} clicked`);
dispatch(selectInstances(instances, [nodeId]));
};
const instancesMap = new Map();

Expand Down Expand Up @@ -71,7 +72,7 @@ const ExperimentControlPanel = (props) => {
};

const getTreeItemsFromData = (treeItems) => treeItems.map((treeItemData) => {
let children;
let children = [];
if (treeItemData.getChildren() && treeItemData.getChildren().length > 0) {
children = getTreeItemsFromData(treeItemData.getChildren());
}
Expand All @@ -84,6 +85,7 @@ const ExperimentControlPanel = (props) => {
type={treeItemData.getType().getId()}
onNodeSelect={onNodeSelect}
onVisibilityClick={onVisibilityClick}
disableRandom={children.length === 0}
>
{children}
</ControlPanelTreeItem>
Expand All @@ -110,12 +112,9 @@ const ExperimentControlPanel = (props) => {
defaultCollapseIcon={<ExpandMoreIcon />}
defaultExpandIcon={<ChevronRightIcon />}
>
<TreeItem nodeId="network" label="network_netpyne">
{filter === ''
? getTreeItemsFromData(window.Instances.network.getChildren())
: getFlatFilteredList(window.Instances.network.getChildren())
}
</TreeItem>
{filter === ''
? getTreeItemsFromData([window.Instances.getInstance('network')])
: getFlatFilteredList([window.Instances.getInstance('network')])}
</TreeView>
</Box>
)
Expand Down
Loading