diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 7dc973ba..7b2f9532 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -12,9 +12,9 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest]
- python-version: ["3.8", "3.9", "3.10", "3.11"]
+ python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
include:
- - os: macos-latest
+ - os: macos-12
python-version: "3.10"
steps:
diff --git a/.install_neuron.sh b/.install_neuron.sh
deleted file mode 100755
index c1756556..00000000
--- a/.install_neuron.sh
+++ /dev/null
@@ -1,37 +0,0 @@
-#!/bin/sh
-
-set -e
-
-SRC_DIR=$1
-INSTALL_DIR=$2
-PYTHON_BIN=$3
-
-if [ ! -e ${INSTALL_DIR}/.install_finished ]
-then
- echo 'Neuron was not fully installed in previous build, installing ...'
- mkdir -p ${SRC_DIR}
- cd ${SRC_DIR}
- echo "Downloading NEURON ..."
- rm -rf nrn
- git clone --depth 1 https://github.com/neuronsimulator/nrn.git >download.log 2>&1
- cd nrn
- echo "Preparing NEURON ..."
- ./build.sh >prepare.log 2>&1
- echo "Configuring NEURON ..."
- PYTHON_BLD=${PYTHON_BIN} ./configure --prefix=${INSTALL_DIR} --without-x --with-nrnpython=${PYTHON_BIN} --disable-rx3d >configure.log 2>&1
- echo "Building NEURON ..."
- make -j4 >make.log 2>&1
- echo "Installing NEURON ..."
- make -j4 install >install.log 2>&1
-
- export PATH="${INSTALL_DIR}/x86_64/bin":${PATH}
- export PYTHONPATH="${INSTALL_DIR}/lib/python":${PYTHONPATH}
-
- echo "Testing NEURON import ...."
- ${PYTHON_BIN} -c 'import neuron' >testimport.log 2>&1
-
- touch -f ${INSTALL_DIR}/.install_finished
- echo "NEURON successfully installed"
-else
- echo 'Neuron was successfully installed in previous build, not rebuilding'
-fi
diff --git a/.jenkins.sh b/.jenkins.sh
deleted file mode 100755
index 3cb8c4b0..00000000
--- a/.jenkins.sh
+++ /dev/null
@@ -1,39 +0,0 @@
-#!/bin/bash
-
-set -e
-set -x
-
-tox_args='--recreate -e py3-unit-functional-style'
-
-if [ "${os}" = "cscsviz" ]
-then
- . /opt/rh/python27/enable
-elif [ "${os}" = "Ubuntu-18.04" ]
-then
- tox_args="${tox_args}"
-fi
-
-which python
-python --version
-
-cd $WORKSPACE
-
-#########
-# Virtualenv
-#########
-
-if [ ! -d "${WORKSPACE}/env" ]; then
- virtualenv ${WORKSPACE}/env --no-site-packages
-fi
-
-. ${WORKSPACE}/env/bin/activate
-pip install pip --upgrade
-pip install tox --upgrade
-
-#####
-# Tests
-#####
-
-cd ${WORKSPACE}/BluePyOpt
-
-tox ${tox_args}
diff --git a/.readthedocs.yaml b/.readthedocs.yaml
index 77ea544e..551d5134 100644
--- a/.readthedocs.yaml
+++ b/.readthedocs.yaml
@@ -14,3 +14,8 @@ python:
- method: pip
path: .
- requirements: requirements_docs.txt
+
+build:
+ os: ubuntu-22.04
+ tools:
+ python: "3.10"
diff --git a/.zenodo.json b/.zenodo.json
new file mode 100644
index 00000000..8768b81a
--- /dev/null
+++ b/.zenodo.json
@@ -0,0 +1,48 @@
+{
+ "title" : "BluePyOpt",
+ "license": "LGPL-3.0",
+ "upload_type": "software",
+ "description": "The Blue Brain Python Optimisation Library (BluePyOpt) is an extensible framework for data-driven model parameter optimisation that wraps and standardises several existing open-source tools. It simplifies the task of creating and sharing these optimisations, and the associated techniques and knowledge. This is achieved by abstracting the optimisation and evaluation tasks into various reusable and flexible discrete elements according to established best-practices. Further, BluePyOpt provides methods for setting up both small- and large-scale optimisations on a variety of platforms, ranging from laptops to Linux clusters and cloud-based compute infrastructures.",
+ "creators": [
+ {
+ "affiliation": "Blue Brain Project, EPFL",
+ "name": "Van Geit, Werner",
+ "orcid": "0000-0002-2915-720X"
+ },
+ {
+ "affiliation": "Blue Brain Project, EPFL",
+ "name": "Gevaert, Michael",
+ "orcid": "0000-0002-7547-3297"
+ },
+ {
+ "affiliation": "Blue Brain Project, EPFL",
+ "name": "Damart, Tanguy",
+ "orcid": "0000-0003-2175-7304"
+ },
+ {
+ "affiliation": "Blue Brain Project, EPFL",
+ "name": "Rössert, Christian",
+ "orcid": "0000-0002-4839-2424"
+ },
+ {
+ "affiliation": "Blue Brain Project, EPFL",
+ "name": "Courcol, Jean-Denis",
+ "orcid": "0000-0002-9351-1461"
+ },
+ {
+ "affiliation": "Blue Brain Project, EPFL",
+ "name": "Chindemi, Guiseppe",
+ "orcid": "0000-0001-6872-2366"
+ },
+ {
+ "affiliation": "Blue Brain Project, EPFL",
+ "name": "Jaquier, Aurélien",
+ "orcid": "0000-0001-6202-6175"
+ },
+ {
+ "affiliation": "Blue Brain Project, EPFL",
+ "name": "Muller, Eilif",
+ "orcid": "0000-0003-4309-8266"
+ }
+ ]
+}
diff --git a/README.rst b/README.rst
index f4cb40a8..8c124b2f 100644
--- a/README.rst
+++ b/README.rst
@@ -3,59 +3,22 @@
BluePyOpt
=========
-.. raw:: html
-
-
-
- Latest Release |
-
-
-
-
- |
-
-
- Documentation |
-
-
-
-
- |
-
-
- License |
-
-
-
-
- |
-
-
- Build Status |
-
-
-
-
- |
-
-
- Coverage |
-
-
-
-
- |
-
-
- Gitter |
-
-
-
- |
-
-
++----------------+------------+
+| Latest Release | |pypi| |
++----------------+------------+
+| Documentation | |docs| |
++----------------+------------+
+| License | |license| |
++----------------+------------+
+| Build Status | |build| |
++----------------+------------+
+| Coverage | |coverage| |
++----------------+------------+
+| Gitter | |gitter| |
++----------------+------------+
+| Zenodo | |zenodo| |
++----------------+------------+
Introduction
@@ -229,6 +192,33 @@ Copyright (c) 2016-2022 Blue Brain Project/EPFL
The index.rst location is used for the docs README; index.rst also defined an end-marker,
to skip content after the marker 'substitutions'.
+.. |pypi| image:: https://img.shields.io/pypi/v/bluepyopt.svg
+ :target: https://pypi.org/project/bluepyopt/
+ :alt: latest release
+
+.. |docs| image:: https://readthedocs.org/projects/bluepyopt/badge/?version=latest
+ :target: https://bluepyopt.readthedocs.io/
+ :alt: latest documentation
+
+.. |license| image:: https://img.shields.io/pypi/l/bluepyopt.svg
+ :target: https://github.com/BlueBrain/bluepyopt/blob/master/LICENSE.txt
+ :alt: license
+
+.. |build| image:: https://github.com/BlueBrain/BluePyOpt/workflows/Build/badge.svg?branch=master
+ :target: https://github.com/BlueBrain/BluePyOpt/actions
+ :alt: actions build status
+
+.. |coverage| image:: https://codecov.io/github/BlueBrain/BluePyOpt/coverage.svg?branch=master
+ :target: https://codecov.io/gh/BlueBrain/bluepyopt
+ :alt: coverage
+
+.. |gitter| image:: https://badges.gitter.im/Join%20Chat.svg
+ :target: https://gitter.im/BlueBrain/blueptopt
+ :alt: Join the chat at https://gitter.im/BlueBrain/BluePyOpt
+
+.. |zenodo| image:: https://zenodo.org/badge/DOI/10.5281/zenodo.8135890.svg
+ :target: https://doi.org/10.5281/zenodo.8135890
+
.. substitutions
.. |banner| image:: docs/source/logo/BluePyOptBanner.png
.. |landscape_example| image:: examples/simplecell/figures/landscape_example.png
diff --git a/bluepyopt/deapext/tools/selIBEA.py b/bluepyopt/deapext/tools/selIBEA.py
index 95c86fbc..84dda0ef 100644
--- a/bluepyopt/deapext/tools/selIBEA.py
+++ b/bluepyopt/deapext/tools/selIBEA.py
@@ -1,9 +1,5 @@
"""IBEA selector"""
-from __future__ import division
-
-from past.builtins import xrange # pylint: disable=W0622
-
"""
Copyright (c) 2016-2022, EPFL/Blue Brain Project
@@ -77,7 +73,7 @@ def _calc_fitness_components(population, kappa):
box_ranges[box_ranges == 0] = 1.0
components_matrix = numpy.zeros((pop_len, pop_len))
- for i in xrange(0, pop_len):
+ for i in range(0, pop_len):
diff = population_matrix - population_matrix[i, :]
components_matrix[i, :] = numpy.max(
numpy.divide(diff, box_ranges),
@@ -115,9 +111,9 @@ def _mating_selection(population, mu, tournament_n):
"""Returns the n_of_parents individuals with the best fitness"""
parents = []
- for _ in xrange(mu):
+ for _ in range(mu):
winner = _choice(population)
- for _ in xrange(tournament_n - 1):
+ for _ in range(tournament_n - 1):
individual = _choice(population)
# Save winner is element with smallest fitness
if individual.ibea_fitness < winner.ibea_fitness:
diff --git a/bluepyopt/ephys/create_acc.py b/bluepyopt/ephys/create_acc.py
index 4d176014..b0974d1e 100644
--- a/bluepyopt/ephys/create_acc.py
+++ b/bluepyopt/ephys/create_acc.py
@@ -1,4 +1,4 @@
-'''create JSON/ACC files for Arbor from a set of BluePyOpt.ephys parameters'''
+"""create JSON/ACC files for Arbor from a set of BluePyOpt.ephys parameters"""
# pylint: disable=R0914
@@ -14,14 +14,18 @@
from bluepyopt.ephys.acc import arbor
from bluepyopt.ephys.morphologies import ArbFileMorphology
-from bluepyopt.ephys.create_hoc import \
- Location, RangeExpr, PointExpr, \
- _get_template_params, format_float
+from bluepyopt.ephys.create_hoc import (
+ Location,
+ RangeExpr,
+ PointExpr,
+ _get_template_params,
+ format_float,
+)
logger = logging.getLogger(__name__)
# Inhomogeneous expression for scaled parameter in Arbor
-RangeIExpr = namedtuple('RangeIExpr', 'name, value, scale')
+RangeIExpr = namedtuple("RangeIExpr", "name, value, scale")
class ArbVar:
@@ -39,25 +43,32 @@ def __init__(self, name, conv=None):
self.conv = conv
def __repr__(self):
- return 'ArbVar(%s, %s)' % (self.name, self.conv)
+ return "ArbVar(%s, %s)" % (self.name, self.conv)
class Nrn2ArbParamAdapter:
"""Converts a Neuron parameter to Arbor format (name and value)"""
_mapping = dict(
- v_init=ArbVar(name='membrane-potential'),
- celsius=ArbVar(name='temperature-kelvin',
- conv=lambda celsius: celsius + 273.15),
- Ra=ArbVar(name='axial-resistivity'),
- cm=ArbVar(name='membrane-capacitance',
- conv=lambda cm: cm / 100.), # NEURON: uF/cm^2, Arbor: F/m^2
- **{species + loc[0]:
- ArbVar(name='ion-%sternal-concentration \"%s\"' % (loc, species))
- for species in ['na', 'k', 'ca'] for loc in ['in', 'ex']},
- **{'e' + species:
- ArbVar(name='ion-reversal-potential \"%s\"' % species)
- for species in ['na', 'k', 'ca']}
+ v_init=ArbVar(name="membrane-potential"),
+ celsius=ArbVar(
+ name="temperature-kelvin", conv=lambda celsius: celsius + 273.15
+ ),
+ Ra=ArbVar(name="axial-resistivity"),
+ cm=ArbVar(
+ name="membrane-capacitance", conv=lambda cm: cm / 100.0
+ ), # NEURON: uF/cm^2, Arbor: F/m^2
+ **{
+ species + loc[0]: ArbVar(
+ name='ion-%sternal-concentration "%s"' % (loc, species)
+ )
+ for species in ["na", "k", "ca"]
+ for loc in ["in", "ex"]
+ },
+ **{
+ "e" + species: ArbVar(name='ion-reversal-potential "%s"' % species)
+ for species in ["na", "k", "ca"]
+ },
)
@classmethod
@@ -77,13 +88,19 @@ def _param_value(cls, param):
param (): A Neuron parameter with a value in Neuron units
"""
- if param.name in cls._mapping and \
- cls._mapping[param.name].conv is not None:
+ if (
+ param.name in cls._mapping
+ and cls._mapping[param.name].conv is not None
+ ):
return format_float(
- cls._mapping[param.name].conv(float(param.value)))
+ cls._mapping[param.name].conv(float(param.value))
+ )
else:
- return param.value if isinstance(param.value, str) else \
- format_float(param.value)
+ return (
+ param.value
+ if isinstance(param.value, str)
+ else format_float(param.value)
+ )
@classmethod
def _conv_param(cls, param, name):
@@ -95,20 +112,26 @@ def _conv_param(cls, param, name):
"""
if isinstance(param, Location):
- return Location(name=cls._param_name(name),
- value=cls._param_value(param))
+ return Location(
+ name=cls._param_name(name), value=cls._param_value(param)
+ )
elif isinstance(param, RangeExpr):
- return RangeExpr(location=param.location,
- name=cls._param_name(name),
- value=cls._param_value(param),
- value_scaler=param.value_scaler)
+ return RangeExpr(
+ location=param.location,
+ name=cls._param_name(name),
+ value=cls._param_value(param),
+ value_scaler=param.value_scaler,
+ )
elif isinstance(param, PointExpr):
- return PointExpr(name=cls._param_name(name),
- point_loc=param.point_loc,
- value=cls._param_value(param))
+ return PointExpr(
+ name=cls._param_name(name),
+ point_loc=param.point_loc,
+ value=cls._param_value(param),
+ )
else:
raise CreateAccException(
- 'Unsupported parameter expression type %s.' % type(param))
+ "Unsupported parameter expression type %s." % type(param)
+ )
@classmethod
def format(cls, param, mechs):
@@ -123,12 +146,16 @@ def format(cls, param, mechs):
parameter in Arbor format
"""
if not isinstance(param, PointExpr):
- mech_matches = [i for i, mech in enumerate(mechs)
- if param.name.endswith("_" + mech)]
+ mech_matches = [
+ i
+ for i, mech in enumerate(mechs)
+ if param.name.endswith("_" + mech)
+ ]
else:
param_pprocesses = [loc.pprocess_mech for loc in param.point_loc]
- mech_matches = [i for i, mech in enumerate(mechs)
- if mech in param_pprocesses]
+ mech_matches = [
+ i for i, mech in enumerate(mechs) if mech in param_pprocesses
+ ]
if len(mech_matches) == 0:
return None, cls._conv_param(param, name=param.name)
@@ -136,15 +163,17 @@ def format(cls, param, mechs):
elif len(mech_matches) == 1:
mech = mechs[mech_matches[0]]
if not isinstance(param, PointExpr):
- name = param.name[:-(len(mech) + 1)]
+ name = param.name[: -(len(mech) + 1)]
else:
name = param.name
return mech, cls._conv_param(param, name=name)
else:
- raise CreateAccException("Parameter name %s matches" % param.name +
- " multiple mechanisms %s" %
- [repr(mechs[i]) for i in mech_matches])
+ raise CreateAccException(
+ "Parameter name %s matches" % param.name
+ + " multiple mechanisms %s"
+ % [repr(mechs[i]) for i in mech_matches]
+ )
class Nrn2ArbMechGrouper:
@@ -159,14 +188,21 @@ def _is_global_property(loc, param):
param (): A parameter in Arbor format (name and units)
"""
- return loc == ArbFileMorphology.region_labels['all'] and (
- param.name in ['membrane-potential',
- 'temperature-kelvin',
- 'axial-resistivity',
- 'membrane-capacitance'] or
- param.name.split(' ')[0] in ['ion-internal-concentration',
- 'ion-external-concentration',
- 'ion-reversal-potential'])
+ return loc == ArbFileMorphology.region_labels["all"] and (
+ param.name
+ in [
+ "membrane-potential",
+ "temperature-kelvin",
+ "axial-resistivity",
+ "membrane-capacitance",
+ ]
+ or param.name.split(" ")[0]
+ in [
+ "ion-internal-concentration",
+ "ion-external-concentration",
+ "ion-reversal-potential",
+ ]
+ )
@classmethod
def _separate_global_properties(cls, loc, mechs):
@@ -187,7 +223,6 @@ def _separate_global_properties(cls, loc, mechs):
global_properties = []
for mech, params in mechs.items():
-
if mech is None:
local_properties = []
for param in params:
@@ -213,8 +248,9 @@ def _format_params_and_group_by_mech(params, channels):
Mapping of Arbor mechanism name to list of parameters in Arbor
format
"""
- mech_params = [Nrn2ArbParamAdapter.format(
- param, channels) for param in params]
+ mech_params = [
+ Nrn2ArbParamAdapter.format(param, channels) for param in params
+ ]
mechs = {mech: [] for mech, _ in mech_params}
for mech in channels:
if mech not in mechs:
@@ -236,9 +272,11 @@ def process_global(cls, params):
(mechanism name is None for non-mechanism parameters).
"""
return cls._format_params_and_group_by_mech(
- [Location(name=name, value=value)
- for name, value in params.items()],
- [] # no default mechanisms
+ [
+ Location(name=name, value=value)
+ for name, value in params.items()
+ ],
+ [], # no default mechanisms
)
@classmethod
@@ -261,16 +299,19 @@ def process_local(cls, params, channels):
global_properties = dict()
for loc, loc_params in params:
mechs = cls._format_params_and_group_by_mech(
- loc_params, channels[loc])
+ loc_params, channels[loc]
+ )
# move Arbor global properties to global_params
mechs, global_props = cls._separate_global_properties(loc, mechs)
if global_props.keys() != {None}:
raise CreateAccException(
- 'Support for Arbor default mechanisms not implemented.')
+ "Support for Arbor default mechanisms not implemented."
+ )
# iterate over global_props items if above exception triggers
- global_properties[None] = \
+ global_properties[None] = (
global_properties.get(None, []) + global_props[None]
+ )
local_mechs[loc] = mechs
return local_mechs, global_properties
@@ -289,8 +330,11 @@ def _arb_filter_point_proc_locs(pprocess_mechs):
for mech, point_exprs in mechs.items():
result[loc][mech.name] = dict(
mech=mech.suffix,
- params=[Location(point_expr.name, point_expr.value)
- for point_expr in point_exprs])
+ params=[
+ Location(point_expr.name, point_expr.value)
+ for point_expr in point_exprs
+ ],
+ )
return result
@@ -300,18 +344,21 @@ def _arb_append_scaled_mechs(mechs, scaled_mechs):
for mech, scaled_params in scaled_mechs.items():
if mech is None and len(scaled_params) > 0:
raise CreateAccException(
- 'Non-mechanism parameters cannot have inhomogeneous'
- ' expressions in Arbor %s' % scaled_params)
- mechs[mech] = mechs.get(mech, []) + \
- [RangeIExpr(
+ "Non-mechanism parameters cannot have inhomogeneous"
+ " expressions in Arbor %s" % scaled_params
+ )
+ mechs[mech] = mechs.get(mech, []) + [
+ RangeIExpr(
name=p.name,
value=p.value,
- scale=p.value_scaler.acc_scale_iexpr(p.value))
- for p in scaled_params]
+ scale=p.value_scaler.acc_scale_iexpr(p.value),
+ )
+ for p in scaled_params
+ ]
# An mechanism's NMODL GLOBAL and RANGE variables in Arbor
-MechMetaData = namedtuple('MechMetaData', 'globals, ranges')
+MechMetaData = namedtuple("MechMetaData", "globals, ranges")
class ArbNmodlMechFormatter:
@@ -338,37 +385,42 @@ def _load_catalogue_meta(cat_dir):
"""
# used to generate arbor_mechanisms.json on NMODL from arbor/mechanisms
- nmodl_pattern = '^\s*%s\s+((?:\w+\,\s*)*?\w+)\s*?$' # NOQA
- suffix_pattern = nmodl_pattern % 'SUFFIX'
- globals_pattern = nmodl_pattern % 'GLOBAL'
- ranges_pattern = nmodl_pattern % 'RANGE'
+ nmodl_pattern = r"^\s*%s\s+((?:\w+\,\s*)*?\w+)\s*?$" # NOQA
+ suffix_pattern = nmodl_pattern % "SUFFIX"
+ globals_pattern = nmodl_pattern % "GLOBAL"
+ ranges_pattern = nmodl_pattern % "RANGE"
def process_nmodl(nmodl_str):
"""Extract global and range params from Arbor-conforming NMODL"""
try:
- nrn = re.search(r'NEURON\s+{([^}]+)}', nmodl_str,
- flags=re.MULTILINE).group(1)
- suffix_ = re.search(suffix_pattern, nrn,
- flags=re.MULTILINE)
+ nrn = re.search(
+ r"NEURON\s+{([^}]+)}", nmodl_str, flags=re.MULTILINE
+ ).group(1)
+ suffix_ = re.search(suffix_pattern, nrn, flags=re.MULTILINE)
suffix_ = suffix_ if suffix_ is None else suffix_.group(1)
- globals_ = re.search(globals_pattern, nrn,
- flags=re.MULTILINE)
- globals_ = globals_ if globals_ is None \
- else re.findall(r'\w+', globals_.group(1))
- ranges_ = re.search(ranges_pattern, nrn,
- flags=re.MULTILINE)
- ranges_ = ranges_ if ranges_ is None \
- else re.findall(r'\w+', ranges_.group(1))
+ globals_ = re.search(globals_pattern, nrn, flags=re.MULTILINE)
+ globals_ = (
+ globals_
+ if globals_ is None
+ else re.findall(r"\w+", globals_.group(1))
+ )
+ ranges_ = re.search(ranges_pattern, nrn, flags=re.MULTILINE)
+ ranges_ = (
+ ranges_
+ if ranges_ is None
+ else re.findall(r"\w+", ranges_.group(1))
+ )
except Exception as e:
raise CreateAccException(
- 'NMODL-inspection for %s failed.' % nmodl_file) from e
+ "NMODL-inspection for %s failed." % nmodl_file
+ ) from e
# skipping suffix_
return MechMetaData(globals=globals_, ranges=ranges_)
mechs = dict()
cat_dir = pathlib.Path(cat_dir)
- for nmodl_file in cat_dir.glob('*.mod'):
+ for nmodl_file in cat_dir.glob("*.mod"):
with open(cat_dir.joinpath(nmodl_file)) as f:
mechs[nmodl_file.stem] = process_nmodl(f.read())
@@ -393,18 +445,23 @@ def _load_mech_catalogue_meta(cls, ext_catalogues):
if ext_catalogues is not None:
for cat, cat_nmodl in ext_catalogues.items():
arb_cats[cat] = cls._load_catalogue_meta(
- pathlib.Path(cat_nmodl).resolve())
+ pathlib.Path(cat_nmodl).resolve()
+ )
- builtin_catalogues = pathlib.Path(__file__).parent.joinpath(
- 'static/arbor_mechanisms.json').resolve()
+ builtin_catalogues = (
+ pathlib.Path(__file__)
+ .parent.joinpath("static/arbor_mechanisms.json")
+ .resolve()
+ )
with open(builtin_catalogues) as f:
builtin_arb_cats = json.load(f)
- for cat in ['BBP', 'default', 'allen']:
+ for cat in ["BBP", "default", "allen"]:
if cat not in arb_cats:
arb_cats[cat] = {
mech: MechMetaData(**meta)
- for mech, meta in builtin_arb_cats[cat].items()}
+ for mech, meta in builtin_arb_cats[cat].items()
+ }
return arb_cats
@@ -415,7 +472,7 @@ def _mech_name(name):
Args:
name (): A Neuron mechanism name
"""
- if name in ['Exp2Syn', 'ExpSyn']:
+ if name in ["Exp2Syn", "ExpSyn"]:
return name.lower()
else:
return name
@@ -441,51 +498,64 @@ def _translate_mech(cls, mech_name, mech_params, arb_cats):
for cat in arb_cats: # in order of precedence
if arb_mech_name in arb_cats[cat]:
arb_mech = arb_cats[cat][arb_mech_name]
- mech_name = cat + '::' + arb_mech_name
+ mech_name = cat + "::" + arb_mech_name
break
if arb_mech is None: # not Arbor built-in mech, no qualifier added
if mech_name is not None:
logger.warn(
- 'create_acc: Could not find Arbor mech for %s (%s).'
- % (mech_name, mech_params))
+ "create_acc: Could not find Arbor mech for %s (%s)."
+ % (mech_name, mech_params)
+ )
return (mech_name, mech_params)
else:
if arb_mech.globals is None: # only Arbor range params
for param in mech_params:
if param.name not in arb_mech.ranges:
raise CreateAccException(
- '%s not a GLOBAL or RANGE parameter of %s' %
- (param.name, mech_name))
+ "%s not a GLOBAL or RANGE parameter of %s"
+ % (param.name, mech_name)
+ )
return (mech_name, mech_params)
else:
for param in mech_params:
- if param.name not in arb_mech.globals and \
- param.name not in arb_mech.ranges:
+ if (
+ param.name not in arb_mech.globals
+ and param.name not in arb_mech.ranges
+ ):
raise CreateAccException(
- '%s not a GLOBAL or RANGE parameter of %s' %
- (param.name, mech_name))
+ "%s not a GLOBAL or RANGE parameter of %s"
+ % (param.name, mech_name)
+ )
mech_name_suffix = []
remaining_mech_params = []
for mech_param in mech_params:
if mech_param.name in arb_mech.globals:
- mech_name_suffix.append(mech_param.name + '=' +
- mech_param.value)
+ mech_name_suffix.append(
+ mech_param.name + "=" + mech_param.value
+ )
if isinstance(mech_param, RangeIExpr):
remaining_mech_params.append(
- RangeIExpr(name=mech_param.name,
- value=None,
- scale=mech_param.scale))
+ RangeIExpr(
+ name=mech_param.name,
+ value=None,
+ scale=mech_param.scale,
+ )
+ )
else:
remaining_mech_params.append(mech_param)
if len(mech_name_suffix) > 0:
- mech_name += '/' + ','.join(mech_name_suffix)
+ mech_name += "/" + ",".join(mech_name_suffix)
return (mech_name, remaining_mech_params)
def translate_density(self, mechs):
"""Translate all density mechanisms in a specific region"""
- return dict([self._translate_mech(mech, params, self.cats)
- for mech, params in mechs.items()])
+ return dict(
+ [
+ self._translate_mech(mech, params, self.cats)
+ for mech, params in mechs.items()
+ ]
+ )
def translate_points(self, mechs):
"""Translate all point mechanisms for a specific locset"""
@@ -493,9 +563,9 @@ def translate_points(self, mechs):
for synapse_name, mech_desc in mechs.items():
mech, params = self._translate_mech(
- mech_desc['mech'], mech_desc['params'], self.cats)
- result[synapse_name] = dict(mech=mech,
- params=params)
+ mech_desc["mech"], mech_desc["params"], self.cats
+ )
+ result[synapse_name] = dict(mech=mech, params=params)
return result
@@ -527,13 +597,16 @@ def _arb_populate_label_dict(local_mechs, local_scaled_mechs, pprocess_mechs):
acc_labels = ChainMap(local_mechs, local_scaled_mechs, pprocess_mechs)
for acc_label in acc_labels:
- if acc_label.name in label_dict and \
- acc_label != label_dict[acc_label.name]:
+ if (
+ acc_label.name in label_dict
+ and acc_label != label_dict[acc_label.name]
+ ):
raise CreateAccException(
- 'Label %s already exists in' % acc_label.name +
- ' label_dict with different s-expression: '
- ' %s != %s.' % (label_dict[acc_label.name].loc,
- acc_label.loc))
+ "Label %s already exists in"
+ % acc_label.name
+ + " label_dict with different s-expression: "
+ " %s != %s." % (label_dict[acc_label.name].loc, acc_label.loc)
+ )
elif acc_label.name not in label_dict:
label_dict[acc_label.name] = acc_label
@@ -542,10 +615,11 @@ def _arb_populate_label_dict(local_mechs, local_scaled_mechs, pprocess_mechs):
def _read_templates(template_dir, template_filename):
"""Expand Jinja2 template filepath with glob and
- return dict of target filename -> parsed template"""
+ return dict of target filename -> parsed template"""
if template_dir is None:
- template_dir = \
- pathlib.Path(__file__).parent.joinpath('templates').resolve()
+ template_dir = (
+ pathlib.Path(__file__).parent.joinpath("templates").resolve()
+ )
template_paths = pathlib.Path(template_dir).glob(template_filename)
@@ -554,17 +628,18 @@ def _read_templates(template_dir, template_filename):
with open(template_path) as template_file:
template = template_file.read()
name = template_path.name
- if name.endswith('.jinja2'):
+ if name.endswith(".jinja2"):
name = name[:-7]
- if name.endswith('_template'):
+ if name.endswith("_template"):
name = name[:-9]
- if '_' in name:
- name = '.'.join(name.rsplit('_', 1))
+ if "_" in name:
+ name = ".".join(name.rsplit("_", 1))
templates[name] = jinja2.Template(template)
if templates == {}:
raise FileNotFoundError(
- f'No templates found for JSON/ACC-export in {template_dir}')
+ f"No templates found for JSON/ACC-export in {template_dir}"
+ )
return templates
@@ -574,20 +649,22 @@ def _arb_loc_desc(location, param_or_mech):
return location.acc_label()
-def create_acc(mechs,
- parameters,
- morphology=None,
- morphology_dir=None,
- ext_catalogues=None,
- ignored_globals=(),
- replace_axon=None,
- create_mod_morph=False,
- template_name='CCell',
- template_filename='acc/*_template.jinja2',
- disable_banner=None,
- template_dir=None,
- custom_jinja_params=None):
- '''return a dict with strings containing the rendered JSON/ACC templates
+def create_acc(
+ mechs,
+ parameters,
+ morphology=None,
+ morphology_dir=None,
+ ext_catalogues=None,
+ ignored_globals=(),
+ replace_axon=None,
+ create_mod_morph=False,
+ template_name="CCell",
+ template_filename="acc/*_template.jinja2",
+ disable_banner=None,
+ template_dir=None,
+ custom_jinja_params=None,
+):
+ """return a dict with strings containing the rendered JSON/ACC templates
Args:
mechs (): All the mechs for the decor template
@@ -604,39 +681,45 @@ def create_acc(mechs,
template_dir (str): dir name of the jinja2 templates
custom_jinja_params (dict): dict of additional jinja2 params in case
of a custom template
- '''
+ """
if custom_jinja_params is None:
custom_jinja_params = {}
- if pathlib.Path(morphology).suffix.lower() not in ['.swc', '.asc']:
- raise CreateAccException("Morphology file %s not supported in Arbor "
- " (only supported types are .swc and .asc)."
- % morphology)
+ if pathlib.Path(morphology).suffix.lower() not in [".swc", ".asc"]:
+ raise CreateAccException(
+ "Morphology file %s not supported in Arbor "
+ " (only supported types are .swc and .asc)." % morphology
+ )
if replace_axon is not None:
- if not hasattr(arbor.segment_tree, 'tag_roots'):
- raise NotImplementedError("Need a newer version of Arbor"
- " for axon replacement.")
- logger.debug("Obtain axon replacement by applying "
- "ArbFileMorphology.replace_axon after loading "
- "morphology in Arbor.")
- replace_axon_path = \
- pathlib.Path(morphology).stem + '_axon_replacement.acc'
+ if not hasattr(arbor.segment_tree, "tag_roots"):
+ raise NotImplementedError(
+ "Need a newer version of Arbor" " for axon replacement."
+ )
+ logger.debug(
+ "Obtain axon replacement by applying "
+ "ArbFileMorphology.replace_axon after loading "
+ "morphology in Arbor."
+ )
+ replace_axon_path = (
+ pathlib.Path(morphology).stem + "_axon_replacement.acc"
+ )
replace_axon_acc = io.StringIO()
arbor.write_component(replace_axon, replace_axon_acc)
replace_axon_acc.seek(0)
if create_mod_morph:
- modified_morphology_path = \
- pathlib.Path(morphology).stem + '_modified.acc'
+ modified_morphology_path = (
+ pathlib.Path(morphology).stem + "_modified.acc"
+ )
modified_morpho = ArbFileMorphology.load(
pathlib.Path(morphology_dir).joinpath(morphology),
- replace_axon_acc)
+ replace_axon_acc,
+ )
replace_axon_acc.seek(0)
modified_morphology_acc = io.StringIO()
- arbor.write_component(
- modified_morpho, modified_morphology_acc)
+ arbor.write_component(modified_morpho, modified_morphology_acc)
modified_morphology_acc.seek(0)
modified_morphology_acc = modified_morphology_acc.read()
else:
@@ -652,47 +735,49 @@ def create_acc(mechs,
default_location_order = list(ArbFileMorphology.region_labels.values())
- template_params = _get_template_params(mechs,
- parameters,
- ignored_globals,
- disable_banner,
- default_location_order,
- _arb_loc_desc)
+ template_params = _get_template_params(
+ mechs,
+ parameters,
+ ignored_globals,
+ disable_banner,
+ default_location_order,
+ _arb_loc_desc,
+ )
filenames = {
- name: template_name + (name if name.startswith('.') else "_" + name)
- for name in templates.keys()}
+ name: template_name + (name if name.startswith(".") else "_" + name)
+ for name in templates.keys()
+ }
# postprocess template parameters for Arbor
- channels = template_params['channels']
- point_channels = template_params['point_channels']
- banner = template_params['banner']
+ channels = template_params["channels"]
+ point_channels = template_params["point_channels"]
+ banner = template_params["banner"]
# global_mechs refer to default density mechs/params in Arbor
# [mech -> param] (params under mech == None)
- global_mechs = \
- Nrn2ArbMechGrouper.process_global(
- template_params['global_params'])
+ global_mechs = Nrn2ArbMechGrouper.process_global(
+ template_params["global_params"]
+ )
# local_mechs refer to locally painted density mechs/params in Arbor
# [label -> mech -> param.name/.value] (params under mech == None)
- local_mechs, additional_global_mechs = \
- Nrn2ArbMechGrouper.process_local(
- template_params['section_params'], channels)
+ local_mechs, additional_global_mechs = Nrn2ArbMechGrouper.process_local(
+ template_params["section_params"], channels
+ )
for mech, params in additional_global_mechs.items():
- global_mechs[mech] = \
- global_mechs.get(mech, []) + params
+ global_mechs[mech] = global_mechs.get(mech, []) + params
# scaled_mechs refer to iexpr params of scaled density mechs in Arbor
# [label -> mech -> param.location/.name/.value/.value_scaler]
range_params = {loc: [] for loc in default_location_order}
- for param in template_params['range_params']:
+ for param in template_params["range_params"]:
range_params[param.location].append(param)
range_params = list(range_params.items())
- local_scaled_mechs, global_scaled_mechs = \
- Nrn2ArbMechGrouper.process_local(
- range_params, channels)
+ local_scaled_mechs, global_scaled_mechs = Nrn2ArbMechGrouper.process_local(
+ range_params, channels
+ )
# join each mech's constant params with inhomogeneous ones on mechanisms
_arb_append_scaled_mechs(global_mechs, global_scaled_mechs)
@@ -701,12 +786,13 @@ def create_acc(mechs,
# pprocess_mechs refer to locally placed mechs/params in Arbor
# [label -> mech -> param.name/.value]
- pprocess_mechs, global_pprocess_mechs = \
- Nrn2ArbMechGrouper.process_local(
- template_params['pprocess_params'], point_channels)
+ pprocess_mechs, global_pprocess_mechs = Nrn2ArbMechGrouper.process_local(
+ template_params["pprocess_params"], point_channels
+ )
if any(len(params) > 0 for params in global_pprocess_mechs.values()):
- raise CreateAccException('Point process mechanisms cannot be'
- ' placed globally in Arbor.')
+ raise CreateAccException(
+ "Point process mechanisms cannot be" " placed globally in Arbor."
+ )
# Evaluate synapse locations
# (no new labels introduced, but locations explicitly defined)
@@ -720,36 +806,43 @@ def create_acc(mechs,
global_mechs = nmodl_formatter.translate_density(global_mechs)
local_mechs = {
loc: nmodl_formatter.translate_density(mechs)
- for loc, mechs in local_mechs.items()}
+ for loc, mechs in local_mechs.items()
+ }
pprocess_mechs = {
loc: nmodl_formatter.translate_points(mechs)
- for loc, mechs in pprocess_mechs.items()}
+ for loc, mechs in pprocess_mechs.items()
+ }
# get iexpr parameters of scaled density mechs
global_scaled_mechs = _arb_project_scaled_mechs(global_mechs)
- local_scaled_mechs = {loc: _arb_project_scaled_mechs(mechs)
- for loc, mechs in local_mechs.items()}
+ local_scaled_mechs = {
+ loc: _arb_project_scaled_mechs(mechs)
+ for loc, mechs in local_mechs.items()
+ }
# populate label dict
- label_dict = _arb_populate_label_dict(local_mechs,
- local_scaled_mechs,
- pprocess_mechs)
-
- ret = {filenames[name]:
- template.render(template_name=template_name,
- banner=banner,
- morphology=morphology,
- replace_axon=replace_axon_path,
- modified_morphology=modified_morphology_path,
- filenames=filenames,
- label_dict=label_dict,
- global_mechs=global_mechs,
- global_scaled_mechs=global_scaled_mechs,
- local_mechs=local_mechs,
- local_scaled_mechs=local_scaled_mechs,
- pprocess_mechs=pprocess_mechs,
- **custom_jinja_params)
- for name, template in templates.items()}
+ label_dict = _arb_populate_label_dict(
+ local_mechs, local_scaled_mechs, pprocess_mechs
+ )
+
+ ret = {
+ filenames[name]: template.render(
+ template_name=template_name,
+ banner=banner,
+ morphology=morphology,
+ replace_axon=replace_axon_path,
+ modified_morphology=modified_morphology_path,
+ filenames=filenames,
+ label_dict=label_dict,
+ global_mechs=global_mechs,
+ global_scaled_mechs=global_scaled_mechs,
+ local_mechs=local_mechs,
+ local_scaled_mechs=local_scaled_mechs,
+ pprocess_mechs=pprocess_mechs,
+ **custom_jinja_params,
+ )
+ for name, template in templates.items()
+ }
if replace_axon is not None:
ret[replace_axon_path] = replace_axon_acc
@@ -759,12 +852,16 @@ def create_acc(mechs,
return ret
-def write_acc(output_dir, cell, parameters,
- template_filename='acc/*_template.jinja2',
- ext_catalogues=None,
- create_mod_morph=False,
- sim=None):
- '''Output mixed JSON/ACC format for Arbor cable cell to files
+def write_acc(
+ output_dir,
+ cell,
+ parameters,
+ template_filename="acc/*_template.jinja2",
+ ext_catalogues=None,
+ create_mod_morph=False,
+ sim=None,
+):
+ """Output mixed JSON/ACC format for Arbor cable cell to files
Args:
output_dir (str): Output directory. If not exists, will be created
@@ -777,18 +874,24 @@ def write_acc(output_dir, cell, parameters,
create_mod_morph (str): Output ACC with axon replacement
sim (): Neuron simulator instance (only used used with axon
replacement if morphology has not yet been instantiated)
- '''
- output = cell.create_acc(parameters, template=template_filename,
- ext_catalogues=ext_catalogues,
- create_mod_morph=create_mod_morph,
- sim=sim)
-
- cell_json = [comp_rendered
- for comp, comp_rendered in output.items()
- if pathlib.Path(comp).suffix == '.json']
+ """
+ output = cell.create_acc(
+ parameters,
+ template=template_filename,
+ ext_catalogues=ext_catalogues,
+ create_mod_morph=create_mod_morph,
+ sim=sim,
+ )
+
+ cell_json = [
+ comp_rendered
+ for comp, comp_rendered in output.items()
+ if pathlib.Path(comp).suffix == ".json"
+ ]
if len(cell_json) != 1:
raise CreateAccException(
- 'JSON file from create_acc is non-unique: %s' % cell_json)
+ "JSON file from create_acc is non-unique: %s" % cell_json
+ )
cell_json = json.loads(cell_json[0])
@@ -799,10 +902,10 @@ def write_acc(output_dir, cell, parameters,
comp_filename = output_dir.joinpath(comp)
if comp_filename.exists():
raise CreateAccException("%s already exists!" % comp_filename)
- with open(output_dir.joinpath(comp), 'w') as f:
+ with open(output_dir.joinpath(comp), "w") as f:
f.write(comp_rendered)
- morpho_filename = output_dir.joinpath(cell_json['morphology']['original'])
+ morpho_filename = output_dir.joinpath(cell_json["morphology"]["original"])
if morpho_filename.exists():
raise CreateAccException("%s already exists!" % morpho_filename)
shutil.copy2(cell.morphology.morphology_path, morpho_filename)
@@ -810,12 +913,12 @@ def write_acc(output_dir, cell, parameters,
# Read the mixed JSON/ACC-output, to be moved to Arbor in future release
def read_acc(cell_json_filename):
- '''Return constituents to build an Arbor cable cell from create_acc-export
+ """Return constituents to build an Arbor cable cell from create_acc-export
Args:
cell_json_filename (str): The path to the JSON file containing
meta-information on morphology, label-dict and decor of exported cell
- '''
+ """
with open(cell_json_filename) as cell_json_file:
cell_json = json.load(cell_json_file)
@@ -823,22 +926,24 @@ def read_acc(cell_json_filename):
cell_json_dir = pathlib.Path(cell_json_filename).parent
morpho_filename = cell_json_dir.joinpath(
- cell_json['morphology']['original'])
- replace_axon = cell_json['morphology'].get('replace_axon', None)
+ cell_json["morphology"]["original"]
+ )
+ replace_axon = cell_json["morphology"].get("replace_axon", None)
if replace_axon is not None:
replace_axon = cell_json_dir.joinpath(replace_axon)
morpho = ArbFileMorphology.load(morpho_filename, replace_axon)
decor = arbor.load_component(
- cell_json_dir.joinpath(cell_json['decor'])).component
+ cell_json_dir.joinpath(cell_json["decor"])
+ ).component
labels = arbor.load_component(
- cell_json_dir.joinpath(cell_json['label_dict'])).component
+ cell_json_dir.joinpath(cell_json["label_dict"])
+ ).component
return cell_json, morpho, decor, labels
class CreateAccException(Exception):
-
"""Exceptions generated by create_acc module"""
def __init__(self, message):
diff --git a/bluepyopt/ephys/create_hoc.py b/bluepyopt/ephys/create_hoc.py
index a287571b..b1a340c1 100644
--- a/bluepyopt/ephys/create_hoc.py
+++ b/bluepyopt/ephys/create_hoc.py
@@ -105,8 +105,18 @@ def range_exprs_to_hoc(range_params):
for param in range_params:
value = param.value_scaler.inst_distribution
value = re.sub(r'math\.', '', value)
+ value = re.sub(r'\&', '&&', value)
value = re.sub('{distance}', FLOAT_FORMAT, value)
value = re.sub('{value}', format_float(param.value), value)
+ if hasattr(param.value_scaler, "step_begin"):
+ value = re.sub(
+ '{step_begin}',
+ format_float(param.value_scaler.step_begin),
+ value
+ )
+ value = re.sub(
+ '{step_end}', format_float(param.value_scaler.step_end), value
+ )
ret.append(Range(param.location, param.name, value))
return ret
diff --git a/bluepyopt/ephys/efeatures.py b/bluepyopt/ephys/efeatures.py
index ab8c4fe1..edfa56cf 100644
--- a/bluepyopt/ephys/efeatures.py
+++ b/bluepyopt/ephys/efeatures.py
@@ -429,6 +429,11 @@ def calculate_feature(
peak_times = self._get_peak_times(
responses, raise_warnings=raise_warnings
)
+ if peak_times is None:
+ if return_waveforms:
+ return None, None
+ else:
+ return None
if len(peak_times) > 1 and self.skip_first_spike:
peak_times = peak_times[1:]
@@ -439,7 +444,10 @@ def calculate_feature(
if responses[self.recording_names[""]] is not None:
response = responses[self.recording_names[""]]
else:
- return None
+ if return_waveforms:
+ return None, None
+ else:
+ return None
if np.std(np.diff(response["time"])) > 0.001 * np.mean(
np.diff(response["time"])
diff --git a/bluepyopt/ephys/objectives.py b/bluepyopt/ephys/objectives.py
index 7df442a5..e5b12848 100644
--- a/bluepyopt/ephys/objectives.py
+++ b/bluepyopt/ephys/objectives.py
@@ -87,7 +87,7 @@ def __str__(self):
return '( %s )' % self.features[0]
-class SingletonWeightObjective(EFeatureObjective):
+class SingletonWeightObjective(SingletonObjective):
"""Single EPhys feature"""
@@ -99,7 +99,7 @@ def __init__(self, name, feature, weight):
weight (float): weight to scale to the efeature with
"""
- super(SingletonWeightObjective, self).__init__(name, [feature])
+ super(SingletonWeightObjective, self).__init__(name, feature)
self.weight = weight
def calculate_score(self, responses):
diff --git a/bluepyopt/ephys/parameterscalers/parameterscalers.py b/bluepyopt/ephys/parameterscalers/parameterscalers.py
index 47f5189a..342cb61f 100644
--- a/bluepyopt/ephys/parameterscalers/parameterscalers.py
+++ b/bluepyopt/ephys/parameterscalers/parameterscalers.py
@@ -161,9 +161,8 @@ def inst_distribution(self):
# Use this special formatting to bypass missing keys
return string.Formatter().vformat(self.distribution, (), dist_dict)
- def eval_dist(self, values, distance):
- """Create the final dist string"""
-
+ def scale_dict(self, values, distance):
+ """Create scale dictionary"""
scale_dict = {}
if isinstance(values, dict):
for k, v in values.items():
@@ -172,6 +171,12 @@ def eval_dist(self, values, distance):
scale_dict["value"] = format_float(values)
scale_dict["distance"] = format_float(distance)
+ return scale_dict
+
+ def eval_dist(self, values, distance):
+ """Create the final dist string"""
+ scale_dict = self.scale_dict(values, distance)
+
return self.inst_distribution.format(**scale_dict)
def scale(self, values, segment, sim=None):
@@ -261,3 +266,51 @@ def acc_scale_iexpr(self, value, constant_formatter=format_float):
ArbFileMorphology.region_labels['somatic'].ref
)
return generate_acc_scale_iexpr(iexpr, variables, constant_formatter)
+
+
+class NrnSegmentSomaDistanceStepScaler(NrnSegmentSomaDistanceScaler,
+ ParameterScaler, DictMixin):
+
+ """Scaler based on distance from soma with a step function"""
+ SERIALIZED_FIELDS = ('name', 'comment', 'distribution', )
+
+ def __init__(
+ self,
+ name=None,
+ distribution=None,
+ comment='',
+ dist_param_names=None,
+ soma_ref_location=0.5,
+ step_begin=None,
+ step_end=None):
+ """Constructor
+ Args:
+ name (str): name of this object
+ distribution (str): distribution of parameter dependent on distance
+ from soma. string can contain `distance` and/or `value` as
+ placeholders for the distance to the soma and parameter value
+ respectively. It can also contain step_begin and step_end.
+ dist_param_names (list): list of names of parameters that
+ parametrise the distribution. These names will become
+ attributes of this object.
+ The distribution string should contain these names, and they
+ will be replaced by values of the corresponding attributes
+ soma_ref_location (float): location along the soma used as origin
+ from which to compute the distances. Expressed as a fraction
+ (between 0.0 and 1.0).
+ step_begin (float): distance at which the step begins
+ step_end (float): distance at which the step ends
+ """
+
+ super(NrnSegmentSomaDistanceStepScaler, self).__init__(
+ name, distribution, comment, dist_param_names,
+ soma_ref_location=soma_ref_location)
+ self.step_begin = step_begin
+ self.step_end = step_end
+
+ def scale_dict(self, values, distance):
+ scale_dict = super().scale_dict(values, distance)
+ scale_dict["step_begin"] = self.step_begin
+ scale_dict["step_end"] = self.step_end
+
+ return scale_dict
diff --git a/bluepyopt/ephys/serializer.py b/bluepyopt/ephys/serializer.py
index 77b64e87..3265271b 100644
--- a/bluepyopt/ephys/serializer.py
+++ b/bluepyopt/ephys/serializer.py
@@ -1,8 +1,4 @@
'''Mixin class to make dictionaries'''
-from __future__ import unicode_literals
-from __future__ import print_function
-from __future__ import division
-from __future__ import absolute_import
# Disabling lines below, generate error when loading ephys.examples
# from future import standard_library
diff --git a/bluepyopt/ephys/simulators.py b/bluepyopt/ephys/simulators.py
index 71790bc8..de546633 100644
--- a/bluepyopt/ephys/simulators.py
+++ b/bluepyopt/ephys/simulators.py
@@ -2,26 +2,30 @@
# pylint: disable=W0511
-import os
-import logging
-import imp
import ctypes
+import importlib.util
+import logging
+import os
+import pathlib
import platform
import warnings
-import pathlib
from bluepyopt.ephys.acc import arbor
-
logger = logging.getLogger(__name__)
class NrnSimulator(object):
-
"""Neuron simulator"""
- def __init__(self, dt=None, cvode_active=True, cvode_minstep=None,
- random123_globalindex=None, mechanisms_directory=None):
+ def __init__(
+ self,
+ dt=None,
+ cvode_active=True,
+ cvode_minstep=None,
+ random123_globalindex=None,
+ mechanisms_directory=None,
+ ):
"""Constructor
Args:
@@ -41,7 +45,7 @@ def __init__(self, dt=None, cvode_active=True, cvode_minstep=None,
# hoc.so does not exist on NEURON Windows or MacOS
# although \\hoc.pyd can work here, it gives an error for
# nrn_nobanner_ line
- self.disable_banner = platform.system() not in ['Windows', 'Darwin']
+ self.disable_banner = platform.system() not in ["Windows", "Darwin"]
self.banner_disabled = False
self.mechanisms_directory = mechanisms_directory
@@ -77,18 +81,21 @@ def cvode_minstep(self, value):
def _nrn_disable_banner():
"""Disable Neuron banner"""
- nrnpy_path = os.path.join(imp.find_module('neuron')[1])
- import glob
- hoc_so_list = \
- glob.glob(os.path.join(nrnpy_path, 'hoc*.so'))
+ nrnpy_path = pathlib.Path(
+ importlib.util.find_spec("neuron").origin
+ ).parent
+
+ hoc_so_list = list(nrnpy_path.glob("hoc*.so"))
if len(hoc_so_list) != 1:
- warnings.warn('Unable to find Neuron hoc shared library in %s, '
- 'not disabling banner' % nrnpy_path)
+ warnings.warn(
+ "Unable to find Neuron hoc shared library in %s, "
+ "not disabling banner" % nrnpy_path
+ )
else:
hoc_so = hoc_so_list[0]
- nrndll = ctypes.cdll[hoc_so]
- ctypes.c_int.in_dll(nrndll, 'nrn_nobanner_').value = 1
+ nrndll = ctypes.cdll[str(hoc_so)]
+ ctypes.c_int.in_dll(nrndll, "nrn_nobanner_").value = 1
# pylint: disable=R0201
@property
@@ -110,24 +117,26 @@ def neuron(self):
def initialize(self):
"""Initialize simulator: Set Neuron variables"""
- self.neuron.h.load_file('stdrun.hoc')
+ self.neuron.h.load_file("stdrun.hoc")
self.neuron.h.dt = self.dt
self.neuron.h.cvode_active(1 if self.cvode_active else 0)
def run(
- self,
- tstop=None,
- dt=None,
- cvode_active=None,
- random123_globalindex=None):
+ self,
+ tstop=None,
+ dt=None,
+ cvode_active=None,
+ random123_globalindex=None,
+ ):
"""Run protocol"""
self.neuron.h.tstop = tstop
if cvode_active and dt is not None:
raise ValueError(
- 'NrnSimulator: Impossible to combine the dt argument when '
- 'cvode_active is True in the NrnSimulator run method')
+ "NrnSimulator: Impossible to combine the dt argument when "
+ "cvode_active is True in the NrnSimulator run method"
+ )
if cvode_active is None:
cvode_active = self.cvode_active
@@ -135,11 +144,12 @@ def run(
if not cvode_active and dt is None: # use dt of simulator
if self.neuron.h.dt != self.dt:
raise Exception(
- 'NrnSimulator: Some process has changed the '
- 'time step dt of Neuron since the creation of this '
- 'NrnSimulator object. Not sure this is intended:\n'
- 'current dt: %.6g\n'
- 'init dt: %.6g' % (self.neuron.h.dt, self.dt))
+ "NrnSimulator: Some process has changed the "
+ "time step dt of Neuron since the creation of this "
+ "NrnSimulator object. Not sure this is intended:\n"
+ "current dt: %.6g\n"
+ "init dt: %.6g" % (self.neuron.h.dt, self.dt)
+ )
dt = self.dt
self.neuron.h.cvode_active(1 if cvode_active else 0)
@@ -148,14 +158,13 @@ def run(
self.cvode_minstep = self.cvode_minstep_value
if cvode_active:
- logger.debug('Running Neuron simulator %.6g ms, with cvode', tstop)
+ logger.debug("Running Neuron simulator %.6g ms, with cvode", tstop)
else:
self.neuron.h.dt = dt
self.neuron.h.steps_per_ms = 1.0 / dt
logger.debug(
- 'Running Neuron simulator %.6g ms, with dt=%r',
- tstop,
- dt)
+ "Running Neuron simulator %.6g ms, with dt=%r", tstop, dt
+ )
if random123_globalindex is None:
random123_globalindex = self.random123_globalindex
@@ -167,16 +176,15 @@ def run(
try:
self.neuron.h.run()
except Exception as e:
- raise NrnSimulatorException('Neuron simulator error', e)
+ raise NrnSimulatorException("Neuron simulator error", e)
if self.cvode_minstep_value is not None:
self.cvode_minstep = save_minstep
- logger.debug('Neuron simulation finished')
+ logger.debug("Neuron simulation finished")
class NrnSimulatorException(Exception):
-
"""All exception generated by Neuron simulator"""
def __init__(self, message, original):
@@ -187,11 +195,16 @@ def __init__(self, message, original):
class LFPySimulator(NrnSimulator):
-
"""LFPy simulator"""
- def __init__(self, dt=None, cvode_active=True, cvode_minstep=None,
- random123_globalindex=None, mechanisms_directory=None):
+ def __init__(
+ self,
+ dt=None,
+ cvode_active=True,
+ cvode_minstep=None,
+ random123_globalindex=None,
+ mechanisms_directory=None,
+ ):
"""Constructor
Args:
@@ -213,17 +226,18 @@ def __init__(self, dt=None, cvode_active=True, cvode_minstep=None,
cvode_active=cvode_active,
cvode_minstep=cvode_minstep,
random123_globalindex=random123_globalindex,
- mechanisms_directory=mechanisms_directory
+ mechanisms_directory=mechanisms_directory,
)
def run(
- self,
- lfpy_cell,
- lfpy_electrode,
- tstop=None,
- dt=None,
- cvode_active=None,
- random123_globalindex=None):
+ self,
+ lfpy_cell,
+ lfpy_electrode,
+ tstop=None,
+ dt=None,
+ cvode_active=None,
+ random123_globalindex=None,
+ ):
"""Run protocol"""
_ = self.neuron
@@ -235,8 +249,9 @@ def run(
if cvode_active and dt is not None:
raise ValueError(
- 'NrnSimulator: Impossible to combine the dt argument when '
- 'cvode_active is True in the NrnSimulator run method')
+ "NrnSimulator: Impossible to combine the dt argument when "
+ "cvode_active is True in the NrnSimulator run method"
+ )
if cvode_active is None:
cvode_active = self.cvode_active
@@ -276,7 +291,6 @@ def run(
class LFPySimulatorException(Exception):
-
"""All exception generated by LFPy simulator"""
def __init__(self, message, original):
@@ -287,7 +301,6 @@ def __init__(self, message, original):
class ArbSimulator(object):
-
"""Arbor simulator"""
def __init__(self, dt=None, ext_catalogues=None):
@@ -303,14 +316,15 @@ def __init__(self, dt=None, ext_catalogues=None):
self.ext_catalogues = ext_catalogues
if ext_catalogues is not None:
for cat, cat_path in ext_catalogues.items():
- cat_lib = '%s-catalogue.so' % cat
+ cat_lib = "%s-catalogue.so" % cat
cat_path = pathlib.Path(cat_path).resolve()
if not os.path.exists(cat_path / cat_lib):
raise ArbSimulatorException(
- 'Cannot find %s at %s - first build' % (cat_lib,
- cat_path) +
- ' mechanism catalogue with modcc:' +
- ' arbor-build-catalogue %s %s' % (cat, cat_path))
+ "Cannot find %s at %s - first build"
+ % (cat_lib, cat_path)
+ + " mechanism catalogue with modcc:"
+ + " arbor-build-catalogue %s %s" % (cat, cat_path)
+ )
# TODO: add parameters for discretization
def initialize(self):
@@ -318,9 +332,9 @@ def initialize(self):
pass
def instantiate(self, morph, decor, labels):
- cable_cell = arbor.cable_cell(morphology=morph,
- decor=decor,
- labels=labels)
+ cable_cell = arbor.cable_cell(
+ morphology=morph, decor=decor, labels=labels
+ )
arb_cell_model = arbor.single_cell_model(cable_cell)
@@ -330,35 +344,31 @@ def instantiate(self, morph, decor, labels):
# User-supplied catalogues take precedence
if self.ext_catalogues is not None:
for cat, cat_path in self.ext_catalogues.items():
- cat_lib = '%s-catalogue.so' % cat
+ cat_lib = "%s-catalogue.so" % cat
cat_path = pathlib.Path(cat_path).resolve()
arb_cell_model.properties.catalogue.extend(
- arbor.load_catalogue(cat_path / cat_lib),
- cat + "::")
+ arbor.load_catalogue(cat_path / cat_lib), cat + "::"
+ )
# Built-in catalogues are always added (could be made optional)
- if self.ext_catalogues is None or \
- 'default' not in self.ext_catalogues:
+ if self.ext_catalogues is None or "default" not in self.ext_catalogues:
arb_cell_model.properties.catalogue.extend(
- arbor.default_catalogue(), "default::")
+ arbor.default_catalogue(), "default::"
+ )
- if self.ext_catalogues is None or \
- 'BBP' not in self.ext_catalogues:
+ if self.ext_catalogues is None or "BBP" not in self.ext_catalogues:
arb_cell_model.properties.catalogue.extend(
- arbor.bbp_catalogue(), "BBP::")
+ arbor.bbp_catalogue(), "BBP::"
+ )
- if self.ext_catalogues is None or \
- 'allen' not in self.ext_catalogues:
+ if self.ext_catalogues is None or "allen" not in self.ext_catalogues:
arb_cell_model.properties.catalogue.extend(
- arbor.allen_catalogue(), "allen::")
+ arbor.allen_catalogue(), "allen::"
+ )
return arb_cell_model
- def run(self,
- arb_cell_model,
- tstop=None,
- dt=None):
-
+ def run(self, arb_cell_model, tstop=None, dt=None):
dt = dt if dt is not None else self.dt
if dt is not None:
@@ -368,7 +378,6 @@ def run(self,
class ArbSimulatorException(Exception):
-
"""All exception generated by Arbor simulator"""
def __init__(self, message):
diff --git a/bluepyopt/ephys/templates/cell_template.jinja2 b/bluepyopt/ephys/templates/cell_template.jinja2
index 33210a3c..65d9376b 100644
--- a/bluepyopt/ephys/templates/cell_template.jinja2
+++ b/bluepyopt/ephys/templates/cell_template.jinja2
@@ -42,6 +42,10 @@ begintemplate {{template_name}}
public all, somatic, apical, axonal, basal, myelinated, APC
objref all, somatic, apical, axonal, basal, myelinated, APC
+obfunc getCell(){
+ return this
+}
+
proc init(/* args: morphology_dir, morphology_name */) {
all = new SectionList()
apical = new SectionList()
@@ -120,7 +124,8 @@ proc distribute_distance(){local x localobj sl
this.soma[0] distance(0, 0.5)
sprint(distfunc, "%%s %s(%%f) = %s", mech, distfunc)
forsec sl for(x, 0) {
- sprint(stmp, distfunc, secname(), x, distance(x))
+ // use distance(x) twice for the step distribution case, e.g. for calcium hotspot
+ sprint(stmp, distfunc, secname(), x, distance(x), distance(x))
execute(stmp)
}
}
diff --git a/bluepyopt/ipyp/bpopt_tasksdb.py b/bluepyopt/ipyp/bpopt_tasksdb.py
index edafd2d7..5709209c 100644
--- a/bluepyopt/ipyp/bpopt_tasksdb.py
+++ b/bluepyopt/ipyp/bpopt_tasksdb.py
@@ -1,7 +1,5 @@
"""Get stats out of ipyparallel's tasks.db"""
-from __future__ import print_function
-
"""
Copyright (c) 2016-2020, EPFL/Blue Brain Project
diff --git a/bluepyopt/tests/test_ephys/test_create_acc.py b/bluepyopt/tests/test_ephys/test_create_acc.py
index 649b0af5..baae6def 100644
--- a/bluepyopt/tests/test_ephys/test_create_acc.py
+++ b/bluepyopt/tests/test_ephys/test_create_acc.py
@@ -2,46 +2,47 @@
# pylint: disable=W0212
+import json
import os
-import sys
import pathlib
import re
-import json
+import sys
import tempfile
-from bluepyopt.ephys.acc import arbor, ArbLabel
-from bluepyopt.ephys.morphologies import ArbFileMorphology
-from bluepyopt.ephys.parameterscalers import NrnSegmentSomaDistanceScaler
-
-from . import utils
+import pytest
from bluepyopt import ephys
from bluepyopt.ephys import create_acc
-from bluepyopt.ephys.create_acc import (Nrn2ArbParamAdapter,
- Nrn2ArbMechGrouper,
- ArbNmodlMechFormatter)
+from bluepyopt.ephys.acc import ArbLabel, arbor
+from bluepyopt.ephys.create_acc import (
+ ArbNmodlMechFormatter,
+ Nrn2ArbMechGrouper,
+ Nrn2ArbParamAdapter,
+)
+from bluepyopt.ephys.morphologies import ArbFileMorphology
+from bluepyopt.ephys.parameterscalers import NrnSegmentSomaDistanceScaler
-import pytest
+from . import utils
DEFAULT_ARBOR_REGION_ORDER = [
- ('soma', 1),
- ('axon', 2),
- ('dend', 3),
- ('apic', 4),
- ('myelin', 5)]
+ ("soma", 1),
+ ("axon", 2),
+ ("dend", 3),
+ ("apic", 4),
+ ("myelin", 5),
+]
-testdata_dir = pathlib.Path(__file__).parent.joinpath(
- 'testdata')
+testdata_dir = pathlib.Path(__file__).parent.joinpath("testdata")
@pytest.mark.unit
def test_read_templates():
"""Unit test for _read_templates function."""
- template_dir = testdata_dir / 'acc/templates'
+ template_dir = testdata_dir / "acc/templates"
template_filename = "*_template.jinja2"
templates = create_acc._read_templates(template_dir, template_filename)
- assert templates.keys() == {'label_dict.acc', 'cell.json', 'decor.acc'}
+ assert templates.keys() == {"label_dict.acc", "cell.json", "decor.acc"}
with pytest.raises(FileNotFoundError):
create_acc._read_templates("DOES_NOT_EXIST", template_filename)
@@ -52,13 +53,14 @@ def test_Nrn2ArbParamAdapter_param_name():
"""Test Neuron to Arbor parameter mapping."""
# Identity
mech_param_name = "gSKv3_1bar_SKv3_1"
- assert Nrn2ArbParamAdapter._param_name(mech_param_name) \
- == mech_param_name
+ assert Nrn2ArbParamAdapter._param_name(mech_param_name) == mech_param_name
# Non-trivial transformation
global_property_name = "v_init"
- assert Nrn2ArbParamAdapter._param_name(global_property_name) \
+ assert (
+ Nrn2ArbParamAdapter._param_name(global_property_name)
== "membrane-potential"
+ )
@pytest.mark.unit
@@ -75,7 +77,8 @@ def test_Nrn2ArbParamAdapter_param_value():
# Non-trivial name and value/units transformation
global_property = create_acc.Location(name="celsius", value=34)
assert Nrn2ArbParamAdapter._param_value(global_property) == (
- "307.14999999999998")
+ "307.14999999999998"
+ )
@pytest.mark.unit
@@ -85,38 +88,39 @@ def test_Nrn2ArbParamAdapter_format():
mech_param = create_acc.Location(name="gSKv3_1bar_SKv3_1", value="1.025")
mech = "SKv3_1"
arb_mech_param = create_acc.Location(name="gSKv3_1bar", value="1.025")
- assert (
- Nrn2ArbParamAdapter.format(
- mech_param, mechs=[mech])
- == (mech, arb_mech_param)
+ assert Nrn2ArbParamAdapter.format(mech_param, mechs=[mech]) == (
+ mech,
+ arb_mech_param,
)
# Non-unique mapping to mechanisms
with pytest.raises(create_acc.CreateAccException):
- Nrn2ArbParamAdapter.format(
- mech_param, mechs=["SKv3_1", "1"])
+ Nrn2ArbParamAdapter.format(mech_param, mechs=["SKv3_1", "1"])
# Global property with non-trivial transformation
global_property = create_acc.Location(name="celsius", value="0")
mech = None
arb_global_property = create_acc.Location(
- name="temperature-kelvin", value="273.14999999999998")
+ name="temperature-kelvin", value="273.14999999999998"
+ )
# Non-trivial name and value/units transformation
- assert Nrn2ArbParamAdapter.format(global_property, []) == \
- (mech, arb_global_property)
+ assert Nrn2ArbParamAdapter.format(global_property, []) == (
+ mech,
+ arb_global_property,
+ )
# Inhomogeneuos mechanism parameter
apical_region = ArbLabel("region", "apic", "(tag 4)")
param_scaler = NrnSegmentSomaDistanceScaler(
- name='soma-distance-scaler',
- distribution='(-0.8696 + 2.087*math.exp(({distance})*0.0031))*{value}'
+ name="soma-distance-scaler",
+ distribution="(-0.8696 + 2.087*math.exp(({distance})*0.0031))*{value}",
)
iexpr_param = create_acc.RangeExpr(
location=apical_region,
name="gkbar_hh",
value="0.025",
- value_scaler=param_scaler
+ value_scaler=param_scaler,
)
mech = "hh"
arb_iexpr_param = create_acc.RangeExpr(
@@ -125,50 +129,56 @@ def test_Nrn2ArbParamAdapter_format():
value="0.025",
value_scaler=param_scaler,
)
- assert (
- Nrn2ArbParamAdapter.format(
- iexpr_param, mechs=[mech])
- == (mech, arb_iexpr_param)
+ assert Nrn2ArbParamAdapter.format(iexpr_param, mechs=[mech]) == (
+ mech,
+ arb_iexpr_param,
)
# Point process mechanism parameter
loc = ephys.locations.ArbLocsetLocation(
- name='somacenter',
- locset='(location 0 0.5)')
+ name="somacenter", locset="(location 0 0.5)"
+ )
mech = ephys.mechanisms.NrnMODPointProcessMechanism(
- name='expsyn',
- suffix='ExpSyn',
- locations=[loc])
+ name="expsyn", suffix="ExpSyn", locations=[loc]
+ )
mech_loc = ephys.locations.NrnPointProcessLocation(
- 'expsyn_loc',
- pprocess_mech=mech)
+ "expsyn_loc", pprocess_mech=mech
+ )
point_expr_param = create_acc.PointExpr(
- name="tau", value="10", point_loc=[mech_loc])
+ name="tau", value="10", point_loc=[mech_loc]
+ )
arb_point_expr_param = create_acc.PointExpr(
- name="tau", value="10", point_loc=[mech_loc])
- assert (
- Nrn2ArbParamAdapter.format(
- point_expr_param, mechs=[mech])
- == (mech, arb_point_expr_param)
+ name="tau", value="10", point_loc=[mech_loc]
+ )
+ assert Nrn2ArbParamAdapter.format(point_expr_param, mechs=[mech]) == (
+ mech,
+ arb_point_expr_param,
)
@pytest.mark.unit
def test_Nrn2ArbMechGrouper_format_params_and_group_by_mech():
"""Test grouping of parameters by mechanism."""
- params = [create_acc.Location(name="gSKv3_1bar_SKv3_1", value="1.025"),
- create_acc.Location(name="ena", value="-30")]
+ params = [
+ create_acc.Location(name="gSKv3_1bar_SKv3_1", value="1.025"),
+ create_acc.Location(name="ena", value="-30"),
+ ]
mechs = ["SKv3_1"]
- local_mechs = Nrn2ArbMechGrouper.\
- _format_params_and_group_by_mech(params, mechs)
- assert local_mechs == \
- {None: [create_acc.Location(name="ion-reversal-potential \"na\"",
- value="-30")],
- "SKv3_1": [create_acc.Location(name="gSKv3_1bar", value="1.025")]}
+ local_mechs = Nrn2ArbMechGrouper._format_params_and_group_by_mech(
+ params, mechs
+ )
+ assert local_mechs == {
+ None: [
+ create_acc.Location(
+ name='ion-reversal-potential "na"', value="-30"
+ )
+ ],
+ "SKv3_1": [create_acc.Location(name="gSKv3_1bar", value="1.025")],
+ }
@pytest.mark.unit
@@ -177,10 +187,13 @@ def test_Nrn2ArbMechGrouper_process_global():
params = {"ki": 3, "v_init": -65}
global_mechs = Nrn2ArbMechGrouper.process_global(params)
assert global_mechs == {
- None: [create_acc.Location(name="ion-internal-concentration \"k\"",
- value="3"),
- create_acc.Location(name="membrane-potential",
- value="-65")]}
+ None: [
+ create_acc.Location(
+ name='ion-internal-concentration "k"', value="3"
+ ),
+ create_acc.Location(name="membrane-potential", value="-65"),
+ ]
+ }
@pytest.mark.unit
@@ -188,22 +201,23 @@ def test_Nrn2ArbMechGrouper_is_global_property():
"""Test adapting local parameters from Neuron to Arbor."""
all_regions = ArbLabel("region", "all_regions", "(all)")
param = create_acc.Location(name="axial-resistivity", value="1")
- assert Nrn2ArbMechGrouper._is_global_property(
- all_regions, param) is True
+ assert Nrn2ArbMechGrouper._is_global_property(all_regions, param) is True
soma_region = ArbLabel("region", "soma", "(tag 1)")
- assert Nrn2ArbMechGrouper._is_global_property(
- soma_region, param) is False
+ assert Nrn2ArbMechGrouper._is_global_property(soma_region, param) is False
@pytest.mark.unit
def test_separate_global_properties():
"""Test separating global properties from label-specific mechs."""
all_regions = ArbLabel("region", "all_regions", "(all)")
- mechs = {None: [create_acc.Location(name="axial-resistivity", value="1")],
- "SKv3_1": [create_acc.Location(name="gSKv3_1bar", value="1.025")]}
- local_mechs, global_properties = \
+ mechs = {
+ None: [create_acc.Location(name="axial-resistivity", value="1")],
+ "SKv3_1": [create_acc.Location(name="gSKv3_1bar", value="1.025")],
+ }
+ local_mechs, global_properties = (
Nrn2ArbMechGrouper._separate_global_properties(all_regions, mechs)
+ )
assert local_mechs == {None: [], "SKv3_1": mechs["SKv3_1"]}
assert global_properties == {None: mechs[None]}
@@ -214,23 +228,28 @@ def test_Nrn2ArbMechGrouper_process_local():
all_regions = ArbLabel("region", "all_regions", "(all)")
soma_region = ArbLabel("region", "soma", "(tag 1)")
params = [
- (all_regions,
- [create_acc.Location(name="cm", value="100")]),
- (soma_region,
- [create_acc.Location(name="v_init", value="-65"),
- create_acc.Location(name="gSKv3_1bar_SKv3_1", value="1.025")])
+ (all_regions, [create_acc.Location(name="cm", value="100")]),
+ (
+ soma_region,
+ [
+ create_acc.Location(name="v_init", value="-65"),
+ create_acc.Location(name="gSKv3_1bar_SKv3_1", value="1.025"),
+ ],
+ ),
]
channels = {all_regions: [], soma_region: ["SKv3_1"]}
- local_mechs, global_properties = \
- Nrn2ArbMechGrouper.process_local(params, channels)
+ local_mechs, global_properties = Nrn2ArbMechGrouper.process_local(
+ params, channels
+ )
assert local_mechs.keys() == {all_regions, soma_region}
assert local_mechs[all_regions] == {None: []}
assert local_mechs[soma_region] == {
None: [create_acc.Location(name="membrane-potential", value="-65")],
- "SKv3_1": [create_acc.Location(name="gSKv3_1bar", value="1.025")]
+ "SKv3_1": [create_acc.Location(name="gSKv3_1bar", value="1.025")],
}
assert global_properties == {
- None: [create_acc.Location(name="membrane-capacitance", value="1")]}
+ None: [create_acc.Location(name="membrane-capacitance", value="1")]
+ }
@pytest.mark.unit
@@ -239,8 +258,8 @@ def test_ArbNmodlMechFormatter_load_mech_catalogue_meta():
nmodl_formatter = ArbNmodlMechFormatter(None)
assert isinstance(nmodl_formatter.cats, dict)
- assert nmodl_formatter.cats.keys() == {'BBP', 'default', 'allen'}
- assert "Ca_HVA" in nmodl_formatter.cats['BBP']
+ assert nmodl_formatter.cats.keys() == {"BBP", "default", "allen"}
+ assert "Ca_HVA" in nmodl_formatter.cats["BBP"]
@pytest.mark.unit
@@ -261,7 +280,7 @@ def test_ArbNmodlMechFormatter_translate_density():
value="0.029999999999999999",
scale=(
"(add (scalar -0.62109375) (mul (scalar 0.546875) "
- "(log (add (mul (distance (region \"soma\"))"
+ '(log (add (mul (distance (region "soma"))'
" (scalar 0.421875) ) (scalar 1.25) ) ) ) )"
),
),
@@ -273,7 +292,7 @@ def test_ArbNmodlMechFormatter_translate_density():
value="0.029999999999999999",
scale=(
"(add (scalar -0.62109375) (mul (scalar 0.546875) "
- "(log (add (mul (distance (region \"soma\"))"
+ '(log (add (mul (distance (region "soma"))'
" (scalar 0.421875) ) (scalar 1.25) ) ) ) )"
),
),
@@ -281,8 +300,7 @@ def test_ArbNmodlMechFormatter_translate_density():
}
nmodl_formatter = ArbNmodlMechFormatter(None)
translated_mechs = nmodl_formatter.translate_density(mechs)
- assert translated_mechs.keys() == {"default::hh",
- "default::pas/e=0.25"}
+ assert translated_mechs.keys() == {"default::hh", "default::pas/e=0.25"}
assert translated_mechs["default::hh"] == mechs["hh"]
assert translated_mechs["default::pas/e=0.25"] == mechs["pas"][1:]
@@ -291,21 +309,21 @@ def test_ArbNmodlMechFormatter_translate_density():
def test_arb_populate_label_dict():
"""Unit test for _populate_label_dict."""
local_mechs = {ArbLabel("region", "all", "(all)"): {}}
- local_scaled_mechs = {
- ArbLabel("region", "first_branch", "(branch 0)"): {}}
+ local_scaled_mechs = {ArbLabel("region", "first_branch", "(branch 0)"): {}}
pprocess_mechs = {}
- label_dict = create_acc._arb_populate_label_dict(local_mechs,
- local_scaled_mechs,
- pprocess_mechs)
+ label_dict = create_acc._arb_populate_label_dict(
+ local_mechs, local_scaled_mechs, pprocess_mechs
+ )
assert label_dict.keys() == {"all", "first_branch"}
with pytest.raises(create_acc.CreateAccException):
other_pprocess_mechs = {
- ArbLabel("region", "first_branch", "(branch 1)"): {}}
- create_acc._arb_populate_label_dict(local_mechs,
- local_scaled_mechs,
- other_pprocess_mechs)
+ ArbLabel("region", "first_branch", "(branch 1)"): {}
+ }
+ create_acc._arb_populate_label_dict(
+ local_mechs, local_scaled_mechs, other_pprocess_mechs
+ )
@pytest.mark.unit
@@ -314,11 +332,16 @@ def test_create_acc():
mech = utils.make_mech()
parameters = utils.make_parameters()
- acc = create_acc.create_acc([mech, ], parameters,
- morphology='CCell.swc',
- template_name='CCell')
+ acc = create_acc.create_acc(
+ [
+ mech,
+ ],
+ parameters,
+ morphology="CCell.swc",
+ template_name="CCell",
+ )
- ref_dir = testdata_dir / 'acc/CCell'
+ ref_dir = testdata_dir / "acc/CCell"
cell_json = "CCell.json"
decor_acc = "CCell_decor.acc"
label_dict_acc = "CCell_label_dict.acc"
@@ -326,22 +349,22 @@ def test_create_acc():
# Testing keys
assert cell_json in acc
cell_json_dict = json.loads(acc[cell_json])
- assert 'cell_model_name' in cell_json_dict
- assert 'produced_by' in cell_json_dict
- assert 'morphology' in cell_json_dict
- assert 'label_dict' in cell_json_dict
- assert 'decor' in cell_json_dict
+ assert "cell_model_name" in cell_json_dict
+ assert "produced_by" in cell_json_dict
+ assert "morphology" in cell_json_dict
+ assert "label_dict" in cell_json_dict
+ assert "decor" in cell_json_dict
# Testing values
with open(ref_dir / cell_json) as f:
ref_cell_json = json.load(f)
for k in ref_cell_json:
- if k != 'produced_by':
+ if k != "produced_by":
assert ref_cell_json[k] == cell_json_dict[k]
# Testing building blocks
assert decor_acc in acc
- assert acc[decor_acc].startswith('(arbor-component')
- assert '(decor' in acc[decor_acc]
+ assert acc[decor_acc].startswith("(arbor-component")
+ assert "(decor" in acc[decor_acc]
# Testing values
with open(ref_dir / decor_acc) as f:
ref_decor = f.read()
@@ -349,19 +372,20 @@ def test_create_acc():
# Testing building blocks
assert label_dict_acc in acc
- assert acc[label_dict_acc].startswith('(arbor-component')
- assert '(label-dict' in acc[label_dict_acc]
- matches = re.findall(r'\(region-def "(?P\w+)" \(tag (?P\d+)\)\)',
- acc[label_dict_acc])
+ assert acc[label_dict_acc].startswith("(arbor-component")
+ assert "(label-dict" in acc[label_dict_acc]
+ matches = re.findall(
+ r'\(region-def "(?P\w+)" \(tag (?P\d+)\)\)',
+ acc[label_dict_acc],
+ )
for pos, loc_tag in enumerate(DEFAULT_ARBOR_REGION_ORDER):
assert matches[pos][0] == loc_tag[0]
assert matches[pos][1] == str(loc_tag[1])
# Testing values
- ref_labels = arbor.load_component(
- ref_dir / label_dict_acc).component
+ ref_labels = arbor.load_component(ref_dir / label_dict_acc).component
with tempfile.TemporaryDirectory() as test_dir:
test_labels_filename = pathlib.Path(test_dir).joinpath(label_dict_acc)
- with open(test_labels_filename, 'w') as f:
+ with open(test_labels_filename, "w") as f:
f.write(acc[label_dict_acc])
test_labels = arbor.load_component(test_labels_filename).component
assert dict(ref_labels.items()) == dict(test_labels.items())
@@ -375,41 +399,46 @@ def test_create_acc_filename():
custom_param_val = str(__file__)
acc = create_acc.create_acc(
- [mech, ],
- parameters, morphology='CCell.asc',
- template_name='CCell',
- template_filename='acc/templates/*_template.jinja2',
+ [
+ mech,
+ ],
+ parameters,
+ morphology="CCell.asc",
+ template_name="CCell",
+ template_filename="acc/templates/*_template.jinja2",
template_dir=testdata_dir,
- custom_jinja_params={
- 'custom_param': custom_param_val})
+ custom_jinja_params={"custom_param": custom_param_val},
+ )
cell_json = "CCell_cell.json"
decor_acc = "CCell_decor.acc"
label_dict_acc = "CCell_label_dict.acc"
assert cell_json in acc
cell_json_dict = json.loads(acc[cell_json])
- assert 'cell_model_name' in cell_json_dict
- assert 'produced_by' in cell_json_dict
- assert 'morphology' in cell_json_dict
- assert 'label_dict' in cell_json_dict
- assert 'decor' in cell_json_dict
+ assert "cell_model_name" in cell_json_dict
+ assert "produced_by" in cell_json_dict
+ assert "morphology" in cell_json_dict
+ assert "label_dict" in cell_json_dict
+ assert "decor" in cell_json_dict
assert decor_acc in acc
- assert acc[decor_acc].startswith('(arbor-component')
- assert '(decor' in acc[decor_acc]
+ assert acc[decor_acc].startswith("(arbor-component")
+ assert "(decor" in acc[decor_acc]
assert label_dict_acc in acc
- assert acc[label_dict_acc].startswith('(arbor-component')
- assert '(label-dict' in acc[label_dict_acc]
- matches = re.findall(r'\(region-def "(?P\w+)" \(tag (?P\d+)\)\)',
- acc[label_dict_acc])
+ assert acc[label_dict_acc].startswith("(arbor-component")
+ assert "(label-dict" in acc[label_dict_acc]
+ matches = re.findall(
+ r'\(region-def "(?P\w+)" \(tag (?P\d+)\)\)',
+ acc[label_dict_acc],
+ )
for pos, loc_tag in enumerate(DEFAULT_ARBOR_REGION_ORDER):
assert matches[pos][0] == loc_tag[0]
assert matches[pos][1] == str(loc_tag[1])
assert '(meta-data (info "test-decor"))' in acc[decor_acc]
assert '(meta-data (info "test-label-dict"))' in acc[label_dict_acc]
- assert custom_param_val in cell_json_dict['produced_by']
+ assert custom_param_val in cell_json_dict["produced_by"]
@pytest.mark.unit
@@ -426,60 +455,76 @@ def test_create_acc_replace_axon():
latest_seg,
arbor.mpoint(prox_x, 0, 0, 0.5),
arbor.mpoint(dist_x, 0, 0, 0.5),
- ArbFileMorphology.tags['axon']
+ ArbFileMorphology.tags["axon"],
)
replace_axon = arbor.morphology(replace_axon_st)
try:
- acc = create_acc.create_acc([mech, ], parameters,
- morphology_dir=testdata_dir,
- morphology='simple.swc',
- template_name='CCell',
- replace_axon=replace_axon)
+ acc = create_acc.create_acc(
+ [
+ mech,
+ ],
+ parameters,
+ morphology_dir=testdata_dir,
+ morphology="simple.swc",
+ template_name="CCell",
+ replace_axon=replace_axon,
+ )
except Exception as e: # fail with an older Arbor version
assert isinstance(e, NotImplementedError)
- assert len(e.args) == 1 and e.args[0] == \
- "Need a newer version of Arbor for axon replacement."
+ assert (
+ len(e.args) == 1
+ and e.args[0]
+ == "Need a newer version of Arbor for axon replacement."
+ )
return
cell_json = "CCell.json"
cell_json_dict = json.loads(acc[cell_json])
- assert 'replace_axon' in cell_json_dict['morphology']
+ assert "replace_axon" in cell_json_dict["morphology"]
- with open(testdata_dir / 'acc/CCell/simple_axon_replacement.acc') as f:
+ with open(testdata_dir / "acc/CCell/simple_axon_replacement.acc") as f:
replace_axon_ref = f.read()
- assert acc[cell_json_dict['morphology']['replace_axon']] == \
- replace_axon_ref
+ assert (
+ acc[cell_json_dict["morphology"]["replace_axon"]] == replace_axon_ref
+ )
def make_cell(replace_axon):
- morph_filename = testdata_dir / 'simple_ax2.swc'
- morph = ephys.morphologies.NrnFileMorphology(morph_filename,
- do_replace_axon=replace_axon)
+ morph_filename = testdata_dir / "simple_ax2.swc"
+ morph = ephys.morphologies.NrnFileMorphology(
+ morph_filename, do_replace_axon=replace_axon
+ )
somatic_loc = ephys.locations.NrnSeclistLocation(
- 'somatic', seclist_name='somatic')
- mechs = [ephys.mechanisms.NrnMODMechanism(
- name='hh', suffix='hh', locations=[somatic_loc])]
- gkbar_hh_scaler = '(-0.62109375 + 0.546875*math.log(' \
- '({distance})*0.421875 + 1.25))*{value}'
+ "somatic", seclist_name="somatic"
+ )
+ mechs = [
+ ephys.mechanisms.NrnMODMechanism(
+ name="hh", suffix="hh", locations=[somatic_loc]
+ )
+ ]
+ gkbar_hh_scaler = (
+ "(-0.62109375 + 0.546875*math.log("
+ "({distance})*0.421875 + 1.25))*{value}"
+ )
params = [
ephys.parameters.NrnSectionParameter(
- name='gnabar_hh',
- param_name='gnabar_hh',
- locations=[somatic_loc]),
+ name="gnabar_hh", param_name="gnabar_hh", locations=[somatic_loc]
+ ),
ephys.parameters.NrnRangeParameter(
- name='gkbar_hh',
- param_name='gkbar_hh',
+ name="gkbar_hh",
+ param_name="gkbar_hh",
value_scaler=ephys.parameterscalers.NrnSegmentSomaDistanceScaler(
- distribution=gkbar_hh_scaler),
- locations=[somatic_loc])]
+ distribution=gkbar_hh_scaler
+ ),
+ locations=[somatic_loc],
+ ),
+ ]
return ephys.models.CellModel(
- 'simple_ax2',
- morph=morph,
- mechs=mechs,
- params=params)
+ "simple_ax2", morph=morph, mechs=mechs, params=params
+ )
def run_short_sim(cable_cell):
@@ -487,9 +532,9 @@ def run_short_sim(cable_cell):
arb_cell_model = arbor.single_cell_model(cable_cell)
arb_cell_model.properties.catalogue = arbor.catalogue()
arb_cell_model.properties.catalogue.extend(
- arbor.default_catalogue(), "default::")
- arb_cell_model.properties.catalogue.extend(
- arbor.bbp_catalogue(), "BBP::")
+ arbor.default_catalogue(), "default::"
+ )
+ arb_cell_model.properties.catalogue.extend(arbor.bbp_catalogue(), "BBP::")
# Run a very short simulation to test mechanism instantiation
arb_cell_model.run(tfinal=0.1)
@@ -499,26 +544,29 @@ def run_short_sim(cable_cell):
def test_cell_model_write_and_read_acc():
"""ephys.create_acc: Test write_acc and read_acc w/o axon replacement"""
cell = make_cell(replace_axon=False)
- param_values = {'gnabar_hh': 0.1,
- 'gkbar_hh': 0.03}
+ param_values = {"gnabar_hh": 0.1, "gkbar_hh": 0.03}
with tempfile.TemporaryDirectory() as acc_dir:
cell.write_acc(acc_dir, param_values)
- cell_json, arb_morph, arb_decor, arb_labels = \
- create_acc.read_acc(
- pathlib.Path(acc_dir).joinpath(cell.name + '.json'))
- assert 'replace_axon' not in cell_json['morphology']
-
- cable_cell = arbor.cable_cell(morphology=arb_morph,
- decor=arb_decor,
- labels=arb_labels)
+ cell_json, arb_morph, arb_decor, arb_labels = create_acc.read_acc(
+ pathlib.Path(acc_dir).joinpath(cell.name + ".json")
+ )
+ assert "replace_axon" not in cell_json["morphology"]
+
+ cable_cell = arbor.cable_cell(
+ morphology=arb_morph, decor=arb_decor, labels=arb_labels
+ )
assert isinstance(cable_cell, arbor.cable_cell)
assert len(cable_cell.cables('"soma"')) == 1
assert len(cable_cell.cables('"axon"')) == 1
- assert len(arb_morph.branch_segments(
- cable_cell.cables('"soma"')[0].branch)) == 5
- assert len(arb_morph.branch_segments(
- cable_cell.cables('"axon"')[0].branch)) == 5
+ assert (
+ len(arb_morph.branch_segments(cable_cell.cables('"soma"')[0].branch))
+ == 5
+ )
+ assert (
+ len(arb_morph.branch_segments(cable_cell.cables('"axon"')[0].branch))
+ == 5
+ )
run_short_sim(cable_cell)
@@ -527,40 +575,50 @@ def test_cell_model_write_and_read_acc():
def test_cell_model_write_and_read_acc_replace_axon():
"""ephys.create_acc: Test write_acc and read_acc w/ axon replacement"""
cell = make_cell(replace_axon=True)
- param_values = {'gnabar_hh': 0.1,
- 'gkbar_hh': 0.03}
+ param_values = {"gnabar_hh": 0.1, "gkbar_hh": 0.03}
with tempfile.TemporaryDirectory() as acc_dir:
try:
nrn_sim = ephys.simulators.NrnSimulator()
- cell.write_acc(acc_dir, param_values,
- sim=nrn_sim)
+ cell.write_acc(acc_dir, param_values, sim=nrn_sim)
except Exception as e: # fail with an older Arbor version
assert isinstance(e, NotImplementedError)
- assert len(e.args) == 1 and e.args[0] == \
- "Need a newer version of Arbor for axon replacement."
+ assert (
+ len(e.args) == 1
+ and e.args[0]
+ == "Need a newer version of Arbor for axon replacement."
+ )
return
# Axon replacement implemented in installed Arbor version
- cell_json, arb_morph, arb_decor, arb_labels = \
- create_acc.read_acc(
- pathlib.Path(acc_dir).joinpath(cell.name + '.json'))
-
- assert 'replace_axon' in cell_json['morphology']
- cable_cell = arbor.cable_cell(morphology=arb_morph,
- decor=arb_decor,
- labels=arb_labels)
+ cell_json, arb_morph, arb_decor, arb_labels = create_acc.read_acc(
+ pathlib.Path(acc_dir).joinpath(cell.name + ".json")
+ )
+
+ assert "replace_axon" in cell_json["morphology"]
+ cable_cell = arbor.cable_cell(
+ morphology=arb_morph, decor=arb_decor, labels=arb_labels
+ )
assert isinstance(cable_cell, arbor.cable_cell)
assert len(cable_cell.cables('"soma"')) == 1
assert len(cable_cell.cables('"axon"')) == 1
- assert len(arb_morph.branch_segments(
- cable_cell.cables('"soma"')[0].branch)) == 6
- assert len(arb_morph.branch_segments(
- cable_cell.cables('"axon"')[0].branch)) == 6
- assert cable_cell.cables('"soma"')[0].prox == 0.
- assert abs(cable_cell.cables('"soma"')[0].dist -
- cable_cell.cables('"axon"')[0].prox) < 1e-6
- assert cable_cell.cables('"axon"')[0].dist == 1.
+ assert (
+ len(arb_morph.branch_segments(cable_cell.cables('"soma"')[0].branch))
+ == 6
+ )
+ assert (
+ len(arb_morph.branch_segments(cable_cell.cables('"axon"')[0].branch))
+ == 6
+ )
+ assert cable_cell.cables('"soma"')[0].prox == 0.0
+ assert (
+ abs(
+ cable_cell.cables('"soma"')[0].dist
+ - cable_cell.cables('"axon"')[0].prox
+ )
+ < 1e-6
+ )
+ assert cable_cell.cables('"axon"')[0].dist == 1.0
run_short_sim(cable_cell)
@@ -569,37 +627,37 @@ def test_cell_model_write_and_read_acc_replace_axon():
def test_cell_model_create_acc_replace_axon_without_instantiate():
"""ephys.create_acc: Test write_acc and read_acc w/ axon replacement"""
cell = make_cell(replace_axon=True)
- param_values = {'gnabar_hh': 0.1,
- 'gkbar_hh': 0.03}
-
- with pytest.raises(ValueError,
- match='Need an instance of NrnSimulator in sim'
- ' to instantiate morphology in order to'
- ' create JSON/ACC-description with'
- ' axon replacement.'):
+ param_values = {"gnabar_hh": 0.1, "gkbar_hh": 0.03}
+
+ with pytest.raises(
+ ValueError,
+ match="Need an instance of NrnSimulator in sim"
+ " to instantiate morphology in order to"
+ " create JSON/ACC-description with"
+ " axon replacement.",
+ ):
cell.create_acc(param_values)
def check_acc_dir(test_dir, ref_dir):
- assert os.listdir(ref_dir) == os.listdir(test_dir)
+ assert sorted(os.listdir(ref_dir)) == sorted(os.listdir(test_dir))
- ref_dir_ver_suffix = '_py' + ''.join(sys.version.split('.')[:2])
+ ref_dir_ver_suffix = "_py" + "".join(sys.version.split(".")[:2])
ref_dir_ver = ref_dir.parent / (ref_dir.name + ref_dir_ver_suffix)
for file in os.listdir(ref_dir):
-
if (ref_dir_ver / file).exists():
ref_dir_file = ref_dir_ver
else:
ref_dir_file = ref_dir
- if file.endswith('.json'):
+ if file.endswith(".json"):
with open(os.path.join(test_dir, file)) as f:
cell_json_dict = json.load(f)
with open(ref_dir_file / file) as f:
ref_cell_json = json.load(f)
for k in ref_cell_json:
- if k != 'produced_by':
+ if k != "produced_by":
assert ref_cell_json[k] == cell_json_dict[k]
else:
with open(os.path.join(test_dir, file)) as f:
@@ -611,17 +669,21 @@ def check_acc_dir(test_dir, ref_dir):
@pytest.mark.unit
def test_write_acc_simple():
- SIMPLECELL_PATH = str((pathlib.Path(__file__).parent /
- '../../../examples/simplecell').resolve())
+ SIMPLECELL_PATH = str(
+ (
+ pathlib.Path(__file__).parent / "../../../examples/simplecell"
+ ).resolve()
+ )
sys.path.insert(0, SIMPLECELL_PATH)
- ref_dir = (testdata_dir / 'acc/simplecell').resolve()
+ ref_dir = (testdata_dir / "acc/simplecell").resolve()
old_cwd = os.getcwd()
try:
os.chdir(SIMPLECELL_PATH)
import simplecell_model
+
param_values = {
- 'gnabar_hh': 0.10299326453483033,
- 'gkbar_hh': 0.027124836082684685
+ "gnabar_hh": 0.10299326453483033,
+ "gkbar_hh": 0.027124836082684685,
}
cell = simplecell_model.create(do_replace_axon=True)
@@ -629,16 +691,20 @@ def test_write_acc_simple():
cell.instantiate_morphology_3d(nrn_sim)
with tempfile.TemporaryDirectory() as test_dir:
- cell.write_acc(test_dir,
- param_values,
- # ext_catalogues=ext_catalogues,
- create_mod_morph=True)
+ cell.write_acc(
+ test_dir,
+ param_values,
+ # ext_catalogues=ext_catalogues,
+ create_mod_morph=True,
+ )
check_acc_dir(test_dir, ref_dir)
- except Exception as e: # fail with an older Arbor version
- assert isinstance(e, NotImplementedError)
- assert len(e.args) == 1 and e.args[0] == \
- "Need a newer version of Arbor for axon replacement."
+ except NotImplementedError as e: # fail with an older Arbor version
+ assert (
+ len(e.args) == 1
+ and e.args[0]
+ == "Need a newer version of Arbor for axon replacement."
+ )
finally:
cell.destroy(nrn_sim)
os.chdir(old_cwd)
@@ -647,34 +713,36 @@ def test_write_acc_simple():
@pytest.mark.unit
def test_write_acc_l5pc():
- L5PC_PATH = str((pathlib.Path(__file__).parent /
- '../../../examples/l5pc').resolve())
+ L5PC_PATH = str(
+ (pathlib.Path(__file__).parent / "../../../examples/l5pc").resolve()
+ )
sys.path.insert(0, L5PC_PATH)
- ref_dir = (testdata_dir / 'acc/l5pc').resolve()
+ ref_dir = (testdata_dir / "acc/l5pc").resolve()
old_cwd = os.getcwd()
try:
import l5pc_model
+
param_values = {
- 'gNaTs2_tbar_NaTs2_t.apical': 0.026145,
- 'gSKv3_1bar_SKv3_1.apical': 0.004226,
- 'gImbar_Im.apical': 0.000143,
- 'gNaTa_tbar_NaTa_t.axonal': 3.137968,
- 'gK_Tstbar_K_Tst.axonal': 0.089259,
- 'gamma_CaDynamics_E2.axonal': 0.002910,
- 'gNap_Et2bar_Nap_Et2.axonal': 0.006827,
- 'gSK_E2bar_SK_E2.axonal': 0.007104,
- 'gCa_HVAbar_Ca_HVA.axonal': 0.000990,
- 'gK_Pstbar_K_Pst.axonal': 0.973538,
- 'gSKv3_1bar_SKv3_1.axonal': 1.021945,
- 'decay_CaDynamics_E2.axonal': 287.198731,
- 'gCa_LVAstbar_Ca_LVAst.axonal': 0.008752,
- 'gamma_CaDynamics_E2.somatic': 0.000609,
- 'gSKv3_1bar_SKv3_1.somatic': 0.303472,
- 'gSK_E2bar_SK_E2.somatic': 0.008407,
- 'gCa_HVAbar_Ca_HVA.somatic': 0.000994,
- 'gNaTs2_tbar_NaTs2_t.somatic': 0.983955,
- 'decay_CaDynamics_E2.somatic': 210.485284,
- 'gCa_LVAstbar_Ca_LVAst.somatic': 0.000333,
+ "gNaTs2_tbar_NaTs2_t.apical": 0.026145,
+ "gSKv3_1bar_SKv3_1.apical": 0.004226,
+ "gImbar_Im.apical": 0.000143,
+ "gNaTa_tbar_NaTa_t.axonal": 3.137968,
+ "gK_Tstbar_K_Tst.axonal": 0.089259,
+ "gamma_CaDynamics_E2.axonal": 0.002910,
+ "gNap_Et2bar_Nap_Et2.axonal": 0.006827,
+ "gSK_E2bar_SK_E2.axonal": 0.007104,
+ "gCa_HVAbar_Ca_HVA.axonal": 0.000990,
+ "gK_Pstbar_K_Pst.axonal": 0.973538,
+ "gSKv3_1bar_SKv3_1.axonal": 1.021945,
+ "decay_CaDynamics_E2.axonal": 287.198731,
+ "gCa_LVAstbar_Ca_LVAst.axonal": 0.008752,
+ "gamma_CaDynamics_E2.somatic": 0.000609,
+ "gSKv3_1bar_SKv3_1.somatic": 0.303472,
+ "gSK_E2bar_SK_E2.somatic": 0.008407,
+ "gCa_HVAbar_Ca_HVA.somatic": 0.000994,
+ "gNaTs2_tbar_NaTs2_t.somatic": 0.983955,
+ "decay_CaDynamics_E2.somatic": 210.485284,
+ "gCa_LVAstbar_Ca_LVAst.somatic": 0.000333,
}
cell = l5pc_model.create(do_replace_axon=True)
@@ -682,16 +750,21 @@ def test_write_acc_l5pc():
cell.instantiate_morphology_3d(nrn_sim)
with tempfile.TemporaryDirectory() as test_dir:
- cell.write_acc(test_dir,
- param_values,
- # ext_catalogues=ext_catalogues,
- create_mod_morph=True)
+ cell.write_acc(
+ test_dir,
+ param_values,
+ # ext_catalogues=ext_catalogues,
+ create_mod_morph=True,
+ )
check_acc_dir(test_dir, ref_dir)
except Exception as e: # fail with an older Arbor version
assert isinstance(e, NotImplementedError)
- assert len(e.args) == 1 and e.args[0] == \
- "Need a newer version of Arbor for axon replacement."
+ assert (
+ len(e.args) == 1
+ and e.args[0]
+ == "Need a newer version of Arbor for axon replacement."
+ )
finally:
cell.destroy(nrn_sim)
os.chdir(old_cwd)
@@ -700,22 +773,26 @@ def test_write_acc_l5pc():
@pytest.mark.unit
def test_write_acc_expsyn():
- EXPSYN_PATH = str((pathlib.Path(__file__).parent /
- '../../../examples/expsyn').resolve())
+ EXPSYN_PATH = str(
+ (pathlib.Path(__file__).parent / "../../../examples/expsyn").resolve()
+ )
sys.path.insert(0, EXPSYN_PATH)
- ref_dir = (testdata_dir / 'acc/expsyn').resolve()
+ ref_dir = (testdata_dir / "acc/expsyn").resolve()
old_cwd = os.getcwd()
try:
import expsyn
- param_values = {'expsyn_tau': 10.0}
- cell = expsyn.create_model(sim='arb', do_replace_axon=False)
+ param_values = {"expsyn_tau": 10.0}
+
+ cell = expsyn.create_model(sim="arb", do_replace_axon=False)
with tempfile.TemporaryDirectory() as test_dir:
- cell.write_acc(test_dir,
- param_values,
- # ext_catalogues=ext_catalogues,
- create_mod_morph=True)
+ cell.write_acc(
+ test_dir,
+ param_values,
+ # ext_catalogues=ext_catalogues,
+ create_mod_morph=True,
+ )
check_acc_dir(test_dir, ref_dir)
finally:
diff --git a/bluepyopt/tests/test_ephys/test_create_hoc.py b/bluepyopt/tests/test_ephys/test_create_hoc.py
index 4666d4e3..19153a52 100644
--- a/bluepyopt/tests/test_ephys/test_create_hoc.py
+++ b/bluepyopt/tests/test_ephys/test_create_hoc.py
@@ -5,7 +5,9 @@
import os
from bluepyopt.ephys.acc import ArbLabel
+from bluepyopt.ephys.locations import NrnSomaDistanceCompLocation
from bluepyopt.ephys.parameterscalers import NrnSegmentSomaDistanceScaler
+from bluepyopt.ephys.parameterscalers import NrnSegmentSomaDistanceStepScaler
from . import utils
from bluepyopt.ephys import create_acc, create_hoc
@@ -151,3 +153,33 @@ def test_range_exprs_to_hoc():
assert hoc[0].param_name == 'gkbar_hh'
val_gt = '(-0.8696 + 2.087*exp((%.17g)*0.0031))*0.025000000000000001'
assert hoc[0].value == val_gt
+
+
+@pytest.mark.unit
+def test_range_exprs_to_hoc_step_scaler():
+ """ephys.create_hoc: Test range_exprs_to_hoc with step scaler"""
+ # apical_region = ArbLabel("region", "apic", "(tag 4)")
+ apical_location = NrnSomaDistanceCompLocation(
+ name='apic100',
+ soma_distance=100,
+ seclist_name='apical',
+ )
+ param_scaler = NrnSegmentSomaDistanceStepScaler(
+ name='soma-distance-step-scaler',
+ distribution='{value} * (0.1 + 0.9 * int('
+ '({distance} > {step_begin}) & ('
+ '{distance} < {step_end})))',
+ step_begin=300,
+ step_end=500)
+
+ range_expr = create_hoc.RangeExpr(
+ location=apical_location,
+ name="gCa_LVAstbar_Ca_LVAst",
+ value=1,
+ value_scaler=param_scaler
+ )
+
+ hoc = create_hoc.range_exprs_to_hoc([range_expr])
+ assert hoc[0].param_name == 'gCa_LVAstbar_Ca_LVAst'
+ val_gt = '1 * (0.1 + 0.9 * int((%.17g > 300) && (%.17g < 500)))'
+ assert hoc[0].value == val_gt
diff --git a/bluepyopt/tests/test_ephys/test_locations.py b/bluepyopt/tests/test_ephys/test_locations.py
index 28868742..cef6c78b 100644
--- a/bluepyopt/tests/test_ephys/test_locations.py
+++ b/bluepyopt/tests/test_ephys/test_locations.py
@@ -21,8 +21,8 @@
# pylint:disable=W0612, W0201
import json
-import numpy as np
+import numpy as np
import pytest
from bluepyopt import ephys
@@ -33,27 +33,24 @@
def test_location_init():
"""ephys.locations: test if Location works"""
- loc = ephys.locations.Location('test')
+ loc = ephys.locations.Location("test")
assert isinstance(loc, ephys.locations.Location)
- assert loc.name == 'test'
+ assert loc.name == "test"
@pytest.mark.unit
class TestNrnSectionCompLocation(object):
-
"""Test class for NrnSectionCompLocation"""
- def setup(self):
+ def setup_method(self):
"""Setup"""
self.loc = ephys.locations.NrnSectionCompLocation(
- name='test',
- sec_name='soma[0]',
- comp_x=0.5)
+ name="test", sec_name="soma[0]", comp_x=0.5
+ )
self.loc_dend = ephys.locations.NrnSectionCompLocation(
- name='test',
- sec_name='dend[1]',
- comp_x=0.5)
- assert self.loc.name == 'test'
+ name="test", sec_name="dend[1]", comp_x=0.5
+ )
+ assert self.loc.name == "test"
self.sim = ephys.simulators.NrnSimulator()
def test_instantiate(self):
@@ -61,13 +58,14 @@ def test_instantiate(self):
# Create a little test class with a soma and two dendritic sections
class Cell(object):
-
"""Cell class"""
+
pass
+
cell = Cell()
soma = self.sim.neuron.h.Section()
- dend1 = self.sim.neuron.h.Section(name='dend1')
- dend2 = self.sim.neuron.h.Section(name='dend2')
+ dend1 = self.sim.neuron.h.Section(name="dend1")
+ dend2 = self.sim.neuron.h.Section(name="dend2")
cell.soma = [soma]
cell.dend = [dend1, dend2]
@@ -81,22 +79,17 @@ class Cell(object):
@pytest.mark.unit
class TestNrnSeclistCompLocation(object):
-
"""Test class for NrnSectionCompLocation"""
- def setup(self):
+ def setup_method(self):
"""Setup"""
self.loc = ephys.locations.NrnSeclistCompLocation(
- name='test',
- seclist_name='somatic',
- sec_index=0,
- comp_x=0.5)
+ name="test", seclist_name="somatic", sec_index=0, comp_x=0.5
+ )
self.loc_dend = ephys.locations.NrnSeclistCompLocation(
- name='test',
- seclist_name='basal',
- sec_index=1,
- comp_x=0.5)
- assert self.loc.name == 'test'
+ name="test", seclist_name="basal", sec_index=1, comp_x=0.5
+ )
+ assert self.loc.name == "test"
self.sim = ephys.simulators.NrnSimulator()
def test_instantiate(self):
@@ -105,12 +98,13 @@ def test_instantiate(self):
# Create a little test class with a soma and two dendritic sections
class Cell(object):
"""Cell class"""
+
pass
cell = Cell()
soma = self.sim.neuron.h.Section()
- dend1 = self.sim.neuron.h.Section(name='dend1')
- dend2 = self.sim.neuron.h.Section(name='dend2')
+ dend1 = self.sim.neuron.h.Section(name="dend1")
+ dend2 = self.sim.neuron.h.Section(name="dend2")
cell.somatic = self.sim.neuron.h.SectionList()
cell.somatic.append(soma)
@@ -130,20 +124,17 @@ class Cell(object):
@pytest.mark.unit
class TestNrnSeclistSecLocation(object):
-
"""Test class for NrnSeclistSecLocation"""
- def setup(self):
+ def setup_method(self):
"""Setup"""
self.loc = ephys.locations.NrnSeclistSecLocation(
- name='test',
- seclist_name='somatic',
- sec_index=0)
+ name="test", seclist_name="somatic", sec_index=0
+ )
self.loc_dend = ephys.locations.NrnSeclistSecLocation(
- name='test',
- seclist_name='basal',
- sec_index=1)
- assert self.loc.name == 'test'
+ name="test", seclist_name="basal", sec_index=1
+ )
+ assert self.loc.name == "test"
self.sim = ephys.simulators.NrnSimulator()
def test_instantiate(self):
@@ -152,12 +143,13 @@ def test_instantiate(self):
# Create a little test class with a soma and two dendritic sections
class Cell(object):
"""Cell class"""
+
pass
cell = Cell()
soma = self.sim.neuron.h.Section()
- dend1 = self.sim.neuron.h.Section(name='dend1')
- dend2 = self.sim.neuron.h.Section(name='dend2')
+ dend1 = self.sim.neuron.h.Section(name="dend1")
+ dend2 = self.sim.neuron.h.Section(name="dend2")
cell.somatic = self.sim.neuron.h.SectionList()
cell.somatic.append(soma)
@@ -174,16 +166,14 @@ class Cell(object):
@pytest.mark.unit
class TestNrnSomaDistanceCompLocation(object):
-
"""Test class for NrnSomaDistanceCompLocation"""
- def setup(self):
+ def setup_method(self):
"""Setup"""
self.loc = ephys.locations.NrnSomaDistanceCompLocation(
- 'test',
- 125,
- 'testdend')
- assert self.loc.name == 'test'
+ "test", 125, "testdend"
+ )
+ assert self.loc.name == "test"
self.sim = ephys.simulators.NrnSimulator()
def test_instantiate(self):
@@ -191,23 +181,26 @@ def test_instantiate(self):
# Create a little test class with a soma and two dendritic sections
class Cell(object):
-
"""Cell class"""
+
pass
+
cell = Cell()
soma = self.sim.neuron.h.Section()
cell.soma = [soma]
cell.testdend = self.sim.neuron.h.SectionList()
- dend1 = self.sim.neuron.h.Section(name='dend1')
- dend2 = self.sim.neuron.h.Section(name='dend2')
+ dend1 = self.sim.neuron.h.Section(name="dend1")
+ dend2 = self.sim.neuron.h.Section(name="dend2")
cell.testdend.append(sec=dend1)
cell.testdend.append(sec=dend2)
- pytest.raises(ephys.locations.EPhysLocInstantiateException,
- self.loc.instantiate,
- sim=self.sim,
- icell=cell)
+ pytest.raises(
+ ephys.locations.EPhysLocInstantiateException,
+ self.loc.instantiate,
+ sim=self.sim,
+ icell=cell,
+ )
dend1.connect(soma(0.5), 0.0)
dend2.connect(dend1(1.0), 0.0)
@@ -218,22 +211,17 @@ class Cell(object):
@pytest.mark.unit
class TestNrnSecSomaDistanceCompLocation(object):
-
"""Test class for NrnSecSomaDistanceCompLocation"""
- def setup(self):
+ def setup_method(self):
"""Setup"""
self.loc = ephys.locations.NrnSecSomaDistanceCompLocation(
- 'test',
- 125,
- 1,
- 'testdend')
+ "test", 125, 1, "testdend"
+ )
self.loc_other = ephys.locations.NrnSecSomaDistanceCompLocation(
- 'test',
- 250,
- 4,
- 'testdend')
- assert self.loc.name == 'test'
+ "test", 250, 4, "testdend"
+ )
+ assert self.loc.name == "test"
self.sim = ephys.simulators.NrnSimulator()
def test_instantiate(self):
@@ -241,18 +229,19 @@ def test_instantiate(self):
# Create a little test class with a soma and two dendritic sections
class Cell(object):
-
"""Cell class"""
+
pass
+
cell = Cell()
soma = self.sim.neuron.h.Section(name="soma[0]")
cell.soma = [soma]
cell.testdend = self.sim.neuron.h.SectionList()
- dend1 = self.sim.neuron.h.Section(name='dend[0]')
- dend2 = self.sim.neuron.h.Section(name='dend[1]')
- dend3 = self.sim.neuron.h.Section(name='dend[2]')
- dend4 = self.sim.neuron.h.Section(name='dend[3]')
- dend5 = self.sim.neuron.h.Section(name='dend[4]')
+ dend1 = self.sim.neuron.h.Section(name="dend[0]")
+ dend2 = self.sim.neuron.h.Section(name="dend[1]")
+ dend3 = self.sim.neuron.h.Section(name="dend[2]")
+ dend4 = self.sim.neuron.h.Section(name="dend[3]")
+ dend5 = self.sim.neuron.h.Section(name="dend[4]")
cell.testdend.append(sec=dend1)
cell.testdend.append(sec=dend2)
@@ -274,21 +263,18 @@ class Cell(object):
@pytest.mark.unit
class TestNrnTrunkSomaDistanceCompLocation(object):
-
"""Test class for NrnTrunkSomaDistanceCompLocation"""
- def setup(self):
+ def setup_method(self):
"""Setup"""
self.loc = ephys.locations.NrnTrunkSomaDistanceCompLocation(
- 'test',
- soma_distance=150,
- seclist_name='testdend')
+ "test", soma_distance=150, seclist_name="testdend"
+ )
self.loc_other = ephys.locations.NrnTrunkSomaDistanceCompLocation(
- 'test',
- soma_distance=350,
- seclist_name='testdend')
+ "test", soma_distance=350, seclist_name="testdend"
+ )
- assert self.loc.name == 'test'
+ assert self.loc.name == "test"
self.sim = ephys.simulators.NrnSimulator()
def test_instantiate(self):
@@ -296,18 +282,19 @@ def test_instantiate(self):
# Create a little test class with a soma and two dendritic sections
class Cell(object):
-
"""Cell class"""
+
pass
+
cell = Cell()
soma = self.sim.neuron.h.Section(name="soma[0]")
cell.soma = [soma]
cell.testdend = self.sim.neuron.h.SectionList()
- dend1 = self.sim.neuron.h.Section(name='dend[0]')
- dend2 = self.sim.neuron.h.Section(name='dend[1]')
- dend3 = self.sim.neuron.h.Section(name='dend[2]')
- dend4 = self.sim.neuron.h.Section(name='dend[3]')
- dend5 = self.sim.neuron.h.Section(name='dend[4]')
+ dend1 = self.sim.neuron.h.Section(name="dend[0]")
+ dend2 = self.sim.neuron.h.Section(name="dend[1]")
+ dend3 = self.sim.neuron.h.Section(name="dend[2]")
+ dend4 = self.sim.neuron.h.Section(name="dend[3]")
+ dend5 = self.sim.neuron.h.Section(name="dend[4]")
cell.testdend.append(sec=dend1)
cell.testdend.append(sec=dend2)
@@ -348,18 +335,22 @@ def test_serialize():
NrnSeclistCompLocation,
NrnSeclistLocation,
NrnSeclistSecLocation,
- NrnSomaDistanceCompLocation)
+ NrnSomaDistanceCompLocation,
+ )
- seclist_name, sec_index, comp_x, soma_distance = 'somatic', 0, 0.5, 800
+ seclist_name, sec_index, comp_x, soma_distance = "somatic", 0, 0.5, 800
locations = (
- NrnSeclistCompLocation('NrnSeclistCompLocation',
- seclist_name, sec_index, comp_x),
- NrnSeclistLocation(
- 'NrnSeclistLocation', seclist_name),
+ NrnSeclistCompLocation(
+ "NrnSeclistCompLocation", seclist_name, sec_index, comp_x
+ ),
+ NrnSeclistLocation("NrnSeclistLocation", seclist_name),
NrnSeclistSecLocation(
- 'NrnSeclistSecLocation', seclist_name, sec_index),
+ "NrnSeclistSecLocation", seclist_name, sec_index
+ ),
NrnSomaDistanceCompLocation(
- 'NrnSomaDistanceCompLocation', soma_distance, seclist_name),)
+ "NrnSomaDistanceCompLocation", soma_distance, seclist_name
+ ),
+ )
for loc in locations:
serialized = loc.to_dict()
diff --git a/bluepyopt/tests/test_ephys/test_parameterscalers.py b/bluepyopt/tests/test_ephys/test_parameterscalers.py
index 7d0380e9..846bf023 100644
--- a/bluepyopt/tests/test_ephys/test_parameterscalers.py
+++ b/bluepyopt/tests/test_ephys/test_parameterscalers.py
@@ -51,6 +51,22 @@ def test_NrnSegmentSectionDistanceScaler_eval_dist_with_dict():
== '0.5 + (1 - (abs(10 - 8) / 4)) * 1')
+@pytest.mark.unit
+def test_NrnSegmentSomaDistanceStepScaler_eval_dist_with_dict():
+ """ephys.parameterscalers: eval_dist of NrnSegmentSomaDistanceStepScaler"""
+
+ dist = '{value} * (0.1 + 0.9 * int(' \
+ '({distance} > {step_begin}) & ({distance} < {step_end})))'
+
+ scaler = ephys.parameterscalers.NrnSegmentSomaDistanceStepScaler(
+ distribution=dist, step_begin=300, step_end=500)
+
+ _values = {'value': 1}
+
+ assert (scaler.eval_dist(values=_values, distance=10)
+ == '1 * (0.1 + 0.9 * int((10 > 300) & (10 < 500)))')
+
+
@pytest.mark.unit
def test_serialize():
"""ephys.parameterscalers: test serialization"""
diff --git a/bluepyopt/tests/test_ephys/test_simulators.py b/bluepyopt/tests/test_ephys/test_simulators.py
index f35fd006..4e4203f5 100644
--- a/bluepyopt/tests/test_ephys/test_simulators.py
+++ b/bluepyopt/tests/test_ephys/test_simulators.py
@@ -22,13 +22,12 @@
# pylint:disable=W0612
import os
+import pathlib
import types
-
-import pytest
-import numpy
-
from unittest import mock
+
import numpy
+import pytest
import bluepyopt.ephys as ephys
import bluepyopt.ephys.examples as examples
@@ -46,7 +45,7 @@ def test_nrnsimulator_init():
def test_nrnsimulator_init_windows():
"""ephys.simulators: test if NrnSimulator constructor works on Windows"""
- with mock.patch('platform.system', mock.MagicMock(return_value="Windows")):
+ with mock.patch("platform.system", mock.MagicMock(return_value="Windows")):
neuron_sim = ephys.simulators.NrnSimulator()
assert isinstance(neuron_sim, ephys.simulators.NrnSimulator)
assert not neuron_sim.disable_banner
@@ -69,9 +68,9 @@ def test_nrnsimulator_cvode_minstep():
# Check default minstep before and after run
neuron_sim = ephys.simulators.NrnSimulator(cvode_minstep=0.01)
- assert neuron_sim.cvode.minstep() == 0.
+ assert neuron_sim.cvode.minstep() == 0.0
neuron_sim.run(tstop=10)
- assert neuron_sim.cvode.minstep() == 0.
+ assert neuron_sim.cvode.minstep() == 0.0
# Check with that minstep is set back to the original value after run
neuron_sim = ephys.simulators.NrnSimulator(cvode_minstep=0.0)
@@ -82,20 +81,26 @@ def test_nrnsimulator_cvode_minstep():
# Check that the minstep is effective
cvode_minstep = 0.012
- params = {'gnabar_hh': 0.10299326453483033,
- 'gkbar_hh': 0.027124836082684685}
+ params = {
+ "gnabar_hh": 0.10299326453483033,
+ "gkbar_hh": 0.027124836082684685,
+ }
simplecell = examples.simplecell.SimpleCell()
evaluator = simplecell.cell_evaluator
evaluator.cell_model.unfreeze(params.keys())
evaluator.sim = ephys.simulators.NrnSimulator(cvode_minstep=cvode_minstep)
responses = evaluator.run_protocols(
- protocols=evaluator.fitness_protocols.values(),
- param_values=params)
+ protocols=evaluator.fitness_protocols.values(), param_values=params
+ )
ton = list(evaluator.fitness_protocols.values())[0].stimuli[0].step_delay
- toff = ton + list(evaluator.fitness_protocols.values())[0].stimuli[
- 0].step_duration
- t_series = numpy.array(responses['Step1.soma.v']['time'])
- t_series = t_series[((ton + 1.) < t_series) & (t_series < (toff - 1.))]
+ toff = (
+ ton
+ + list(evaluator.fitness_protocols.values())[0]
+ .stimuli[0]
+ .step_duration
+ )
+ t_series = numpy.array(responses["Step1.soma.v"]["time"])
+ t_series = t_series[((ton + 1.0) < t_series) & (t_series < (toff - 1.0))]
min_dt = numpy.min(numpy.ediff1d(t_series))
assert (min_dt >= cvode_minstep) == 1
evaluator.cell_model.freeze(params)
@@ -105,6 +110,7 @@ def test_nrnsimulator_cvode_minstep():
def test_neuron_import():
"""ephys.simulators: test if bluepyopt.neuron import was successful"""
from bluepyopt import ephys # NOQA
+
neuron_sim = ephys.simulators.NrnSimulator()
assert isinstance(neuron_sim.neuron, types.ModuleType)
@@ -114,6 +120,7 @@ def test_nrnsim_run_dt_exception():
"""ephys.simulators: test if run return exception when dt was changed"""
from bluepyopt import ephys # NOQA
+
neuron_sim = ephys.simulators.NrnSimulator()
neuron_sim.neuron.h.dt = 1.0
pytest.raises(Exception, neuron_sim.run, 10, cvode_active=False)
@@ -124,18 +131,20 @@ def test_nrnsim_run_cvodeactive_dt_exception():
"""ephys.simulators: test if run return exception cvode and dt both used"""
from bluepyopt import ephys # NOQA
+
neuron_sim = ephys.simulators.NrnSimulator()
neuron_sim.neuron.h.dt = 1.0
pytest.raises(ValueError, neuron_sim.run, 10, dt=0.1, cvode_active=True)
@pytest.mark.unit
-@mock.patch('glob.glob')
+@mock.patch.object(pathlib.Path, "glob")
def test_disable_banner_exception(mock_glob):
"""ephys.simulators: test if disable_banner raises exception"""
mock_glob.return_value = []
import warnings
+
with warnings.catch_warnings(record=True) as warnings_record:
ephys.simulators.NrnSimulator._nrn_disable_banner()
assert len(warnings_record) == 1
@@ -154,7 +163,7 @@ def test_lfpysimulator_init():
def test_lfpyimulator_init_windows():
"""ephys.simulators: test if LFPySimulator constructor works on Windows"""
- with mock.patch('platform.system', mock.MagicMock(return_value="Windows")):
+ with mock.patch("platform.system", mock.MagicMock(return_value="Windows")):
empty_cell = ephys.models.LFPyCellModel(name="empty_cell")
neuron_sim = ephys.simulators.LFPySimulator()
assert isinstance(neuron_sim, ephys.simulators.LFPySimulator)
@@ -171,6 +180,7 @@ def test_lfpyimulator_init_windows():
def test__lfpysimulator_neuron_import():
"""ephys.simulators: test neuron import from LFPySimulator"""
from bluepyopt import ephys # NOQA
+
empty_cell = ephys.models.LFPyCellModel(name="empty_cell")
neuron_sim = ephys.simulators.LFPySimulator()
assert isinstance(neuron_sim.neuron, types.ModuleType)
@@ -181,10 +191,11 @@ def test_lfpysim_run_cvodeactive_dt_exception():
"""ephys.simulators: test if LFPySimulator run returns exception"""
from bluepyopt import ephys # NOQA
+
TESTDATA_DIR = os.path.join(
- os.path.dirname(os.path.abspath(__file__)), 'testdata'
+ os.path.dirname(os.path.abspath(__file__)), "testdata"
)
- simple_morphology_path = os.path.join(TESTDATA_DIR, 'simple.swc')
+ simple_morphology_path = os.path.join(TESTDATA_DIR, "simple.swc")
test_morph = ephys.morphologies.NrnFileMorphology(simple_morphology_path)
lfpy_cell = ephys.models.LFPyCellModel(
@@ -196,9 +207,9 @@ def test_lfpysim_run_cvodeactive_dt_exception():
with pytest.raises(
ValueError,
match=(
- 'NrnSimulator: '
- 'Impossible to combine the dt argument when '
- 'cvode_active is True in the NrnSimulator run method'
+ "NrnSimulator: "
+ "Impossible to combine the dt argument when "
+ "cvode_active is True in the NrnSimulator run method"
),
):
neuron_sim.run(
@@ -206,19 +217,20 @@ def test_lfpysim_run_cvodeactive_dt_exception():
dt=0.1,
cvode_active=True,
lfpy_cell=lfpy_cell.lfpy_cell,
- lfpy_electrode=lfpy_cell.lfpy_electrode
+ lfpy_electrode=lfpy_cell.lfpy_electrode,
)
lfpy_cell.destroy(sim=neuron_sim)
@pytest.mark.unit
-@mock.patch('glob.glob')
+@mock.patch.object(pathlib.Path, "glob")
def test_lfpysimulator_disable_banner_exception(mock_glob):
"""ephys.simulators: test LFPySimulator disable_banner raises exception"""
mock_glob.return_value = []
import warnings
+
with warnings.catch_warnings(record=True) as warnings_record:
ephys.simulators.LFPySimulator._nrn_disable_banner()
assert len(warnings_record) == 1
diff --git a/bluepyopt/tests/test_l5pc.py b/bluepyopt/tests/test_l5pc.py
index b6e3fc8a..ddc76883 100644
--- a/bluepyopt/tests/test_l5pc.py
+++ b/bluepyopt/tests/test_l5pc.py
@@ -3,8 +3,11 @@
import json
import os
import sys
-
from contextlib import contextmanager
+
+import bluepyopt
+from bluepyopt import ephys
+
if sys.version_info[0] < 3:
from StringIO import StringIO
else:
@@ -13,45 +16,43 @@
import pytest
-L5PC_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__),
- '../../examples/l5pc'))
+L5PC_PATH = os.path.abspath(
+ os.path.join(os.path.dirname(__file__), "../../examples/l5pc")
+)
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, L5PC_PATH)
-import bluepyopt
-from bluepyopt import ephys
neuron_sim = ephys.simulators.NrnSimulator()
neuron_sim.neuron.h.nrn_load_dll(
- os.path.join(
- L5PC_PATH,
- 'x86_64/.libs/libnrnmech.so'))
+ os.path.join(L5PC_PATH, "x86_64/.libs/libnrnmech.so")
+)
# Parameters in release circuit model
release_parameters = {
- 'gNaTs2_tbar_NaTs2_t.apical': 0.026145,
- 'gSKv3_1bar_SKv3_1.apical': 0.004226,
- 'gImbar_Im.apical': 0.000143,
- 'gNaTa_tbar_NaTa_t.axonal': 3.137968,
- 'gK_Tstbar_K_Tst.axonal': 0.089259,
- 'gamma_CaDynamics_E2.axonal': 0.002910,
- 'gNap_Et2bar_Nap_Et2.axonal': 0.006827,
- 'gSK_E2bar_SK_E2.axonal': 0.007104,
- 'gCa_HVAbar_Ca_HVA.axonal': 0.000990,
- 'gK_Pstbar_K_Pst.axonal': 0.973538,
- 'gSKv3_1bar_SKv3_1.axonal': 1.021945,
- 'decay_CaDynamics_E2.axonal': 287.198731,
- 'gCa_LVAstbar_Ca_LVAst.axonal': 0.008752,
- 'gamma_CaDynamics_E2.somatic': 0.000609,
- 'gSKv3_1bar_SKv3_1.somatic': 0.303472,
- 'gSK_E2bar_SK_E2.somatic': 0.008407,
- 'gCa_HVAbar_Ca_HVA.somatic': 0.000994,
- 'gNaTs2_tbar_NaTs2_t.somatic': 0.983955,
- 'decay_CaDynamics_E2.somatic': 210.485284,
- 'gCa_LVAstbar_Ca_LVAst.somatic': 0.000333
+ "gNaTs2_tbar_NaTs2_t.apical": 0.026145,
+ "gSKv3_1bar_SKv3_1.apical": 0.004226,
+ "gImbar_Im.apical": 0.000143,
+ "gNaTa_tbar_NaTa_t.axonal": 3.137968,
+ "gK_Tstbar_K_Tst.axonal": 0.089259,
+ "gamma_CaDynamics_E2.axonal": 0.002910,
+ "gNap_Et2bar_Nap_Et2.axonal": 0.006827,
+ "gSK_E2bar_SK_E2.axonal": 0.007104,
+ "gCa_HVAbar_Ca_HVA.axonal": 0.000990,
+ "gK_Pstbar_K_Pst.axonal": 0.973538,
+ "gSKv3_1bar_SKv3_1.axonal": 1.021945,
+ "decay_CaDynamics_E2.axonal": 287.198731,
+ "gCa_LVAstbar_Ca_LVAst.axonal": 0.008752,
+ "gamma_CaDynamics_E2.somatic": 0.000609,
+ "gSKv3_1bar_SKv3_1.somatic": 0.303472,
+ "gSK_E2bar_SK_E2.somatic": 0.008407,
+ "gCa_HVAbar_Ca_HVA.somatic": 0.000994,
+ "gNaTs2_tbar_NaTs2_t.somatic": 0.983955,
+ "decay_CaDynamics_E2.somatic": 210.485284,
+ "gCa_LVAstbar_Ca_LVAst.somatic": 0.000333,
}
@@ -65,8 +66,8 @@ def load_from_json(filename):
def dump_to_json(content, filename):
"""Dump structure to json"""
- with open(filename, 'w') as json_file:
- return json.dump(content, json_file, indent=4, separators=(',', ': '))
+ with open(filename, "w") as json_file:
+ return json.dump(content, json_file, indent=4, separators=(",", ": "))
def test_import():
@@ -78,17 +79,16 @@ def test_import():
class TestL5PCModel(object):
-
"""Test L5PC model"""
- def setup(self):
+ def setup_method(self):
"""Set up class"""
sys.path.insert(0, L5PC_PATH)
import l5pc_model # NOQA
+
self.l5pc_cell = l5pc_model.create()
- assert isinstance(self.l5pc_cell,
- bluepyopt.ephys.models.CellModel)
+ assert isinstance(self.l5pc_cell, bluepyopt.ephys.models.CellModel)
self.nrn = ephys.simulators.NrnSimulator()
def test_instantiate(self):
@@ -96,42 +96,45 @@ def test_instantiate(self):
self.l5pc_cell.freeze(release_parameters)
self.l5pc_cell.instantiate(sim=self.nrn)
- def teardown(self):
+ def teardown_method(self):
"""Teardown"""
self.l5pc_cell.destroy(sim=self.nrn)
class TestL5PCEvaluator(object):
-
"""Test L5PC evaluator"""
- def setup(self):
+ def setup_method(self):
"""Set up class"""
import l5pc_evaluator # NOQA
self.l5pc_evaluator = l5pc_evaluator.create()
- assert isinstance(self.l5pc_evaluator,
- bluepyopt.ephys.evaluators.CellEvaluator)
+ assert isinstance(
+ self.l5pc_evaluator, bluepyopt.ephys.evaluators.CellEvaluator
+ )
@pytest.mark.slow
def test_eval(self):
"""L5PC: test evaluation of l5pc evaluator"""
result = self.l5pc_evaluator.evaluate_with_dicts(
- param_dict=release_parameters)
+ param_dict=release_parameters
+ )
expected_results = load_from_json(
- os.path.join(SCRIPT_DIR, 'expected_results.json'))
+ os.path.join(SCRIPT_DIR, "expected_results.json")
+ )
# Use two lines below to update expected result
# expected_results['TestL5PCEvaluator.test_eval'] = result
# dump_to_json(expected_results, 'expected_results.json')
- assert set(result.keys(
- )) == set(expected_results['TestL5PCEvaluator.test_eval'].keys())
+ assert set(result.keys()) == set(
+ expected_results["TestL5PCEvaluator.test_eval"].keys()
+ )
- def teardown(self):
+ def teardown_method(self):
"""Teardown"""
pass
@@ -152,7 +155,8 @@ def stdout_redirector(stream):
def test_exec():
"""L5PC Notebook: test execution"""
import numpy
- numpy.seterr(all='raise')
+
+ numpy.seterr(all="raise")
old_cwd = os.getcwd()
output = StringIO()
try:
@@ -162,14 +166,14 @@ def test_exec():
# Probably because multiprocessing doesn't work correctly during
# import
if sys.version_info[0] < 3:
- execfile('L5PC.py') # NOQA
+ execfile("L5PC.py") # NOQA
else:
- with open('L5PC.py') as l5pc_file:
- exec(compile(l5pc_file.read(), 'L5PC.py', 'exec')) # NOQA
+ with open("L5PC.py") as l5pc_file:
+ exec(compile(l5pc_file.read(), "L5PC.py", "exec")) # NOQA
stdout = output.getvalue()
# first and last values of optimal individual
- assert '0.001017834439738432' in stdout
- assert '202.18814057682334' in stdout
+ assert "0.001017834439738432" in stdout
+ assert "202.18814057682334" in stdout
assert "'gamma_CaDynamics_E2.somatic': 0.03229357096515606" in stdout
finally:
os.chdir(old_cwd)
@@ -180,7 +184,8 @@ def test_exec():
def test_l5pc_validate_neuron_arbor():
"""L5PC Neuron/Arbor validation Notebook: test execution"""
import numpy
- numpy.seterr(all='raise')
+
+ numpy.seterr(all="raise")
old_cwd = os.getcwd()
output = StringIO()
try:
@@ -190,19 +195,28 @@ def test_l5pc_validate_neuron_arbor():
# Probably because multiprocessing doesn't work correctly during
# import
if sys.version_info[0] < 3:
- execfile('l5pc_validate_neuron_arbor_somatic.py') # NOQA
+ execfile("l5pc_validate_neuron_arbor_somatic.py") # NOQA
else:
- with open('l5pc_validate_neuron_arbor_somatic.py') \
- as l5pc_file:
+ with open(
+ "l5pc_validate_neuron_arbor_somatic.py"
+ ) as l5pc_file:
l5pc_globals = {}
- exec(compile(l5pc_file.read(),
- 'l5pc_validate_neuron_arbor_somatic.py',
- 'exec'), l5pc_globals, l5pc_globals) # NOQA
+ exec(
+ compile(
+ l5pc_file.read(),
+ "l5pc_validate_neuron_arbor_somatic.py",
+ "exec",
+ ),
+ l5pc_globals,
+ l5pc_globals,
+ ) # NOQA
stdout = output.getvalue()
# mean relative L1-deviation between Arbor and Neuron below tolerance
- assert 'Default dt ({:,.3g}): test_l5pc OK!'.format(0.025) + \
- ' The mean relative Arbor-Neuron L1-deviation and error' \
+ assert (
+ "Default dt ({:,.3g}): test_l5pc OK!".format(0.025)
+ + " The mean relative Arbor-Neuron L1-deviation and error"
in stdout
+ )
finally:
os.chdir(old_cwd)
output.close()
diff --git a/bluepyopt/tests/test_lfpy.py b/bluepyopt/tests/test_lfpy.py
index c141e7f6..746b4bb3 100644
--- a/bluepyopt/tests/test_lfpy.py
+++ b/bluepyopt/tests/test_lfpy.py
@@ -2,13 +2,11 @@
import os
import sys
+
import pytest
L5PC_LFPY_PATH = os.path.abspath(
- os.path.join(
- os.path.dirname(__file__),
- '../../examples/l5pc_lfpy'
- )
+ os.path.join(os.path.dirname(__file__), "../../examples/l5pc_lfpy")
)
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
@@ -30,10 +28,10 @@ def test_lfpy_evaluator():
responses = evaluator.run_protocols(
protocols=evaluator.fitness_protocols.values(),
- param_values=release_params
+ param_values=release_params,
)
values = evaluator.fitness_calculator.calculate_values(responses)
assert len(values) == 21
- assert abs(values['Step1.soma.AP_height'] - 27.85963902931001) < 1e-5
- assert len(responses['Step1.MEA.v']["voltage"]) == 40
+ assert abs(values["Step1.soma.AP_height"] - 27.85963902931001) < 1e-5
+ assert len(responses["Step1.MEA.v"]["voltage"]) == 40
diff --git a/bluepyopt/tests/test_simplecell.py b/bluepyopt/tests/test_simplecell.py
index 93a6c564..394a9fbc 100644
--- a/bluepyopt/tests/test_simplecell.py
+++ b/bluepyopt/tests/test_simplecell.py
@@ -1,40 +1,38 @@
"""Simple cell example test class"""
-import sys
import os
+import sys
-SIMPLECELL_PATH = os.path.abspath(os.path.join(
- os.path.dirname(__file__),
- '../../examples/simplecell'))
+SIMPLECELL_PATH = os.path.abspath(
+ os.path.join(os.path.dirname(__file__), "../../examples/simplecell")
+)
# sys.path.insert(0, SIMPLECELL_PATH)
class TestSimpleCellClass(object):
-
"""Simple cell example test class for NEURON"""
- def setup(self):
+ def setup_method(self):
"""Setup"""
self.old_cwd = os.getcwd()
self.old_stdout = sys.stdout
os.chdir(SIMPLECELL_PATH)
- sys.stdout = open(os.devnull, 'w')
+ sys.stdout = open(os.devnull, "w")
- @staticmethod
- def test_exec():
+ def test_exec(self):
"""Simplecell NEURON: test execution"""
# When using import instead of execfile this doesn't work
# Probably because multiprocessing doesn't work correctly during
# import
if sys.version_info[0] < 3:
- execfile('simplecell.py') # NOQA
+ execfile("simplecell.py") # NOQA
else:
- with open('simplecell.py') as sc_file:
- exec(compile(sc_file.read(), 'simplecell.py', 'exec')) # NOQA
+ with open("simplecell.py") as sc_file:
+ exec(compile(sc_file.read(), "simplecell.py", "exec")) # NOQA
- def teardown(self):
+ def teardown_method(self):
"""Tear down"""
sys.stdout = self.old_stdout
@@ -42,31 +40,28 @@ def teardown(self):
class TestSimpleCellArborClass(object):
-
"""Simple cell example test class for Arbor"""
- def setup(self):
+ def setup_method(self):
"""Setup"""
self.old_cwd = os.getcwd()
self.old_stdout = sys.stdout
os.chdir(SIMPLECELL_PATH)
- sys.stdout = open(os.devnull, 'w')
+ sys.stdout = open(os.devnull, "w")
- @staticmethod
- def test_exec():
+ def test_exec(self):
"""Simplecell Arbor: test execution"""
# When using import instead of execfile this doesn't work
# Probably because multiprocessing doesn't work correctly during
# import
if sys.version_info[0] < 3:
- execfile('simplecell_arbor.py') # NOQA
+ execfile("simplecell_arbor.py") # NOQA
else:
- with open('simplecell_arbor.py') as sc_file:
- exec(compile(sc_file.read(),
- 'simplecell_arbor.py', 'exec')) # NOQA
+ with open("simplecell_arbor.py") as sc_file:
+ exec(compile(sc_file.read(), "simplecell_arbor.py", "exec")) # NOQA
- def teardown(self):
+ def teardown_method(self):
"""Tear down"""
sys.stdout = self.old_stdout
diff --git a/examples/graupnerbrunelstdp/run_fit.py b/examples/graupnerbrunelstdp/run_fit.py
index 2b1ae136..a677fa82 100644
--- a/examples/graupnerbrunelstdp/run_fit.py
+++ b/examples/graupnerbrunelstdp/run_fit.py
@@ -2,8 +2,6 @@
# pylint: disable=R0914
-from __future__ import print_function
-
import pickle
import bluepyopt as bpop
import matplotlib.pyplot as plt
diff --git a/examples/l5pc/L5PC.ipynb b/examples/l5pc/L5PC.ipynb
index 2a1b2d3b..9997e9ca 100644
--- a/examples/l5pc/L5PC.ipynb
+++ b/examples/l5pc/L5PC.ipynb
@@ -1,6 +1,7 @@
{
"cells": [
{
+ "attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
@@ -8,6 +9,7 @@
]
},
{
+ "attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
@@ -31,6 +33,7 @@
]
},
{
+ "attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
@@ -67,8 +70,6 @@
"%load_ext autoreload\n",
"%autoreload\n",
"\n",
- "from __future__ import print_function\n",
- "\n",
"!nrnivmodl mechanisms\n",
"import bluepyopt as bpopt\n",
"import bluepyopt.ephys as ephys\n",
@@ -81,6 +82,7 @@
]
},
{
+ "attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
@@ -102,6 +104,7 @@
]
},
{
+ "attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
@@ -109,6 +112,7 @@
]
},
{
+ "attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
@@ -116,6 +120,7 @@
]
},
{
+ "attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
@@ -158,758 +163,7 @@
},
{
"data": {
- "application/javascript": [
- "/* Put everything inside the global mpl namespace */\n",
- "window.mpl = {};\n",
- "\n",
- "mpl.get_websocket_type = function() {\n",
- " if (typeof(WebSocket) !== 'undefined') {\n",
- " return WebSocket;\n",
- " } else if (typeof(MozWebSocket) !== 'undefined') {\n",
- " return MozWebSocket;\n",
- " } else {\n",
- " alert('Your browser does not have WebSocket support.' +\n",
- " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
- " 'Firefox 4 and 5 are also supported but you ' +\n",
- " 'have to enable WebSockets in about:config.');\n",
- " };\n",
- "}\n",
- "\n",
- "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n",
- " this.id = figure_id;\n",
- "\n",
- " this.ws = websocket;\n",
- "\n",
- " this.supports_binary = (this.ws.binaryType != undefined);\n",
- "\n",
- " if (!this.supports_binary) {\n",
- " var warnings = document.getElementById(\"mpl-warnings\");\n",
- " if (warnings) {\n",
- " warnings.style.display = 'block';\n",
- " warnings.textContent = (\n",
- " \"This browser does not support binary websocket messages. \" +\n",
- " \"Performance may be slow.\");\n",
- " }\n",
- " }\n",
- "\n",
- " this.imageObj = new Image();\n",
- "\n",
- " this.context = undefined;\n",
- " this.message = undefined;\n",
- " this.canvas = undefined;\n",
- " this.rubberband_canvas = undefined;\n",
- " this.rubberband_context = undefined;\n",
- " this.format_dropdown = undefined;\n",
- "\n",
- " this.image_mode = 'full';\n",
- "\n",
- " this.root = $('');\n",
- " this._root_extra_style(this.root)\n",
- " this.root.attr('style', 'display: inline-block');\n",
- "\n",
- " $(parent_element).append(this.root);\n",
- "\n",
- " this._init_header(this);\n",
- " this._init_canvas(this);\n",
- " this._init_toolbar(this);\n",
- "\n",
- " var fig = this;\n",
- "\n",
- " this.waiting = false;\n",
- "\n",
- " this.ws.onopen = function () {\n",
- " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n",
- " fig.send_message(\"send_image_mode\", {});\n",
- " fig.send_message(\"refresh\", {});\n",
- " }\n",
- "\n",
- " this.imageObj.onload = function() {\n",
- " if (fig.image_mode == 'full') {\n",
- " // Full images could contain transparency (where diff images\n",
- " // almost always do), so we need to clear the canvas so that\n",
- " // there is no ghosting.\n",
- " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
- " }\n",
- " fig.context.drawImage(fig.imageObj, 0, 0);\n",
- " };\n",
- "\n",
- " this.imageObj.onunload = function() {\n",
- " this.ws.close();\n",
- " }\n",
- "\n",
- " this.ws.onmessage = this._make_on_message_function(this);\n",
- "\n",
- " this.ondownload = ondownload;\n",
- "}\n",
- "\n",
- "mpl.figure.prototype._init_header = function() {\n",
- " var titlebar = $(\n",
- " '');\n",
- " var titletext = $(\n",
- " '');\n",
- " titlebar.append(titletext)\n",
- " this.root.append(titlebar);\n",
- " this.header = titletext[0];\n",
- "}\n",
- "\n",
- "\n",
- "\n",
- "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n",
- "\n",
- "}\n",
- "\n",
- "\n",
- "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n",
- "\n",
- "}\n",
- "\n",
- "mpl.figure.prototype._init_canvas = function() {\n",
- " var fig = this;\n",
- "\n",
- " var canvas_div = $('');\n",
- "\n",
- " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n",
- "\n",
- " function canvas_keyboard_event(event) {\n",
- " return fig.key_event(event, event['data']);\n",
- " }\n",
- "\n",
- " canvas_div.keydown('key_press', canvas_keyboard_event);\n",
- " canvas_div.keyup('key_release', canvas_keyboard_event);\n",
- " this.canvas_div = canvas_div\n",
- " this._canvas_extra_style(canvas_div)\n",
- " this.root.append(canvas_div);\n",
- "\n",
- " var canvas = $('');\n",
- " canvas.addClass('mpl-canvas');\n",
- " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n",
- "\n",
- " this.canvas = canvas[0];\n",
- " this.context = canvas[0].getContext(\"2d\");\n",
- "\n",
- " var rubberband = $('');\n",
- " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n",
- "\n",
- " var pass_mouse_events = true;\n",
- "\n",
- " canvas_div.resizable({\n",
- " start: function(event, ui) {\n",
- " pass_mouse_events = false;\n",
- " },\n",
- " resize: function(event, ui) {\n",
- " fig.request_resize(ui.size.width, ui.size.height);\n",
- " },\n",
- " stop: function(event, ui) {\n",
- " pass_mouse_events = true;\n",
- " fig.request_resize(ui.size.width, ui.size.height);\n",
- " },\n",
- " });\n",
- "\n",
- " function mouse_event_fn(event) {\n",
- " if (pass_mouse_events)\n",
- " return fig.mouse_event(event, event['data']);\n",
- " }\n",
- "\n",
- " rubberband.mousedown('button_press', mouse_event_fn);\n",
- " rubberband.mouseup('button_release', mouse_event_fn);\n",
- " // Throttle sequential mouse events to 1 every 20ms.\n",
- " rubberband.mousemove('motion_notify', mouse_event_fn);\n",
- "\n",
- " rubberband.mouseenter('figure_enter', mouse_event_fn);\n",
- " rubberband.mouseleave('figure_leave', mouse_event_fn);\n",
- "\n",
- " canvas_div.on(\"wheel\", function (event) {\n",
- " event = event.originalEvent;\n",
- " event['data'] = 'scroll'\n",
- " if (event.deltaY < 0) {\n",
- " event.step = 1;\n",
- " } else {\n",
- " event.step = -1;\n",
- " }\n",
- " mouse_event_fn(event);\n",
- " });\n",
- "\n",
- " canvas_div.append(canvas);\n",
- " canvas_div.append(rubberband);\n",
- "\n",
- " this.rubberband = rubberband;\n",
- " this.rubberband_canvas = rubberband[0];\n",
- " this.rubberband_context = rubberband[0].getContext(\"2d\");\n",
- " this.rubberband_context.strokeStyle = \"#000000\";\n",
- "\n",
- " this._resize_canvas = function(width, height) {\n",
- " // Keep the size of the canvas, canvas container, and rubber band\n",
- " // canvas in synch.\n",
- " canvas_div.css('width', width)\n",
- " canvas_div.css('height', height)\n",
- "\n",
- " canvas.attr('width', width);\n",
- " canvas.attr('height', height);\n",
- "\n",
- " rubberband.attr('width', width);\n",
- " rubberband.attr('height', height);\n",
- " }\n",
- "\n",
- " // Set the figure to an initial 600x600px, this will subsequently be updated\n",
- " // upon first draw.\n",
- " this._resize_canvas(600, 600);\n",
- "\n",
- " // Disable right mouse context menu.\n",
- " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n",
- " return false;\n",
- " });\n",
- "\n",
- " function set_focus () {\n",
- " canvas.focus();\n",
- " canvas_div.focus();\n",
- " }\n",
- "\n",
- " window.setTimeout(set_focus, 100);\n",
- "}\n",
- "\n",
- "mpl.figure.prototype._init_toolbar = function() {\n",
- " var fig = this;\n",
- "\n",
- " var nav_element = $('')\n",
- " nav_element.attr('style', 'width: 100%');\n",
- " this.root.append(nav_element);\n",
- "\n",
- " // Define a callback function for later on.\n",
- " function toolbar_event(event) {\n",
- " return fig.toolbar_button_onclick(event['data']);\n",
- " }\n",
- " function toolbar_mouse_event(event) {\n",
- " return fig.toolbar_button_onmouseover(event['data']);\n",
- " }\n",
- "\n",
- " for(var toolbar_ind in mpl.toolbar_items) {\n",
- " var name = mpl.toolbar_items[toolbar_ind][0];\n",
- " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
- " var image = mpl.toolbar_items[toolbar_ind][2];\n",
- " var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
- "\n",
- " if (!name) {\n",
- " // put a spacer in here.\n",
- " continue;\n",
- " }\n",
- " var button = $('');\n",
- " button.addClass('ui-button ui-widget ui-state-default ui-corner-all ' +\n",
- " 'ui-button-icon-only');\n",
- " button.attr('role', 'button');\n",
- " button.attr('aria-disabled', 'false');\n",
- " button.click(method_name, toolbar_event);\n",
- " button.mouseover(tooltip, toolbar_mouse_event);\n",
- "\n",
- " var icon_img = $('');\n",
- " icon_img.addClass('ui-button-icon-primary ui-icon');\n",
- " icon_img.addClass(image);\n",
- " icon_img.addClass('ui-corner-all');\n",
- "\n",
- " var tooltip_span = $('');\n",
- " tooltip_span.addClass('ui-button-text');\n",
- " tooltip_span.html(tooltip);\n",
- "\n",
- " button.append(icon_img);\n",
- " button.append(tooltip_span);\n",
- "\n",
- " nav_element.append(button);\n",
- " }\n",
- "\n",
- " var fmt_picker_span = $('');\n",
- "\n",
- " var fmt_picker = $('');\n",
- " fmt_picker.addClass('mpl-toolbar-option ui-widget ui-widget-content');\n",
- " fmt_picker_span.append(fmt_picker);\n",
- " nav_element.append(fmt_picker_span);\n",
- " this.format_dropdown = fmt_picker[0];\n",
- "\n",
- " for (var ind in mpl.extensions) {\n",
- " var fmt = mpl.extensions[ind];\n",
- " var option = $(\n",
- " '', {selected: fmt === mpl.default_extension}).html(fmt);\n",
- " fmt_picker.append(option)\n",
- " }\n",
- "\n",
- " // Add hover states to the ui-buttons\n",
- " $( \".ui-button\" ).hover(\n",
- " function() { $(this).addClass(\"ui-state-hover\");},\n",
- " function() { $(this).removeClass(\"ui-state-hover\");}\n",
- " );\n",
- "\n",
- " var status_bar = $('');\n",
- " nav_element.append(status_bar);\n",
- " this.message = status_bar[0];\n",
- "}\n",
- "\n",
- "mpl.figure.prototype.request_resize = function(x_pixels, y_pixels) {\n",
- " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
- " // which will in turn request a refresh of the image.\n",
- " this.send_message('resize', {'width': x_pixels, 'height': y_pixels});\n",
- "}\n",
- "\n",
- "mpl.figure.prototype.send_message = function(type, properties) {\n",
- " properties['type'] = type;\n",
- " properties['figure_id'] = this.id;\n",
- " this.ws.send(JSON.stringify(properties));\n",
- "}\n",
- "\n",
- "mpl.figure.prototype.send_draw_message = function() {\n",
- " if (!this.waiting) {\n",
- " this.waiting = true;\n",
- " this.ws.send(JSON.stringify({type: \"draw\", figure_id: this.id}));\n",
- " }\n",
- "}\n",
- "\n",
- "\n",
- "mpl.figure.prototype.handle_save = function(fig, msg) {\n",
- " var format_dropdown = fig.format_dropdown;\n",
- " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
- " fig.ondownload(fig, format);\n",
- "}\n",
- "\n",
- "\n",
- "mpl.figure.prototype.handle_resize = function(fig, msg) {\n",
- " var size = msg['size'];\n",
- " if (size[0] != fig.canvas.width || size[1] != fig.canvas.height) {\n",
- " fig._resize_canvas(size[0], size[1]);\n",
- " fig.send_message(\"refresh\", {});\n",
- " };\n",
- "}\n",
- "\n",
- "mpl.figure.prototype.handle_rubberband = function(fig, msg) {\n",
- " var x0 = msg['x0'];\n",
- " var y0 = fig.canvas.height - msg['y0'];\n",
- " var x1 = msg['x1'];\n",
- " var y1 = fig.canvas.height - msg['y1'];\n",
- " x0 = Math.floor(x0) + 0.5;\n",
- " y0 = Math.floor(y0) + 0.5;\n",
- " x1 = Math.floor(x1) + 0.5;\n",
- " y1 = Math.floor(y1) + 0.5;\n",
- " var min_x = Math.min(x0, x1);\n",
- " var min_y = Math.min(y0, y1);\n",
- " var width = Math.abs(x1 - x0);\n",
- " var height = Math.abs(y1 - y0);\n",
- "\n",
- " fig.rubberband_context.clearRect(\n",
- " 0, 0, fig.canvas.width, fig.canvas.height);\n",
- "\n",
- " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
- "}\n",
- "\n",
- "mpl.figure.prototype.handle_figure_label = function(fig, msg) {\n",
- " // Updates the figure title.\n",
- " fig.header.textContent = msg['label'];\n",
- "}\n",
- "\n",
- "mpl.figure.prototype.handle_cursor = function(fig, msg) {\n",
- " var cursor = msg['cursor'];\n",
- " switch(cursor)\n",
- " {\n",
- " case 0:\n",
- " cursor = 'pointer';\n",
- " break;\n",
- " case 1:\n",
- " cursor = 'default';\n",
- " break;\n",
- " case 2:\n",
- " cursor = 'crosshair';\n",
- " break;\n",
- " case 3:\n",
- " cursor = 'move';\n",
- " break;\n",
- " }\n",
- " fig.rubberband_canvas.style.cursor = cursor;\n",
- "}\n",
- "\n",
- "mpl.figure.prototype.handle_message = function(fig, msg) {\n",
- " fig.message.textContent = msg['message'];\n",
- "}\n",
- "\n",
- "mpl.figure.prototype.handle_draw = function(fig, msg) {\n",
- " // Request the server to send over a new figure.\n",
- " fig.send_draw_message();\n",
- "}\n",
- "\n",
- "mpl.figure.prototype.handle_image_mode = function(fig, msg) {\n",
- " fig.image_mode = msg['mode'];\n",
- "}\n",
- "\n",
- "mpl.figure.prototype.updated_canvas_event = function() {\n",
- " // Called whenever the canvas gets updated.\n",
- " this.send_message(\"ack\", {});\n",
- "}\n",
- "\n",
- "// A function to construct a web socket function for onmessage handling.\n",
- "// Called in the figure constructor.\n",
- "mpl.figure.prototype._make_on_message_function = function(fig) {\n",
- " return function socket_on_message(evt) {\n",
- " if (evt.data instanceof Blob) {\n",
- " /* FIXME: We get \"Resource interpreted as Image but\n",
- " * transferred with MIME type text/plain:\" errors on\n",
- " * Chrome. But how to set the MIME type? It doesn't seem\n",
- " * to be part of the websocket stream */\n",
- " evt.data.type = \"image/png\";\n",
- "\n",
- " /* Free the memory for the previous frames */\n",
- " if (fig.imageObj.src) {\n",
- " (window.URL || window.webkitURL).revokeObjectURL(\n",
- " fig.imageObj.src);\n",
- " }\n",
- "\n",
- " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
- " evt.data);\n",
- " fig.updated_canvas_event();\n",
- " fig.waiting = false;\n",
- " return;\n",
- " }\n",
- " else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n",
- " fig.imageObj.src = evt.data;\n",
- " fig.updated_canvas_event();\n",
- " fig.waiting = false;\n",
- " return;\n",
- " }\n",
- "\n",
- " var msg = JSON.parse(evt.data);\n",
- " var msg_type = msg['type'];\n",
- "\n",
- " // Call the \"handle_{type}\" callback, which takes\n",
- " // the figure and JSON message as its only arguments.\n",
- " try {\n",
- " var callback = fig[\"handle_\" + msg_type];\n",
- " } catch (e) {\n",
- " console.log(\"No handler for the '\" + msg_type + \"' message type: \", msg);\n",
- " return;\n",
- " }\n",
- "\n",
- " if (callback) {\n",
- " try {\n",
- " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
- " callback(fig, msg);\n",
- " } catch (e) {\n",
- " console.log(\"Exception inside the 'handler_\" + msg_type + \"' callback:\", e, e.stack, msg);\n",
- " }\n",
- " }\n",
- " };\n",
- "}\n",
- "\n",
- "// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
- "mpl.findpos = function(e) {\n",
- " //this section is from http://www.quirksmode.org/js/events_properties.html\n",
- " var targ;\n",
- " if (!e)\n",
- " e = window.event;\n",
- " if (e.target)\n",
- " targ = e.target;\n",
- " else if (e.srcElement)\n",
- " targ = e.srcElement;\n",
- " if (targ.nodeType == 3) // defeat Safari bug\n",
- " targ = targ.parentNode;\n",
- "\n",
- " // jQuery normalizes the pageX and pageY\n",
- " // pageX,Y are the mouse positions relative to the document\n",
- " // offset() returns the position of the element relative to the document\n",
- " var x = e.pageX - $(targ).offset().left;\n",
- " var y = e.pageY - $(targ).offset().top;\n",
- "\n",
- " return {\"x\": x, \"y\": y};\n",
- "};\n",
- "\n",
- "/*\n",
- " * return a copy of an object with only non-object keys\n",
- " * we need this to avoid circular references\n",
- " * http://stackoverflow.com/a/24161582/3208463\n",
- " */\n",
- "function simpleKeys (original) {\n",
- " return Object.keys(original).reduce(function (obj, key) {\n",
- " if (typeof original[key] !== 'object')\n",
- " obj[key] = original[key]\n",
- " return obj;\n",
- " }, {});\n",
- "}\n",
- "\n",
- "mpl.figure.prototype.mouse_event = function(event, name) {\n",
- " var canvas_pos = mpl.findpos(event)\n",
- "\n",
- " if (name === 'button_press')\n",
- " {\n",
- " this.canvas.focus();\n",
- " this.canvas_div.focus();\n",
- " }\n",
- "\n",
- " var x = canvas_pos.x;\n",
- " var y = canvas_pos.y;\n",
- "\n",
- " this.send_message(name, {x: x, y: y, button: event.button,\n",
- " step: event.step,\n",
- " guiEvent: simpleKeys(event)});\n",
- "\n",
- " /* This prevents the web browser from automatically changing to\n",
- " * the text insertion cursor when the button is pressed. We want\n",
- " * to control all of the cursor setting manually through the\n",
- " * 'cursor' event from matplotlib */\n",
- " event.preventDefault();\n",
- " return false;\n",
- "}\n",
- "\n",
- "mpl.figure.prototype._key_event_extra = function(event, name) {\n",
- " // Handle any extra behaviour associated with a key event\n",
- "}\n",
- "\n",
- "mpl.figure.prototype.key_event = function(event, name) {\n",
- "\n",
- " // Prevent repeat events\n",
- " if (name == 'key_press')\n",
- " {\n",
- " if (event.which === this._key)\n",
- " return;\n",
- " else\n",
- " this._key = event.which;\n",
- " }\n",
- " if (name == 'key_release')\n",
- " this._key = null;\n",
- "\n",
- " var value = '';\n",
- " if (event.ctrlKey && event.which != 17)\n",
- " value += \"ctrl+\";\n",
- " if (event.altKey && event.which != 18)\n",
- " value += \"alt+\";\n",
- " if (event.shiftKey && event.which != 16)\n",
- " value += \"shift+\";\n",
- "\n",
- " value += 'k';\n",
- " value += event.which.toString();\n",
- "\n",
- " this._key_event_extra(event, name);\n",
- "\n",
- " this.send_message(name, {key: value,\n",
- " guiEvent: simpleKeys(event)});\n",
- " return false;\n",
- "}\n",
- "\n",
- "mpl.figure.prototype.toolbar_button_onclick = function(name) {\n",
- " if (name == 'download') {\n",
- " this.handle_save(this, null);\n",
- " } else {\n",
- " this.send_message(\"toolbar_button\", {name: name});\n",
- " }\n",
- "};\n",
- "\n",
- "mpl.figure.prototype.toolbar_button_onmouseover = function(tooltip) {\n",
- " this.message.textContent = tooltip;\n",
- "};\n",
- "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
- "\n",
- "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n",
- "\n",
- "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n",
- " // Create a \"websocket\"-like object which calls the given IPython comm\n",
- " // object with the appropriate methods. Currently this is a non binary\n",
- " // socket, so there is still some room for performance tuning.\n",
- " var ws = {};\n",
- "\n",
- " ws.close = function() {\n",
- " comm.close()\n",
- " };\n",
- " ws.send = function(m) {\n",
- " //console.log('sending', m);\n",
- " comm.send(m);\n",
- " };\n",
- " // Register the callback with on_msg.\n",
- " comm.on_msg(function(msg) {\n",
- " //console.log('receiving', msg['content']['data'], msg);\n",
- " // Pass the mpl event to the overriden (by mpl) onmessage function.\n",
- " ws.onmessage(msg['content']['data'])\n",
- " });\n",
- " return ws;\n",
- "}\n",
- "\n",
- "mpl.mpl_figure_comm = function(comm, msg) {\n",
- " // This is the function which gets called when the mpl process\n",
- " // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
- "\n",
- " var id = msg.content.data.id;\n",
- " // Get hold of the div created by the display call when the Comm\n",
- " // socket was opened in Python.\n",
- " var element = $(\"#\" + id);\n",
- " var ws_proxy = comm_websocket_adapter(comm)\n",
- "\n",
- " function ondownload(figure, format) {\n",
- " window.open(figure.imageObj.src);\n",
- " }\n",
- "\n",
- " var fig = new mpl.figure(id, ws_proxy,\n",
- " ondownload,\n",
- " element.get(0));\n",
- "\n",
- " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
- " // web socket which is closed, not our websocket->open comm proxy.\n",
- " ws_proxy.onopen();\n",
- "\n",
- " fig.parent_element = element.get(0);\n",
- " fig.cell_info = mpl.find_output_cell(\"\");\n",
- " if (!fig.cell_info) {\n",
- " console.error(\"Failed to find cell for figure\", id, fig);\n",
- " return;\n",
- " }\n",
- "\n",
- " var output_index = fig.cell_info[2]\n",
- " var cell = fig.cell_info[0];\n",
- "\n",
- "};\n",
- "\n",
- "mpl.figure.prototype.handle_close = function(fig, msg) {\n",
- " fig.root.unbind('remove')\n",
- "\n",
- " // Update the output cell to use the data from the current canvas.\n",
- " fig.push_to_output();\n",
- " var dataURL = fig.canvas.toDataURL();\n",
- " // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
- " // the notebook keyboard shortcuts fail.\n",
- " IPython.keyboard_manager.enable()\n",
- " $(fig.parent_element).html('');\n",
- " fig.close_ws(fig, msg);\n",
- "}\n",
- "\n",
- "mpl.figure.prototype.close_ws = function(fig, msg){\n",
- " fig.send_message('closing', msg);\n",
- " // fig.ws.close()\n",
- "}\n",
- "\n",
- "mpl.figure.prototype.push_to_output = function(remove_interactive) {\n",
- " // Turn the data on the canvas into data in the output cell.\n",
- " var dataURL = this.canvas.toDataURL();\n",
- " this.cell_info[1]['text/html'] = '';\n",
- "}\n",
- "\n",
- "mpl.figure.prototype.updated_canvas_event = function() {\n",
- " // Tell IPython that the notebook contents must change.\n",
- " IPython.notebook.set_dirty(true);\n",
- " this.send_message(\"ack\", {});\n",
- " var fig = this;\n",
- " // Wait a second, then push the new image to the DOM so\n",
- " // that it is saved nicely (might be nice to debounce this).\n",
- " setTimeout(function () { fig.push_to_output() }, 1000);\n",
- "}\n",
- "\n",
- "mpl.figure.prototype._init_toolbar = function() {\n",
- " var fig = this;\n",
- "\n",
- " var nav_element = $('')\n",
- " nav_element.attr('style', 'width: 100%');\n",
- " this.root.append(nav_element);\n",
- "\n",
- " // Define a callback function for later on.\n",
- " function toolbar_event(event) {\n",
- " return fig.toolbar_button_onclick(event['data']);\n",
- " }\n",
- " function toolbar_mouse_event(event) {\n",
- " return fig.toolbar_button_onmouseover(event['data']);\n",
- " }\n",
- "\n",
- " for(var toolbar_ind in mpl.toolbar_items){\n",
- " var name = mpl.toolbar_items[toolbar_ind][0];\n",
- " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
- " var image = mpl.toolbar_items[toolbar_ind][2];\n",
- " var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
- "\n",
- " if (!name) { continue; };\n",
- "\n",
- " var button = $('');\n",
- " button.click(method_name, toolbar_event);\n",
- " button.mouseover(tooltip, toolbar_mouse_event);\n",
- " nav_element.append(button);\n",
- " }\n",
- "\n",
- " // Add the status bar.\n",
- " var status_bar = $('');\n",
- " nav_element.append(status_bar);\n",
- " this.message = status_bar[0];\n",
- "\n",
- " // Add the close button to the window.\n",
- " var buttongrp = $('');\n",
- " var button = $('');\n",
- " button.click(function (evt) { fig.handle_close(fig, {}); } );\n",
- " button.mouseover('Stop Interaction', toolbar_mouse_event);\n",
- " buttongrp.append(button);\n",
- " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n",
- " titlebar.prepend(buttongrp);\n",
- "}\n",
- "\n",
- "mpl.figure.prototype._root_extra_style = function(el){\n",
- " var fig = this\n",
- " el.on(\"remove\", function(){\n",
- "\tfig.close_ws(fig, {});\n",
- " });\n",
- "}\n",
- "\n",
- "mpl.figure.prototype._canvas_extra_style = function(el){\n",
- " // this is important to make the div 'focusable\n",
- " el.attr('tabindex', 0)\n",
- " // reach out to IPython and tell the keyboard manager to turn it's self\n",
- " // off when our div gets focus\n",
- "\n",
- " // location in version 3\n",
- " if (IPython.notebook.keyboard_manager) {\n",
- " IPython.notebook.keyboard_manager.register_events(el);\n",
- " }\n",
- " else {\n",
- " // location in version 2\n",
- " IPython.keyboard_manager.register_events(el);\n",
- " }\n",
- "\n",
- "}\n",
- "\n",
- "mpl.figure.prototype._key_event_extra = function(event, name) {\n",
- " var manager = IPython.notebook.keyboard_manager;\n",
- " if (!manager)\n",
- " manager = IPython.keyboard_manager;\n",
- "\n",
- " // Check for shift+enter\n",
- " if (event.shiftKey && event.which == 13) {\n",
- " this.canvas_div.blur();\n",
- " // select the cell after this one\n",
- " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n",
- " IPython.notebook.select(index + 1);\n",
- " }\n",
- "}\n",
- "\n",
- "mpl.figure.prototype.handle_save = function(fig, msg) {\n",
- " fig.ondownload(fig, null);\n",
- "}\n",
- "\n",
- "\n",
- "mpl.find_output_cell = function(html_output) {\n",
- " // Return the cell and output element which can be found *uniquely* in the notebook.\n",
- " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
- " // IPython event is triggered only after the cells have been serialised, which for\n",
- " // our purposes (turning an active figure into a static one), is too late.\n",
- " var cells = IPython.notebook.get_cells();\n",
- " var ncells = cells.length;\n",
- " for (var i=0; i= 3 moved mimebundle to data attribute of output\n",
- " data = data.data;\n",
- " }\n",
- " if (data['text/html'] == html_output) {\n",
- " return [cell, data, j];\n",
- " }\n",
- " }\n",
- " }\n",
- " }\n",
- "}\n",
- "\n",
- "// Register the function which deals with the matplotlib target/channel.\n",
- "// The kernel may be null if the page has been refreshed.\n",
- "if (IPython.notebook.kernel != null) {\n",
- " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n",
- "}\n"
- ],
+ "application/javascript": "/* Put everything inside the global mpl namespace */\nwindow.mpl = {};\n\nmpl.get_websocket_type = function() {\n if (typeof(WebSocket) !== 'undefined') {\n return WebSocket;\n } else if (typeof(MozWebSocket) !== 'undefined') {\n return MozWebSocket;\n } else {\n alert('Your browser does not have WebSocket support.' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.');\n };\n}\n\nmpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = (this.ws.binaryType != undefined);\n\n if (!this.supports_binary) {\n var warnings = document.getElementById(\"mpl-warnings\");\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent = (\n \"This browser does not support binary websocket messages. \" +\n \"Performance may be slow.\");\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = $('');\n this._root_extra_style(this.root)\n this.root.attr('style', 'display: inline-block');\n\n $(parent_element).append(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n fig.send_message(\"send_image_mode\", {});\n fig.send_message(\"refresh\", {});\n }\n\n this.imageObj.onload = function() {\n if (fig.image_mode == 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function() {\n this.ws.close();\n }\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n}\n\nmpl.figure.prototype._init_header = function() {\n var titlebar = $(\n '');\n var titletext = $(\n '');\n titlebar.append(titletext)\n this.root.append(titlebar);\n this.header = titletext[0];\n}\n\n\n\nmpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n\n}\n\n\nmpl.figure.prototype._root_extra_style = function(canvas_div) {\n\n}\n\nmpl.figure.prototype._init_canvas = function() {\n var fig = this;\n\n var canvas_div = $('');\n\n canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n\n function canvas_keyboard_event(event) {\n return fig.key_event(event, event['data']);\n }\n\n canvas_div.keydown('key_press', canvas_keyboard_event);\n canvas_div.keyup('key_release', canvas_keyboard_event);\n this.canvas_div = canvas_div\n this._canvas_extra_style(canvas_div)\n this.root.append(canvas_div);\n\n var canvas = $('');\n canvas.addClass('mpl-canvas');\n canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n\n this.canvas = canvas[0];\n this.context = canvas[0].getContext(\"2d\");\n\n var rubberband = $('');\n rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n\n var pass_mouse_events = true;\n\n canvas_div.resizable({\n start: function(event, ui) {\n pass_mouse_events = false;\n },\n resize: function(event, ui) {\n fig.request_resize(ui.size.width, ui.size.height);\n },\n stop: function(event, ui) {\n pass_mouse_events = true;\n fig.request_resize(ui.size.width, ui.size.height);\n },\n });\n\n function mouse_event_fn(event) {\n if (pass_mouse_events)\n return fig.mouse_event(event, event['data']);\n }\n\n rubberband.mousedown('button_press', mouse_event_fn);\n rubberband.mouseup('button_release', mouse_event_fn);\n // Throttle sequential mouse events to 1 every 20ms.\n rubberband.mousemove('motion_notify', mouse_event_fn);\n\n rubberband.mouseenter('figure_enter', mouse_event_fn);\n rubberband.mouseleave('figure_leave', mouse_event_fn);\n\n canvas_div.on(\"wheel\", function (event) {\n event = event.originalEvent;\n event['data'] = 'scroll'\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n mouse_event_fn(event);\n });\n\n canvas_div.append(canvas);\n canvas_div.append(rubberband);\n\n this.rubberband = rubberband;\n this.rubberband_canvas = rubberband[0];\n this.rubberband_context = rubberband[0].getContext(\"2d\");\n this.rubberband_context.strokeStyle = \"#000000\";\n\n this._resize_canvas = function(width, height) {\n // Keep the size of the canvas, canvas container, and rubber band\n // canvas in synch.\n canvas_div.css('width', width)\n canvas_div.css('height', height)\n\n canvas.attr('width', width);\n canvas.attr('height', height);\n\n rubberband.attr('width', width);\n rubberband.attr('height', height);\n }\n\n // Set the figure to an initial 600x600px, this will subsequently be updated\n // upon first draw.\n this._resize_canvas(600, 600);\n\n // Disable right mouse context menu.\n $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n return false;\n });\n\n function set_focus () {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n}\n\nmpl.figure.prototype._init_toolbar = function() {\n var fig = this;\n\n var nav_element = $('')\n nav_element.attr('style', 'width: 100%');\n this.root.append(nav_element);\n\n // Define a callback function for later on.\n function toolbar_event(event) {\n return fig.toolbar_button_onclick(event['data']);\n }\n function toolbar_mouse_event(event) {\n return fig.toolbar_button_onmouseover(event['data']);\n }\n\n for(var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n // put a spacer in here.\n continue;\n }\n var button = $('');\n button.addClass('ui-button ui-widget ui-state-default ui-corner-all ' +\n 'ui-button-icon-only');\n button.attr('role', 'button');\n button.attr('aria-disabled', 'false');\n button.click(method_name, toolbar_event);\n button.mouseover(tooltip, toolbar_mouse_event);\n\n var icon_img = $('');\n icon_img.addClass('ui-button-icon-primary ui-icon');\n icon_img.addClass(image);\n icon_img.addClass('ui-corner-all');\n\n var tooltip_span = $('');\n tooltip_span.addClass('ui-button-text');\n tooltip_span.html(tooltip);\n\n button.append(icon_img);\n button.append(tooltip_span);\n\n nav_element.append(button);\n }\n\n var fmt_picker_span = $('');\n\n var fmt_picker = $('');\n fmt_picker.addClass('mpl-toolbar-option ui-widget ui-widget-content');\n fmt_picker_span.append(fmt_picker);\n nav_element.append(fmt_picker_span);\n this.format_dropdown = fmt_picker[0];\n\n for (var ind in mpl.extensions) {\n var fmt = mpl.extensions[ind];\n var option = $(\n '', {selected: fmt === mpl.default_extension}).html(fmt);\n fmt_picker.append(option)\n }\n\n // Add hover states to the ui-buttons\n $( \".ui-button\" ).hover(\n function() { $(this).addClass(\"ui-state-hover\");},\n function() { $(this).removeClass(\"ui-state-hover\");}\n );\n\n var status_bar = $('');\n nav_element.append(status_bar);\n this.message = status_bar[0];\n}\n\nmpl.figure.prototype.request_resize = function(x_pixels, y_pixels) {\n // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n // which will in turn request a refresh of the image.\n this.send_message('resize', {'width': x_pixels, 'height': y_pixels});\n}\n\nmpl.figure.prototype.send_message = function(type, properties) {\n properties['type'] = type;\n properties['figure_id'] = this.id;\n this.ws.send(JSON.stringify(properties));\n}\n\nmpl.figure.prototype.send_draw_message = function() {\n if (!this.waiting) {\n this.waiting = true;\n this.ws.send(JSON.stringify({type: \"draw\", figure_id: this.id}));\n }\n}\n\n\nmpl.figure.prototype.handle_save = function(fig, msg) {\n var format_dropdown = fig.format_dropdown;\n var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n fig.ondownload(fig, format);\n}\n\n\nmpl.figure.prototype.handle_resize = function(fig, msg) {\n var size = msg['size'];\n if (size[0] != fig.canvas.width || size[1] != fig.canvas.height) {\n fig._resize_canvas(size[0], size[1]);\n fig.send_message(\"refresh\", {});\n };\n}\n\nmpl.figure.prototype.handle_rubberband = function(fig, msg) {\n var x0 = msg['x0'];\n var y0 = fig.canvas.height - msg['y0'];\n var x1 = msg['x1'];\n var y1 = fig.canvas.height - msg['y1'];\n x0 = Math.floor(x0) + 0.5;\n y0 = Math.floor(y0) + 0.5;\n x1 = Math.floor(x1) + 0.5;\n y1 = Math.floor(y1) + 0.5;\n var min_x = Math.min(x0, x1);\n var min_y = Math.min(y0, y1);\n var width = Math.abs(x1 - x0);\n var height = Math.abs(y1 - y0);\n\n fig.rubberband_context.clearRect(\n 0, 0, fig.canvas.width, fig.canvas.height);\n\n fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n}\n\nmpl.figure.prototype.handle_figure_label = function(fig, msg) {\n // Updates the figure title.\n fig.header.textContent = msg['label'];\n}\n\nmpl.figure.prototype.handle_cursor = function(fig, msg) {\n var cursor = msg['cursor'];\n switch(cursor)\n {\n case 0:\n cursor = 'pointer';\n break;\n case 1:\n cursor = 'default';\n break;\n case 2:\n cursor = 'crosshair';\n break;\n case 3:\n cursor = 'move';\n break;\n }\n fig.rubberband_canvas.style.cursor = cursor;\n}\n\nmpl.figure.prototype.handle_message = function(fig, msg) {\n fig.message.textContent = msg['message'];\n}\n\nmpl.figure.prototype.handle_draw = function(fig, msg) {\n // Request the server to send over a new figure.\n fig.send_draw_message();\n}\n\nmpl.figure.prototype.handle_image_mode = function(fig, msg) {\n fig.image_mode = msg['mode'];\n}\n\nmpl.figure.prototype.updated_canvas_event = function() {\n // Called whenever the canvas gets updated.\n this.send_message(\"ack\", {});\n}\n\n// A function to construct a web socket function for onmessage handling.\n// Called in the figure constructor.\nmpl.figure.prototype._make_on_message_function = function(fig) {\n return function socket_on_message(evt) {\n if (evt.data instanceof Blob) {\n /* FIXME: We get \"Resource interpreted as Image but\n * transferred with MIME type text/plain:\" errors on\n * Chrome. But how to set the MIME type? It doesn't seem\n * to be part of the websocket stream */\n evt.data.type = \"image/png\";\n\n /* Free the memory for the previous frames */\n if (fig.imageObj.src) {\n (window.URL || window.webkitURL).revokeObjectURL(\n fig.imageObj.src);\n }\n\n fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n evt.data);\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n }\n else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n fig.imageObj.src = evt.data;\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n }\n\n var msg = JSON.parse(evt.data);\n var msg_type = msg['type'];\n\n // Call the \"handle_{type}\" callback, which takes\n // the figure and JSON message as its only arguments.\n try {\n var callback = fig[\"handle_\" + msg_type];\n } catch (e) {\n console.log(\"No handler for the '\" + msg_type + \"' message type: \", msg);\n return;\n }\n\n if (callback) {\n try {\n // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n callback(fig, msg);\n } catch (e) {\n console.log(\"Exception inside the 'handler_\" + msg_type + \"' callback:\", e, e.stack, msg);\n }\n }\n };\n}\n\n// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\nmpl.findpos = function(e) {\n //this section is from http://www.quirksmode.org/js/events_properties.html\n var targ;\n if (!e)\n e = window.event;\n if (e.target)\n targ = e.target;\n else if (e.srcElement)\n targ = e.srcElement;\n if (targ.nodeType == 3) // defeat Safari bug\n targ = targ.parentNode;\n\n // jQuery normalizes the pageX and pageY\n // pageX,Y are the mouse positions relative to the document\n // offset() returns the position of the element relative to the document\n var x = e.pageX - $(targ).offset().left;\n var y = e.pageY - $(targ).offset().top;\n\n return {\"x\": x, \"y\": y};\n};\n\n/*\n * return a copy of an object with only non-object keys\n * we need this to avoid circular references\n * http://stackoverflow.com/a/24161582/3208463\n */\nfunction simpleKeys (original) {\n return Object.keys(original).reduce(function (obj, key) {\n if (typeof original[key] !== 'object')\n obj[key] = original[key]\n return obj;\n }, {});\n}\n\nmpl.figure.prototype.mouse_event = function(event, name) {\n var canvas_pos = mpl.findpos(event)\n\n if (name === 'button_press')\n {\n this.canvas.focus();\n this.canvas_div.focus();\n }\n\n var x = canvas_pos.x;\n var y = canvas_pos.y;\n\n this.send_message(name, {x: x, y: y, button: event.button,\n step: event.step,\n guiEvent: simpleKeys(event)});\n\n /* This prevents the web browser from automatically changing to\n * the text insertion cursor when the button is pressed. We want\n * to control all of the cursor setting manually through the\n * 'cursor' event from matplotlib */\n event.preventDefault();\n return false;\n}\n\nmpl.figure.prototype._key_event_extra = function(event, name) {\n // Handle any extra behaviour associated with a key event\n}\n\nmpl.figure.prototype.key_event = function(event, name) {\n\n // Prevent repeat events\n if (name == 'key_press')\n {\n if (event.which === this._key)\n return;\n else\n this._key = event.which;\n }\n if (name == 'key_release')\n this._key = null;\n\n var value = '';\n if (event.ctrlKey && event.which != 17)\n value += \"ctrl+\";\n if (event.altKey && event.which != 18)\n value += \"alt+\";\n if (event.shiftKey && event.which != 16)\n value += \"shift+\";\n\n value += 'k';\n value += event.which.toString();\n\n this._key_event_extra(event, name);\n\n this.send_message(name, {key: value,\n guiEvent: simpleKeys(event)});\n return false;\n}\n\nmpl.figure.prototype.toolbar_button_onclick = function(name) {\n if (name == 'download') {\n this.handle_save(this, null);\n } else {\n this.send_message(\"toolbar_button\", {name: name});\n }\n};\n\nmpl.figure.prototype.toolbar_button_onmouseover = function(tooltip) {\n this.message.textContent = tooltip;\n};\nmpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n\nmpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n\nmpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n // Create a \"websocket\"-like object which calls the given IPython comm\n // object with the appropriate methods. Currently this is a non binary\n // socket, so there is still some room for performance tuning.\n var ws = {};\n\n ws.close = function() {\n comm.close()\n };\n ws.send = function(m) {\n //console.log('sending', m);\n comm.send(m);\n };\n // Register the callback with on_msg.\n comm.on_msg(function(msg) {\n //console.log('receiving', msg['content']['data'], msg);\n // Pass the mpl event to the overriden (by mpl) onmessage function.\n ws.onmessage(msg['content']['data'])\n });\n return ws;\n}\n\nmpl.mpl_figure_comm = function(comm, msg) {\n // This is the function which gets called when the mpl process\n // starts-up an IPython Comm through the \"matplotlib\" channel.\n\n var id = msg.content.data.id;\n // Get hold of the div created by the display call when the Comm\n // socket was opened in Python.\n var element = $(\"#\" + id);\n var ws_proxy = comm_websocket_adapter(comm)\n\n function ondownload(figure, format) {\n window.open(figure.imageObj.src);\n }\n\n var fig = new mpl.figure(id, ws_proxy,\n ondownload,\n element.get(0));\n\n // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n // web socket which is closed, not our websocket->open comm proxy.\n ws_proxy.onopen();\n\n fig.parent_element = element.get(0);\n fig.cell_info = mpl.find_output_cell(\"\");\n if (!fig.cell_info) {\n console.error(\"Failed to find cell for figure\", id, fig);\n return;\n }\n\n var output_index = fig.cell_info[2]\n var cell = fig.cell_info[0];\n\n};\n\nmpl.figure.prototype.handle_close = function(fig, msg) {\n fig.root.unbind('remove')\n\n // Update the output cell to use the data from the current canvas.\n fig.push_to_output();\n var dataURL = fig.canvas.toDataURL();\n // Re-enable the keyboard manager in IPython - without this line, in FF,\n // the notebook keyboard shortcuts fail.\n IPython.keyboard_manager.enable()\n $(fig.parent_element).html('');\n fig.close_ws(fig, msg);\n}\n\nmpl.figure.prototype.close_ws = function(fig, msg){\n fig.send_message('closing', msg);\n // fig.ws.close()\n}\n\nmpl.figure.prototype.push_to_output = function(remove_interactive) {\n // Turn the data on the canvas into data in the output cell.\n var dataURL = this.canvas.toDataURL();\n this.cell_info[1]['text/html'] = '';\n}\n\nmpl.figure.prototype.updated_canvas_event = function() {\n // Tell IPython that the notebook contents must change.\n IPython.notebook.set_dirty(true);\n this.send_message(\"ack\", {});\n var fig = this;\n // Wait a second, then push the new image to the DOM so\n // that it is saved nicely (might be nice to debounce this).\n setTimeout(function () { fig.push_to_output() }, 1000);\n}\n\nmpl.figure.prototype._init_toolbar = function() {\n var fig = this;\n\n var nav_element = $('')\n nav_element.attr('style', 'width: 100%');\n this.root.append(nav_element);\n\n // Define a callback function for later on.\n function toolbar_event(event) {\n return fig.toolbar_button_onclick(event['data']);\n }\n function toolbar_mouse_event(event) {\n return fig.toolbar_button_onmouseover(event['data']);\n }\n\n for(var toolbar_ind in mpl.toolbar_items){\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) { continue; };\n\n var button = $('');\n button.click(method_name, toolbar_event);\n button.mouseover(tooltip, toolbar_mouse_event);\n nav_element.append(button);\n }\n\n // Add the status bar.\n var status_bar = $('');\n nav_element.append(status_bar);\n this.message = status_bar[0];\n\n // Add the close button to the window.\n var buttongrp = $('');\n var button = $('');\n button.click(function (evt) { fig.handle_close(fig, {}); } );\n button.mouseover('Stop Interaction', toolbar_mouse_event);\n buttongrp.append(button);\n var titlebar = this.root.find($('.ui-dialog-titlebar'));\n titlebar.prepend(buttongrp);\n}\n\nmpl.figure.prototype._root_extra_style = function(el){\n var fig = this\n el.on(\"remove\", function(){\n\tfig.close_ws(fig, {});\n });\n}\n\nmpl.figure.prototype._canvas_extra_style = function(el){\n // this is important to make the div 'focusable\n el.attr('tabindex', 0)\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n }\n else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n\n}\n\nmpl.figure.prototype._key_event_extra = function(event, name) {\n var manager = IPython.notebook.keyboard_manager;\n if (!manager)\n manager = IPython.keyboard_manager;\n\n // Check for shift+enter\n if (event.shiftKey && event.which == 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n}\n\nmpl.figure.prototype.handle_save = function(fig, msg) {\n fig.ondownload(fig, null);\n}\n\n\nmpl.find_output_cell = function(html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i=0; i= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] == html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n}\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel != null) {\n IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n}\n",
"text/plain": [
""
]
@@ -963,6 +217,7 @@
]
},
{
+ "attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
@@ -990,6 +245,7 @@
]
},
{
+ "attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
@@ -997,6 +253,7 @@
]
},
{
+ "attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
@@ -1025,6 +282,7 @@
]
},
{
+ "attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
@@ -1089,6 +347,7 @@
]
},
{
+ "attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
@@ -1096,6 +355,7 @@
]
},
{
+ "attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
@@ -1103,6 +363,7 @@
]
},
{
+ "attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
@@ -1152,6 +413,7 @@
]
},
{
+ "attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
@@ -1159,6 +421,7 @@
]
},
{
+ "attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
@@ -1250,6 +513,7 @@
]
},
{
+ "attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
@@ -1269,6 +533,7 @@
]
},
{
+ "attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
@@ -1276,6 +541,7 @@
]
},
{
+ "attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
@@ -1303,6 +569,7 @@
]
},
{
+ "attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
@@ -1359,6 +626,7 @@
]
},
{
+ "attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
@@ -1366,6 +634,7 @@
]
},
{
+ "attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
@@ -1482,6 +751,7 @@
]
},
{
+ "attachments": {},
"cell_type": "markdown",
"metadata": {
"collapsed": true
@@ -1491,6 +761,7 @@
]
},
{
+ "attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
@@ -1509,6 +780,7 @@
]
},
{
+ "attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
@@ -1516,6 +788,7 @@
]
},
{
+ "attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
@@ -1539,6 +812,7 @@
]
},
{
+ "attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
@@ -1578,6 +852,7 @@
]
},
{
+ "attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
@@ -1596,6 +871,7 @@
]
},
{
+ "attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
@@ -1611,758 +887,7 @@
"outputs": [
{
"data": {
- "application/javascript": [
- "/* Put everything inside the global mpl namespace */\n",
- "window.mpl = {};\n",
- "\n",
- "mpl.get_websocket_type = function() {\n",
- " if (typeof(WebSocket) !== 'undefined') {\n",
- " return WebSocket;\n",
- " } else if (typeof(MozWebSocket) !== 'undefined') {\n",
- " return MozWebSocket;\n",
- " } else {\n",
- " alert('Your browser does not have WebSocket support.' +\n",
- " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
- " 'Firefox 4 and 5 are also supported but you ' +\n",
- " 'have to enable WebSockets in about:config.');\n",
- " };\n",
- "}\n",
- "\n",
- "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n",
- " this.id = figure_id;\n",
- "\n",
- " this.ws = websocket;\n",
- "\n",
- " this.supports_binary = (this.ws.binaryType != undefined);\n",
- "\n",
- " if (!this.supports_binary) {\n",
- " var warnings = document.getElementById(\"mpl-warnings\");\n",
- " if (warnings) {\n",
- " warnings.style.display = 'block';\n",
- " warnings.textContent = (\n",
- " \"This browser does not support binary websocket messages. \" +\n",
- " \"Performance may be slow.\");\n",
- " }\n",
- " }\n",
- "\n",
- " this.imageObj = new Image();\n",
- "\n",
- " this.context = undefined;\n",
- " this.message = undefined;\n",
- " this.canvas = undefined;\n",
- " this.rubberband_canvas = undefined;\n",
- " this.rubberband_context = undefined;\n",
- " this.format_dropdown = undefined;\n",
- "\n",
- " this.image_mode = 'full';\n",
- "\n",
- " this.root = $('');\n",
- " this._root_extra_style(this.root)\n",
- " this.root.attr('style', 'display: inline-block');\n",
- "\n",
- " $(parent_element).append(this.root);\n",
- "\n",
- " this._init_header(this);\n",
- " this._init_canvas(this);\n",
- " this._init_toolbar(this);\n",
- "\n",
- " var fig = this;\n",
- "\n",
- " this.waiting = false;\n",
- "\n",
- " this.ws.onopen = function () {\n",
- " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n",
- " fig.send_message(\"send_image_mode\", {});\n",
- " fig.send_message(\"refresh\", {});\n",
- " }\n",
- "\n",
- " this.imageObj.onload = function() {\n",
- " if (fig.image_mode == 'full') {\n",
- " // Full images could contain transparency (where diff images\n",
- " // almost always do), so we need to clear the canvas so that\n",
- " // there is no ghosting.\n",
- " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
- " }\n",
- " fig.context.drawImage(fig.imageObj, 0, 0);\n",
- " };\n",
- "\n",
- " this.imageObj.onunload = function() {\n",
- " this.ws.close();\n",
- " }\n",
- "\n",
- " this.ws.onmessage = this._make_on_message_function(this);\n",
- "\n",
- " this.ondownload = ondownload;\n",
- "}\n",
- "\n",
- "mpl.figure.prototype._init_header = function() {\n",
- " var titlebar = $(\n",
- " '');\n",
- " var titletext = $(\n",
- " '');\n",
- " titlebar.append(titletext)\n",
- " this.root.append(titlebar);\n",
- " this.header = titletext[0];\n",
- "}\n",
- "\n",
- "\n",
- "\n",
- "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n",
- "\n",
- "}\n",
- "\n",
- "\n",
- "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n",
- "\n",
- "}\n",
- "\n",
- "mpl.figure.prototype._init_canvas = function() {\n",
- " var fig = this;\n",
- "\n",
- " var canvas_div = $('');\n",
- "\n",
- " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n",
- "\n",
- " function canvas_keyboard_event(event) {\n",
- " return fig.key_event(event, event['data']);\n",
- " }\n",
- "\n",
- " canvas_div.keydown('key_press', canvas_keyboard_event);\n",
- " canvas_div.keyup('key_release', canvas_keyboard_event);\n",
- " this.canvas_div = canvas_div\n",
- " this._canvas_extra_style(canvas_div)\n",
- " this.root.append(canvas_div);\n",
- "\n",
- " var canvas = $('');\n",
- " canvas.addClass('mpl-canvas');\n",
- " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n",
- "\n",
- " this.canvas = canvas[0];\n",
- " this.context = canvas[0].getContext(\"2d\");\n",
- "\n",
- " var rubberband = $('');\n",
- " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n",
- "\n",
- " var pass_mouse_events = true;\n",
- "\n",
- " canvas_div.resizable({\n",
- " start: function(event, ui) {\n",
- " pass_mouse_events = false;\n",
- " },\n",
- " resize: function(event, ui) {\n",
- " fig.request_resize(ui.size.width, ui.size.height);\n",
- " },\n",
- " stop: function(event, ui) {\n",
- " pass_mouse_events = true;\n",
- " fig.request_resize(ui.size.width, ui.size.height);\n",
- " },\n",
- " });\n",
- "\n",
- " function mouse_event_fn(event) {\n",
- " if (pass_mouse_events)\n",
- " return fig.mouse_event(event, event['data']);\n",
- " }\n",
- "\n",
- " rubberband.mousedown('button_press', mouse_event_fn);\n",
- " rubberband.mouseup('button_release', mouse_event_fn);\n",
- " // Throttle sequential mouse events to 1 every 20ms.\n",
- " rubberband.mousemove('motion_notify', mouse_event_fn);\n",
- "\n",
- " rubberband.mouseenter('figure_enter', mouse_event_fn);\n",
- " rubberband.mouseleave('figure_leave', mouse_event_fn);\n",
- "\n",
- " canvas_div.on(\"wheel\", function (event) {\n",
- " event = event.originalEvent;\n",
- " event['data'] = 'scroll'\n",
- " if (event.deltaY < 0) {\n",
- " event.step = 1;\n",
- " } else {\n",
- " event.step = -1;\n",
- " }\n",
- " mouse_event_fn(event);\n",
- " });\n",
- "\n",
- " canvas_div.append(canvas);\n",
- " canvas_div.append(rubberband);\n",
- "\n",
- " this.rubberband = rubberband;\n",
- " this.rubberband_canvas = rubberband[0];\n",
- " this.rubberband_context = rubberband[0].getContext(\"2d\");\n",
- " this.rubberband_context.strokeStyle = \"#000000\";\n",
- "\n",
- " this._resize_canvas = function(width, height) {\n",
- " // Keep the size of the canvas, canvas container, and rubber band\n",
- " // canvas in synch.\n",
- " canvas_div.css('width', width)\n",
- " canvas_div.css('height', height)\n",
- "\n",
- " canvas.attr('width', width);\n",
- " canvas.attr('height', height);\n",
- "\n",
- " rubberband.attr('width', width);\n",
- " rubberband.attr('height', height);\n",
- " }\n",
- "\n",
- " // Set the figure to an initial 600x600px, this will subsequently be updated\n",
- " // upon first draw.\n",
- " this._resize_canvas(600, 600);\n",
- "\n",
- " // Disable right mouse context menu.\n",
- " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n",
- " return false;\n",
- " });\n",
- "\n",
- " function set_focus () {\n",
- " canvas.focus();\n",
- " canvas_div.focus();\n",
- " }\n",
- "\n",
- " window.setTimeout(set_focus, 100);\n",
- "}\n",
- "\n",
- "mpl.figure.prototype._init_toolbar = function() {\n",
- " var fig = this;\n",
- "\n",
- " var nav_element = $('')\n",
- " nav_element.attr('style', 'width: 100%');\n",
- " this.root.append(nav_element);\n",
- "\n",
- " // Define a callback function for later on.\n",
- " function toolbar_event(event) {\n",
- " return fig.toolbar_button_onclick(event['data']);\n",
- " }\n",
- " function toolbar_mouse_event(event) {\n",
- " return fig.toolbar_button_onmouseover(event['data']);\n",
- " }\n",
- "\n",
- " for(var toolbar_ind in mpl.toolbar_items) {\n",
- " var name = mpl.toolbar_items[toolbar_ind][0];\n",
- " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
- " var image = mpl.toolbar_items[toolbar_ind][2];\n",
- " var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
- "\n",
- " if (!name) {\n",
- " // put a spacer in here.\n",
- " continue;\n",
- " }\n",
- " var button = $('');\n",
- " button.addClass('ui-button ui-widget ui-state-default ui-corner-all ' +\n",
- " 'ui-button-icon-only');\n",
- " button.attr('role', 'button');\n",
- " button.attr('aria-disabled', 'false');\n",
- " button.click(method_name, toolbar_event);\n",
- " button.mouseover(tooltip, toolbar_mouse_event);\n",
- "\n",
- " var icon_img = $('');\n",
- " icon_img.addClass('ui-button-icon-primary ui-icon');\n",
- " icon_img.addClass(image);\n",
- " icon_img.addClass('ui-corner-all');\n",
- "\n",
- " var tooltip_span = $('');\n",
- " tooltip_span.addClass('ui-button-text');\n",
- " tooltip_span.html(tooltip);\n",
- "\n",
- " button.append(icon_img);\n",
- " button.append(tooltip_span);\n",
- "\n",
- " nav_element.append(button);\n",
- " }\n",
- "\n",
- " var fmt_picker_span = $('');\n",
- "\n",
- " var fmt_picker = $('');\n",
- " fmt_picker.addClass('mpl-toolbar-option ui-widget ui-widget-content');\n",
- " fmt_picker_span.append(fmt_picker);\n",
- " nav_element.append(fmt_picker_span);\n",
- " this.format_dropdown = fmt_picker[0];\n",
- "\n",
- " for (var ind in mpl.extensions) {\n",
- " var fmt = mpl.extensions[ind];\n",
- " var option = $(\n",
- " '', {selected: fmt === mpl.default_extension}).html(fmt);\n",
- " fmt_picker.append(option)\n",
- " }\n",
- "\n",
- " // Add hover states to the ui-buttons\n",
- " $( \".ui-button\" ).hover(\n",
- " function() { $(this).addClass(\"ui-state-hover\");},\n",
- " function() { $(this).removeClass(\"ui-state-hover\");}\n",
- " );\n",
- "\n",
- " var status_bar = $('');\n",
- " nav_element.append(status_bar);\n",
- " this.message = status_bar[0];\n",
- "}\n",
- "\n",
- "mpl.figure.prototype.request_resize = function(x_pixels, y_pixels) {\n",
- " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
- " // which will in turn request a refresh of the image.\n",
- " this.send_message('resize', {'width': x_pixels, 'height': y_pixels});\n",
- "}\n",
- "\n",
- "mpl.figure.prototype.send_message = function(type, properties) {\n",
- " properties['type'] = type;\n",
- " properties['figure_id'] = this.id;\n",
- " this.ws.send(JSON.stringify(properties));\n",
- "}\n",
- "\n",
- "mpl.figure.prototype.send_draw_message = function() {\n",
- " if (!this.waiting) {\n",
- " this.waiting = true;\n",
- " this.ws.send(JSON.stringify({type: \"draw\", figure_id: this.id}));\n",
- " }\n",
- "}\n",
- "\n",
- "\n",
- "mpl.figure.prototype.handle_save = function(fig, msg) {\n",
- " var format_dropdown = fig.format_dropdown;\n",
- " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
- " fig.ondownload(fig, format);\n",
- "}\n",
- "\n",
- "\n",
- "mpl.figure.prototype.handle_resize = function(fig, msg) {\n",
- " var size = msg['size'];\n",
- " if (size[0] != fig.canvas.width || size[1] != fig.canvas.height) {\n",
- " fig._resize_canvas(size[0], size[1]);\n",
- " fig.send_message(\"refresh\", {});\n",
- " };\n",
- "}\n",
- "\n",
- "mpl.figure.prototype.handle_rubberband = function(fig, msg) {\n",
- " var x0 = msg['x0'];\n",
- " var y0 = fig.canvas.height - msg['y0'];\n",
- " var x1 = msg['x1'];\n",
- " var y1 = fig.canvas.height - msg['y1'];\n",
- " x0 = Math.floor(x0) + 0.5;\n",
- " y0 = Math.floor(y0) + 0.5;\n",
- " x1 = Math.floor(x1) + 0.5;\n",
- " y1 = Math.floor(y1) + 0.5;\n",
- " var min_x = Math.min(x0, x1);\n",
- " var min_y = Math.min(y0, y1);\n",
- " var width = Math.abs(x1 - x0);\n",
- " var height = Math.abs(y1 - y0);\n",
- "\n",
- " fig.rubberband_context.clearRect(\n",
- " 0, 0, fig.canvas.width, fig.canvas.height);\n",
- "\n",
- " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
- "}\n",
- "\n",
- "mpl.figure.prototype.handle_figure_label = function(fig, msg) {\n",
- " // Updates the figure title.\n",
- " fig.header.textContent = msg['label'];\n",
- "}\n",
- "\n",
- "mpl.figure.prototype.handle_cursor = function(fig, msg) {\n",
- " var cursor = msg['cursor'];\n",
- " switch(cursor)\n",
- " {\n",
- " case 0:\n",
- " cursor = 'pointer';\n",
- " break;\n",
- " case 1:\n",
- " cursor = 'default';\n",
- " break;\n",
- " case 2:\n",
- " cursor = 'crosshair';\n",
- " break;\n",
- " case 3:\n",
- " cursor = 'move';\n",
- " break;\n",
- " }\n",
- " fig.rubberband_canvas.style.cursor = cursor;\n",
- "}\n",
- "\n",
- "mpl.figure.prototype.handle_message = function(fig, msg) {\n",
- " fig.message.textContent = msg['message'];\n",
- "}\n",
- "\n",
- "mpl.figure.prototype.handle_draw = function(fig, msg) {\n",
- " // Request the server to send over a new figure.\n",
- " fig.send_draw_message();\n",
- "}\n",
- "\n",
- "mpl.figure.prototype.handle_image_mode = function(fig, msg) {\n",
- " fig.image_mode = msg['mode'];\n",
- "}\n",
- "\n",
- "mpl.figure.prototype.updated_canvas_event = function() {\n",
- " // Called whenever the canvas gets updated.\n",
- " this.send_message(\"ack\", {});\n",
- "}\n",
- "\n",
- "// A function to construct a web socket function for onmessage handling.\n",
- "// Called in the figure constructor.\n",
- "mpl.figure.prototype._make_on_message_function = function(fig) {\n",
- " return function socket_on_message(evt) {\n",
- " if (evt.data instanceof Blob) {\n",
- " /* FIXME: We get \"Resource interpreted as Image but\n",
- " * transferred with MIME type text/plain:\" errors on\n",
- " * Chrome. But how to set the MIME type? It doesn't seem\n",
- " * to be part of the websocket stream */\n",
- " evt.data.type = \"image/png\";\n",
- "\n",
- " /* Free the memory for the previous frames */\n",
- " if (fig.imageObj.src) {\n",
- " (window.URL || window.webkitURL).revokeObjectURL(\n",
- " fig.imageObj.src);\n",
- " }\n",
- "\n",
- " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
- " evt.data);\n",
- " fig.updated_canvas_event();\n",
- " fig.waiting = false;\n",
- " return;\n",
- " }\n",
- " else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n",
- " fig.imageObj.src = evt.data;\n",
- " fig.updated_canvas_event();\n",
- " fig.waiting = false;\n",
- " return;\n",
- " }\n",
- "\n",
- " var msg = JSON.parse(evt.data);\n",
- " var msg_type = msg['type'];\n",
- "\n",
- " // Call the \"handle_{type}\" callback, which takes\n",
- " // the figure and JSON message as its only arguments.\n",
- " try {\n",
- " var callback = fig[\"handle_\" + msg_type];\n",
- " } catch (e) {\n",
- " console.log(\"No handler for the '\" + msg_type + \"' message type: \", msg);\n",
- " return;\n",
- " }\n",
- "\n",
- " if (callback) {\n",
- " try {\n",
- " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
- " callback(fig, msg);\n",
- " } catch (e) {\n",
- " console.log(\"Exception inside the 'handler_\" + msg_type + \"' callback:\", e, e.stack, msg);\n",
- " }\n",
- " }\n",
- " };\n",
- "}\n",
- "\n",
- "// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
- "mpl.findpos = function(e) {\n",
- " //this section is from http://www.quirksmode.org/js/events_properties.html\n",
- " var targ;\n",
- " if (!e)\n",
- " e = window.event;\n",
- " if (e.target)\n",
- " targ = e.target;\n",
- " else if (e.srcElement)\n",
- " targ = e.srcElement;\n",
- " if (targ.nodeType == 3) // defeat Safari bug\n",
- " targ = targ.parentNode;\n",
- "\n",
- " // jQuery normalizes the pageX and pageY\n",
- " // pageX,Y are the mouse positions relative to the document\n",
- " // offset() returns the position of the element relative to the document\n",
- " var x = e.pageX - $(targ).offset().left;\n",
- " var y = e.pageY - $(targ).offset().top;\n",
- "\n",
- " return {\"x\": x, \"y\": y};\n",
- "};\n",
- "\n",
- "/*\n",
- " * return a copy of an object with only non-object keys\n",
- " * we need this to avoid circular references\n",
- " * http://stackoverflow.com/a/24161582/3208463\n",
- " */\n",
- "function simpleKeys (original) {\n",
- " return Object.keys(original).reduce(function (obj, key) {\n",
- " if (typeof original[key] !== 'object')\n",
- " obj[key] = original[key]\n",
- " return obj;\n",
- " }, {});\n",
- "}\n",
- "\n",
- "mpl.figure.prototype.mouse_event = function(event, name) {\n",
- " var canvas_pos = mpl.findpos(event)\n",
- "\n",
- " if (name === 'button_press')\n",
- " {\n",
- " this.canvas.focus();\n",
- " this.canvas_div.focus();\n",
- " }\n",
- "\n",
- " var x = canvas_pos.x;\n",
- " var y = canvas_pos.y;\n",
- "\n",
- " this.send_message(name, {x: x, y: y, button: event.button,\n",
- " step: event.step,\n",
- " guiEvent: simpleKeys(event)});\n",
- "\n",
- " /* This prevents the web browser from automatically changing to\n",
- " * the text insertion cursor when the button is pressed. We want\n",
- " * to control all of the cursor setting manually through the\n",
- " * 'cursor' event from matplotlib */\n",
- " event.preventDefault();\n",
- " return false;\n",
- "}\n",
- "\n",
- "mpl.figure.prototype._key_event_extra = function(event, name) {\n",
- " // Handle any extra behaviour associated with a key event\n",
- "}\n",
- "\n",
- "mpl.figure.prototype.key_event = function(event, name) {\n",
- "\n",
- " // Prevent repeat events\n",
- " if (name == 'key_press')\n",
- " {\n",
- " if (event.which === this._key)\n",
- " return;\n",
- " else\n",
- " this._key = event.which;\n",
- " }\n",
- " if (name == 'key_release')\n",
- " this._key = null;\n",
- "\n",
- " var value = '';\n",
- " if (event.ctrlKey && event.which != 17)\n",
- " value += \"ctrl+\";\n",
- " if (event.altKey && event.which != 18)\n",
- " value += \"alt+\";\n",
- " if (event.shiftKey && event.which != 16)\n",
- " value += \"shift+\";\n",
- "\n",
- " value += 'k';\n",
- " value += event.which.toString();\n",
- "\n",
- " this._key_event_extra(event, name);\n",
- "\n",
- " this.send_message(name, {key: value,\n",
- " guiEvent: simpleKeys(event)});\n",
- " return false;\n",
- "}\n",
- "\n",
- "mpl.figure.prototype.toolbar_button_onclick = function(name) {\n",
- " if (name == 'download') {\n",
- " this.handle_save(this, null);\n",
- " } else {\n",
- " this.send_message(\"toolbar_button\", {name: name});\n",
- " }\n",
- "};\n",
- "\n",
- "mpl.figure.prototype.toolbar_button_onmouseover = function(tooltip) {\n",
- " this.message.textContent = tooltip;\n",
- "};\n",
- "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
- "\n",
- "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n",
- "\n",
- "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n",
- " // Create a \"websocket\"-like object which calls the given IPython comm\n",
- " // object with the appropriate methods. Currently this is a non binary\n",
- " // socket, so there is still some room for performance tuning.\n",
- " var ws = {};\n",
- "\n",
- " ws.close = function() {\n",
- " comm.close()\n",
- " };\n",
- " ws.send = function(m) {\n",
- " //console.log('sending', m);\n",
- " comm.send(m);\n",
- " };\n",
- " // Register the callback with on_msg.\n",
- " comm.on_msg(function(msg) {\n",
- " //console.log('receiving', msg['content']['data'], msg);\n",
- " // Pass the mpl event to the overriden (by mpl) onmessage function.\n",
- " ws.onmessage(msg['content']['data'])\n",
- " });\n",
- " return ws;\n",
- "}\n",
- "\n",
- "mpl.mpl_figure_comm = function(comm, msg) {\n",
- " // This is the function which gets called when the mpl process\n",
- " // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
- "\n",
- " var id = msg.content.data.id;\n",
- " // Get hold of the div created by the display call when the Comm\n",
- " // socket was opened in Python.\n",
- " var element = $(\"#\" + id);\n",
- " var ws_proxy = comm_websocket_adapter(comm)\n",
- "\n",
- " function ondownload(figure, format) {\n",
- " window.open(figure.imageObj.src);\n",
- " }\n",
- "\n",
- " var fig = new mpl.figure(id, ws_proxy,\n",
- " ondownload,\n",
- " element.get(0));\n",
- "\n",
- " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
- " // web socket which is closed, not our websocket->open comm proxy.\n",
- " ws_proxy.onopen();\n",
- "\n",
- " fig.parent_element = element.get(0);\n",
- " fig.cell_info = mpl.find_output_cell(\"\");\n",
- " if (!fig.cell_info) {\n",
- " console.error(\"Failed to find cell for figure\", id, fig);\n",
- " return;\n",
- " }\n",
- "\n",
- " var output_index = fig.cell_info[2]\n",
- " var cell = fig.cell_info[0];\n",
- "\n",
- "};\n",
- "\n",
- "mpl.figure.prototype.handle_close = function(fig, msg) {\n",
- " fig.root.unbind('remove')\n",
- "\n",
- " // Update the output cell to use the data from the current canvas.\n",
- " fig.push_to_output();\n",
- " var dataURL = fig.canvas.toDataURL();\n",
- " // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
- " // the notebook keyboard shortcuts fail.\n",
- " IPython.keyboard_manager.enable()\n",
- " $(fig.parent_element).html('');\n",
- " fig.close_ws(fig, msg);\n",
- "}\n",
- "\n",
- "mpl.figure.prototype.close_ws = function(fig, msg){\n",
- " fig.send_message('closing', msg);\n",
- " // fig.ws.close()\n",
- "}\n",
- "\n",
- "mpl.figure.prototype.push_to_output = function(remove_interactive) {\n",
- " // Turn the data on the canvas into data in the output cell.\n",
- " var dataURL = this.canvas.toDataURL();\n",
- " this.cell_info[1]['text/html'] = '';\n",
- "}\n",
- "\n",
- "mpl.figure.prototype.updated_canvas_event = function() {\n",
- " // Tell IPython that the notebook contents must change.\n",
- " IPython.notebook.set_dirty(true);\n",
- " this.send_message(\"ack\", {});\n",
- " var fig = this;\n",
- " // Wait a second, then push the new image to the DOM so\n",
- " // that it is saved nicely (might be nice to debounce this).\n",
- " setTimeout(function () { fig.push_to_output() }, 1000);\n",
- "}\n",
- "\n",
- "mpl.figure.prototype._init_toolbar = function() {\n",
- " var fig = this;\n",
- "\n",
- " var nav_element = $('')\n",
- " nav_element.attr('style', 'width: 100%');\n",
- " this.root.append(nav_element);\n",
- "\n",
- " // Define a callback function for later on.\n",
- " function toolbar_event(event) {\n",
- " return fig.toolbar_button_onclick(event['data']);\n",
- " }\n",
- " function toolbar_mouse_event(event) {\n",
- " return fig.toolbar_button_onmouseover(event['data']);\n",
- " }\n",
- "\n",
- " for(var toolbar_ind in mpl.toolbar_items){\n",
- " var name = mpl.toolbar_items[toolbar_ind][0];\n",
- " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
- " var image = mpl.toolbar_items[toolbar_ind][2];\n",
- " var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
- "\n",
- " if (!name) { continue; };\n",
- "\n",
- " var button = $('');\n",
- " button.click(method_name, toolbar_event);\n",
- " button.mouseover(tooltip, toolbar_mouse_event);\n",
- " nav_element.append(button);\n",
- " }\n",
- "\n",
- " // Add the status bar.\n",
- " var status_bar = $('');\n",
- " nav_element.append(status_bar);\n",
- " this.message = status_bar[0];\n",
- "\n",
- " // Add the close button to the window.\n",
- " var buttongrp = $('');\n",
- " var button = $('');\n",
- " button.click(function (evt) { fig.handle_close(fig, {}); } );\n",
- " button.mouseover('Stop Interaction', toolbar_mouse_event);\n",
- " buttongrp.append(button);\n",
- " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n",
- " titlebar.prepend(buttongrp);\n",
- "}\n",
- "\n",
- "mpl.figure.prototype._root_extra_style = function(el){\n",
- " var fig = this\n",
- " el.on(\"remove\", function(){\n",
- "\tfig.close_ws(fig, {});\n",
- " });\n",
- "}\n",
- "\n",
- "mpl.figure.prototype._canvas_extra_style = function(el){\n",
- " // this is important to make the div 'focusable\n",
- " el.attr('tabindex', 0)\n",
- " // reach out to IPython and tell the keyboard manager to turn it's self\n",
- " // off when our div gets focus\n",
- "\n",
- " // location in version 3\n",
- " if (IPython.notebook.keyboard_manager) {\n",
- " IPython.notebook.keyboard_manager.register_events(el);\n",
- " }\n",
- " else {\n",
- " // location in version 2\n",
- " IPython.keyboard_manager.register_events(el);\n",
- " }\n",
- "\n",
- "}\n",
- "\n",
- "mpl.figure.prototype._key_event_extra = function(event, name) {\n",
- " var manager = IPython.notebook.keyboard_manager;\n",
- " if (!manager)\n",
- " manager = IPython.keyboard_manager;\n",
- "\n",
- " // Check for shift+enter\n",
- " if (event.shiftKey && event.which == 13) {\n",
- " this.canvas_div.blur();\n",
- " // select the cell after this one\n",
- " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n",
- " IPython.notebook.select(index + 1);\n",
- " }\n",
- "}\n",
- "\n",
- "mpl.figure.prototype.handle_save = function(fig, msg) {\n",
- " fig.ondownload(fig, null);\n",
- "}\n",
- "\n",
- "\n",
- "mpl.find_output_cell = function(html_output) {\n",
- " // Return the cell and output element which can be found *uniquely* in the notebook.\n",
- " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
- " // IPython event is triggered only after the cells have been serialised, which for\n",
- " // our purposes (turning an active figure into a static one), is too late.\n",
- " var cells = IPython.notebook.get_cells();\n",
- " var ncells = cells.length;\n",
- " for (var i=0; i= 3 moved mimebundle to data attribute of output\n",
- " data = data.data;\n",
- " }\n",
- " if (data['text/html'] == html_output) {\n",
- " return [cell, data, j];\n",
- " }\n",
- " }\n",
- " }\n",
- " }\n",
- "}\n",
- "\n",
- "// Register the function which deals with the matplotlib target/channel.\n",
- "// The kernel may be null if the page has been refreshed.\n",
- "if (IPython.notebook.kernel != null) {\n",
- " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n",
- "}\n"
- ],
+ "application/javascript": "/* Put everything inside the global mpl namespace */\nwindow.mpl = {};\n\nmpl.get_websocket_type = function() {\n if (typeof(WebSocket) !== 'undefined') {\n return WebSocket;\n } else if (typeof(MozWebSocket) !== 'undefined') {\n return MozWebSocket;\n } else {\n alert('Your browser does not have WebSocket support.' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.');\n };\n}\n\nmpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = (this.ws.binaryType != undefined);\n\n if (!this.supports_binary) {\n var warnings = document.getElementById(\"mpl-warnings\");\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent = (\n \"This browser does not support binary websocket messages. \" +\n \"Performance may be slow.\");\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = $('');\n this._root_extra_style(this.root)\n this.root.attr('style', 'display: inline-block');\n\n $(parent_element).append(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n fig.send_message(\"send_image_mode\", {});\n fig.send_message(\"refresh\", {});\n }\n\n this.imageObj.onload = function() {\n if (fig.image_mode == 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function() {\n this.ws.close();\n }\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n}\n\nmpl.figure.prototype._init_header = function() {\n var titlebar = $(\n '');\n var titletext = $(\n '');\n titlebar.append(titletext)\n this.root.append(titlebar);\n this.header = titletext[0];\n}\n\n\n\nmpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n\n}\n\n\nmpl.figure.prototype._root_extra_style = function(canvas_div) {\n\n}\n\nmpl.figure.prototype._init_canvas = function() {\n var fig = this;\n\n var canvas_div = $('');\n\n canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n\n function canvas_keyboard_event(event) {\n return fig.key_event(event, event['data']);\n }\n\n canvas_div.keydown('key_press', canvas_keyboard_event);\n canvas_div.keyup('key_release', canvas_keyboard_event);\n this.canvas_div = canvas_div\n this._canvas_extra_style(canvas_div)\n this.root.append(canvas_div);\n\n var canvas = $('');\n canvas.addClass('mpl-canvas');\n canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n\n this.canvas = canvas[0];\n this.context = canvas[0].getContext(\"2d\");\n\n var rubberband = $('');\n rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n\n var pass_mouse_events = true;\n\n canvas_div.resizable({\n start: function(event, ui) {\n pass_mouse_events = false;\n },\n resize: function(event, ui) {\n fig.request_resize(ui.size.width, ui.size.height);\n },\n stop: function(event, ui) {\n pass_mouse_events = true;\n fig.request_resize(ui.size.width, ui.size.height);\n },\n });\n\n function mouse_event_fn(event) {\n if (pass_mouse_events)\n return fig.mouse_event(event, event['data']);\n }\n\n rubberband.mousedown('button_press', mouse_event_fn);\n rubberband.mouseup('button_release', mouse_event_fn);\n // Throttle sequential mouse events to 1 every 20ms.\n rubberband.mousemove('motion_notify', mouse_event_fn);\n\n rubberband.mouseenter('figure_enter', mouse_event_fn);\n rubberband.mouseleave('figure_leave', mouse_event_fn);\n\n canvas_div.on(\"wheel\", function (event) {\n event = event.originalEvent;\n event['data'] = 'scroll'\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n mouse_event_fn(event);\n });\n\n canvas_div.append(canvas);\n canvas_div.append(rubberband);\n\n this.rubberband = rubberband;\n this.rubberband_canvas = rubberband[0];\n this.rubberband_context = rubberband[0].getContext(\"2d\");\n this.rubberband_context.strokeStyle = \"#000000\";\n\n this._resize_canvas = function(width, height) {\n // Keep the size of the canvas, canvas container, and rubber band\n // canvas in synch.\n canvas_div.css('width', width)\n canvas_div.css('height', height)\n\n canvas.attr('width', width);\n canvas.attr('height', height);\n\n rubberband.attr('width', width);\n rubberband.attr('height', height);\n }\n\n // Set the figure to an initial 600x600px, this will subsequently be updated\n // upon first draw.\n this._resize_canvas(600, 600);\n\n // Disable right mouse context menu.\n $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n return false;\n });\n\n function set_focus () {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n}\n\nmpl.figure.prototype._init_toolbar = function() {\n var fig = this;\n\n var nav_element = $('')\n nav_element.attr('style', 'width: 100%');\n this.root.append(nav_element);\n\n // Define a callback function for later on.\n function toolbar_event(event) {\n return fig.toolbar_button_onclick(event['data']);\n }\n function toolbar_mouse_event(event) {\n return fig.toolbar_button_onmouseover(event['data']);\n }\n\n for(var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n // put a spacer in here.\n continue;\n }\n var button = $('');\n button.addClass('ui-button ui-widget ui-state-default ui-corner-all ' +\n 'ui-button-icon-only');\n button.attr('role', 'button');\n button.attr('aria-disabled', 'false');\n button.click(method_name, toolbar_event);\n button.mouseover(tooltip, toolbar_mouse_event);\n\n var icon_img = $('');\n icon_img.addClass('ui-button-icon-primary ui-icon');\n icon_img.addClass(image);\n icon_img.addClass('ui-corner-all');\n\n var tooltip_span = $('');\n tooltip_span.addClass('ui-button-text');\n tooltip_span.html(tooltip);\n\n button.append(icon_img);\n button.append(tooltip_span);\n\n nav_element.append(button);\n }\n\n var fmt_picker_span = $('');\n\n var fmt_picker = $('');\n fmt_picker.addClass('mpl-toolbar-option ui-widget ui-widget-content');\n fmt_picker_span.append(fmt_picker);\n nav_element.append(fmt_picker_span);\n this.format_dropdown = fmt_picker[0];\n\n for (var ind in mpl.extensions) {\n var fmt = mpl.extensions[ind];\n var option = $(\n '', {selected: fmt === mpl.default_extension}).html(fmt);\n fmt_picker.append(option)\n }\n\n // Add hover states to the ui-buttons\n $( \".ui-button\" ).hover(\n function() { $(this).addClass(\"ui-state-hover\");},\n function() { $(this).removeClass(\"ui-state-hover\");}\n );\n\n var status_bar = $('');\n nav_element.append(status_bar);\n this.message = status_bar[0];\n}\n\nmpl.figure.prototype.request_resize = function(x_pixels, y_pixels) {\n // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n // which will in turn request a refresh of the image.\n this.send_message('resize', {'width': x_pixels, 'height': y_pixels});\n}\n\nmpl.figure.prototype.send_message = function(type, properties) {\n properties['type'] = type;\n properties['figure_id'] = this.id;\n this.ws.send(JSON.stringify(properties));\n}\n\nmpl.figure.prototype.send_draw_message = function() {\n if (!this.waiting) {\n this.waiting = true;\n this.ws.send(JSON.stringify({type: \"draw\", figure_id: this.id}));\n }\n}\n\n\nmpl.figure.prototype.handle_save = function(fig, msg) {\n var format_dropdown = fig.format_dropdown;\n var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n fig.ondownload(fig, format);\n}\n\n\nmpl.figure.prototype.handle_resize = function(fig, msg) {\n var size = msg['size'];\n if (size[0] != fig.canvas.width || size[1] != fig.canvas.height) {\n fig._resize_canvas(size[0], size[1]);\n fig.send_message(\"refresh\", {});\n };\n}\n\nmpl.figure.prototype.handle_rubberband = function(fig, msg) {\n var x0 = msg['x0'];\n var y0 = fig.canvas.height - msg['y0'];\n var x1 = msg['x1'];\n var y1 = fig.canvas.height - msg['y1'];\n x0 = Math.floor(x0) + 0.5;\n y0 = Math.floor(y0) + 0.5;\n x1 = Math.floor(x1) + 0.5;\n y1 = Math.floor(y1) + 0.5;\n var min_x = Math.min(x0, x1);\n var min_y = Math.min(y0, y1);\n var width = Math.abs(x1 - x0);\n var height = Math.abs(y1 - y0);\n\n fig.rubberband_context.clearRect(\n 0, 0, fig.canvas.width, fig.canvas.height);\n\n fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n}\n\nmpl.figure.prototype.handle_figure_label = function(fig, msg) {\n // Updates the figure title.\n fig.header.textContent = msg['label'];\n}\n\nmpl.figure.prototype.handle_cursor = function(fig, msg) {\n var cursor = msg['cursor'];\n switch(cursor)\n {\n case 0:\n cursor = 'pointer';\n break;\n case 1:\n cursor = 'default';\n break;\n case 2:\n cursor = 'crosshair';\n break;\n case 3:\n cursor = 'move';\n break;\n }\n fig.rubberband_canvas.style.cursor = cursor;\n}\n\nmpl.figure.prototype.handle_message = function(fig, msg) {\n fig.message.textContent = msg['message'];\n}\n\nmpl.figure.prototype.handle_draw = function(fig, msg) {\n // Request the server to send over a new figure.\n fig.send_draw_message();\n}\n\nmpl.figure.prototype.handle_image_mode = function(fig, msg) {\n fig.image_mode = msg['mode'];\n}\n\nmpl.figure.prototype.updated_canvas_event = function() {\n // Called whenever the canvas gets updated.\n this.send_message(\"ack\", {});\n}\n\n// A function to construct a web socket function for onmessage handling.\n// Called in the figure constructor.\nmpl.figure.prototype._make_on_message_function = function(fig) {\n return function socket_on_message(evt) {\n if (evt.data instanceof Blob) {\n /* FIXME: We get \"Resource interpreted as Image but\n * transferred with MIME type text/plain:\" errors on\n * Chrome. But how to set the MIME type? It doesn't seem\n * to be part of the websocket stream */\n evt.data.type = \"image/png\";\n\n /* Free the memory for the previous frames */\n if (fig.imageObj.src) {\n (window.URL || window.webkitURL).revokeObjectURL(\n fig.imageObj.src);\n }\n\n fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n evt.data);\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n }\n else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n fig.imageObj.src = evt.data;\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n }\n\n var msg = JSON.parse(evt.data);\n var msg_type = msg['type'];\n\n // Call the \"handle_{type}\" callback, which takes\n // the figure and JSON message as its only arguments.\n try {\n var callback = fig[\"handle_\" + msg_type];\n } catch (e) {\n console.log(\"No handler for the '\" + msg_type + \"' message type: \", msg);\n return;\n }\n\n if (callback) {\n try {\n // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n callback(fig, msg);\n } catch (e) {\n console.log(\"Exception inside the 'handler_\" + msg_type + \"' callback:\", e, e.stack, msg);\n }\n }\n };\n}\n\n// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\nmpl.findpos = function(e) {\n //this section is from http://www.quirksmode.org/js/events_properties.html\n var targ;\n if (!e)\n e = window.event;\n if (e.target)\n targ = e.target;\n else if (e.srcElement)\n targ = e.srcElement;\n if (targ.nodeType == 3) // defeat Safari bug\n targ = targ.parentNode;\n\n // jQuery normalizes the pageX and pageY\n // pageX,Y are the mouse positions relative to the document\n // offset() returns the position of the element relative to the document\n var x = e.pageX - $(targ).offset().left;\n var y = e.pageY - $(targ).offset().top;\n\n return {\"x\": x, \"y\": y};\n};\n\n/*\n * return a copy of an object with only non-object keys\n * we need this to avoid circular references\n * http://stackoverflow.com/a/24161582/3208463\n */\nfunction simpleKeys (original) {\n return Object.keys(original).reduce(function (obj, key) {\n if (typeof original[key] !== 'object')\n obj[key] = original[key]\n return obj;\n }, {});\n}\n\nmpl.figure.prototype.mouse_event = function(event, name) {\n var canvas_pos = mpl.findpos(event)\n\n if (name === 'button_press')\n {\n this.canvas.focus();\n this.canvas_div.focus();\n }\n\n var x = canvas_pos.x;\n var y = canvas_pos.y;\n\n this.send_message(name, {x: x, y: y, button: event.button,\n step: event.step,\n guiEvent: simpleKeys(event)});\n\n /* This prevents the web browser from automatically changing to\n * the text insertion cursor when the button is pressed. We want\n * to control all of the cursor setting manually through the\n * 'cursor' event from matplotlib */\n event.preventDefault();\n return false;\n}\n\nmpl.figure.prototype._key_event_extra = function(event, name) {\n // Handle any extra behaviour associated with a key event\n}\n\nmpl.figure.prototype.key_event = function(event, name) {\n\n // Prevent repeat events\n if (name == 'key_press')\n {\n if (event.which === this._key)\n return;\n else\n this._key = event.which;\n }\n if (name == 'key_release')\n this._key = null;\n\n var value = '';\n if (event.ctrlKey && event.which != 17)\n value += \"ctrl+\";\n if (event.altKey && event.which != 18)\n value += \"alt+\";\n if (event.shiftKey && event.which != 16)\n value += \"shift+\";\n\n value += 'k';\n value += event.which.toString();\n\n this._key_event_extra(event, name);\n\n this.send_message(name, {key: value,\n guiEvent: simpleKeys(event)});\n return false;\n}\n\nmpl.figure.prototype.toolbar_button_onclick = function(name) {\n if (name == 'download') {\n this.handle_save(this, null);\n } else {\n this.send_message(\"toolbar_button\", {name: name});\n }\n};\n\nmpl.figure.prototype.toolbar_button_onmouseover = function(tooltip) {\n this.message.textContent = tooltip;\n};\nmpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n\nmpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n\nmpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n // Create a \"websocket\"-like object which calls the given IPython comm\n // object with the appropriate methods. Currently this is a non binary\n // socket, so there is still some room for performance tuning.\n var ws = {};\n\n ws.close = function() {\n comm.close()\n };\n ws.send = function(m) {\n //console.log('sending', m);\n comm.send(m);\n };\n // Register the callback with on_msg.\n comm.on_msg(function(msg) {\n //console.log('receiving', msg['content']['data'], msg);\n // Pass the mpl event to the overriden (by mpl) onmessage function.\n ws.onmessage(msg['content']['data'])\n });\n return ws;\n}\n\nmpl.mpl_figure_comm = function(comm, msg) {\n // This is the function which gets called when the mpl process\n // starts-up an IPython Comm through the \"matplotlib\" channel.\n\n var id = msg.content.data.id;\n // Get hold of the div created by the display call when the Comm\n // socket was opened in Python.\n var element = $(\"#\" + id);\n var ws_proxy = comm_websocket_adapter(comm)\n\n function ondownload(figure, format) {\n window.open(figure.imageObj.src);\n }\n\n var fig = new mpl.figure(id, ws_proxy,\n ondownload,\n element.get(0));\n\n // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n // web socket which is closed, not our websocket->open comm proxy.\n ws_proxy.onopen();\n\n fig.parent_element = element.get(0);\n fig.cell_info = mpl.find_output_cell(\"\");\n if (!fig.cell_info) {\n console.error(\"Failed to find cell for figure\", id, fig);\n return;\n }\n\n var output_index = fig.cell_info[2]\n var cell = fig.cell_info[0];\n\n};\n\nmpl.figure.prototype.handle_close = function(fig, msg) {\n fig.root.unbind('remove')\n\n // Update the output cell to use the data from the current canvas.\n fig.push_to_output();\n var dataURL = fig.canvas.toDataURL();\n // Re-enable the keyboard manager in IPython - without this line, in FF,\n // the notebook keyboard shortcuts fail.\n IPython.keyboard_manager.enable()\n $(fig.parent_element).html('');\n fig.close_ws(fig, msg);\n}\n\nmpl.figure.prototype.close_ws = function(fig, msg){\n fig.send_message('closing', msg);\n // fig.ws.close()\n}\n\nmpl.figure.prototype.push_to_output = function(remove_interactive) {\n // Turn the data on the canvas into data in the output cell.\n var dataURL = this.canvas.toDataURL();\n this.cell_info[1]['text/html'] = '';\n}\n\nmpl.figure.prototype.updated_canvas_event = function() {\n // Tell IPython that the notebook contents must change.\n IPython.notebook.set_dirty(true);\n this.send_message(\"ack\", {});\n var fig = this;\n // Wait a second, then push the new image to the DOM so\n // that it is saved nicely (might be nice to debounce this).\n setTimeout(function () { fig.push_to_output() }, 1000);\n}\n\nmpl.figure.prototype._init_toolbar = function() {\n var fig = this;\n\n var nav_element = $('')\n nav_element.attr('style', 'width: 100%');\n this.root.append(nav_element);\n\n // Define a callback function for later on.\n function toolbar_event(event) {\n return fig.toolbar_button_onclick(event['data']);\n }\n function toolbar_mouse_event(event) {\n return fig.toolbar_button_onmouseover(event['data']);\n }\n\n for(var toolbar_ind in mpl.toolbar_items){\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) { continue; };\n\n var button = $('');\n button.click(method_name, toolbar_event);\n button.mouseover(tooltip, toolbar_mouse_event);\n nav_element.append(button);\n }\n\n // Add the status bar.\n var status_bar = $('');\n nav_element.append(status_bar);\n this.message = status_bar[0];\n\n // Add the close button to the window.\n var buttongrp = $('');\n var button = $('');\n button.click(function (evt) { fig.handle_close(fig, {}); } );\n button.mouseover('Stop Interaction', toolbar_mouse_event);\n buttongrp.append(button);\n var titlebar = this.root.find($('.ui-dialog-titlebar'));\n titlebar.prepend(buttongrp);\n}\n\nmpl.figure.prototype._root_extra_style = function(el){\n var fig = this\n el.on(\"remove\", function(){\n\tfig.close_ws(fig, {});\n });\n}\n\nmpl.figure.prototype._canvas_extra_style = function(el){\n // this is important to make the div 'focusable\n el.attr('tabindex', 0)\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n }\n else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n\n}\n\nmpl.figure.prototype._key_event_extra = function(event, name) {\n var manager = IPython.notebook.keyboard_manager;\n if (!manager)\n manager = IPython.keyboard_manager;\n\n // Check for shift+enter\n if (event.shiftKey && event.which == 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n}\n\nmpl.figure.prototype.handle_save = function(fig, msg) {\n fig.ondownload(fig, null);\n}\n\n\nmpl.find_output_cell = function(html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i=0; i= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] == html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n}\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel != null) {\n IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n}\n",
"text/plain": [
""
]
@@ -2419,6 +944,7 @@
]
},
{
+ "attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
@@ -2444,6 +970,7 @@
]
},
{
+ "attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
@@ -2470,6 +997,7 @@
]
},
{
+ "attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
@@ -2518,6 +1046,7 @@
]
},
{
+ "attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
@@ -2536,6 +1065,7 @@
]
},
{
+ "attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
@@ -2553,758 +1083,7 @@
"outputs": [
{
"data": {
- "application/javascript": [
- "/* Put everything inside the global mpl namespace */\n",
- "window.mpl = {};\n",
- "\n",
- "mpl.get_websocket_type = function() {\n",
- " if (typeof(WebSocket) !== 'undefined') {\n",
- " return WebSocket;\n",
- " } else if (typeof(MozWebSocket) !== 'undefined') {\n",
- " return MozWebSocket;\n",
- " } else {\n",
- " alert('Your browser does not have WebSocket support.' +\n",
- " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
- " 'Firefox 4 and 5 are also supported but you ' +\n",
- " 'have to enable WebSockets in about:config.');\n",
- " };\n",
- "}\n",
- "\n",
- "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n",
- " this.id = figure_id;\n",
- "\n",
- " this.ws = websocket;\n",
- "\n",
- " this.supports_binary = (this.ws.binaryType != undefined);\n",
- "\n",
- " if (!this.supports_binary) {\n",
- " var warnings = document.getElementById(\"mpl-warnings\");\n",
- " if (warnings) {\n",
- " warnings.style.display = 'block';\n",
- " warnings.textContent = (\n",
- " \"This browser does not support binary websocket messages. \" +\n",
- " \"Performance may be slow.\");\n",
- " }\n",
- " }\n",
- "\n",
- " this.imageObj = new Image();\n",
- "\n",
- " this.context = undefined;\n",
- " this.message = undefined;\n",
- " this.canvas = undefined;\n",
- " this.rubberband_canvas = undefined;\n",
- " this.rubberband_context = undefined;\n",
- " this.format_dropdown = undefined;\n",
- "\n",
- " this.image_mode = 'full';\n",
- "\n",
- " this.root = $('');\n",
- " this._root_extra_style(this.root)\n",
- " this.root.attr('style', 'display: inline-block');\n",
- "\n",
- " $(parent_element).append(this.root);\n",
- "\n",
- " this._init_header(this);\n",
- " this._init_canvas(this);\n",
- " this._init_toolbar(this);\n",
- "\n",
- " var fig = this;\n",
- "\n",
- " this.waiting = false;\n",
- "\n",
- " this.ws.onopen = function () {\n",
- " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n",
- " fig.send_message(\"send_image_mode\", {});\n",
- " fig.send_message(\"refresh\", {});\n",
- " }\n",
- "\n",
- " this.imageObj.onload = function() {\n",
- " if (fig.image_mode == 'full') {\n",
- " // Full images could contain transparency (where diff images\n",
- " // almost always do), so we need to clear the canvas so that\n",
- " // there is no ghosting.\n",
- " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
- " }\n",
- " fig.context.drawImage(fig.imageObj, 0, 0);\n",
- " };\n",
- "\n",
- " this.imageObj.onunload = function() {\n",
- " this.ws.close();\n",
- " }\n",
- "\n",
- " this.ws.onmessage = this._make_on_message_function(this);\n",
- "\n",
- " this.ondownload = ondownload;\n",
- "}\n",
- "\n",
- "mpl.figure.prototype._init_header = function() {\n",
- " var titlebar = $(\n",
- " '');\n",
- " var titletext = $(\n",
- " '');\n",
- " titlebar.append(titletext)\n",
- " this.root.append(titlebar);\n",
- " this.header = titletext[0];\n",
- "}\n",
- "\n",
- "\n",
- "\n",
- "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n",
- "\n",
- "}\n",
- "\n",
- "\n",
- "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n",
- "\n",
- "}\n",
- "\n",
- "mpl.figure.prototype._init_canvas = function() {\n",
- " var fig = this;\n",
- "\n",
- " var canvas_div = $('');\n",
- "\n",
- " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n",
- "\n",
- " function canvas_keyboard_event(event) {\n",
- " return fig.key_event(event, event['data']);\n",
- " }\n",
- "\n",
- " canvas_div.keydown('key_press', canvas_keyboard_event);\n",
- " canvas_div.keyup('key_release', canvas_keyboard_event);\n",
- " this.canvas_div = canvas_div\n",
- " this._canvas_extra_style(canvas_div)\n",
- " this.root.append(canvas_div);\n",
- "\n",
- " var canvas = $('');\n",
- " canvas.addClass('mpl-canvas');\n",
- " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n",
- "\n",
- " this.canvas = canvas[0];\n",
- " this.context = canvas[0].getContext(\"2d\");\n",
- "\n",
- " var rubberband = $('');\n",
- " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n",
- "\n",
- " var pass_mouse_events = true;\n",
- "\n",
- " canvas_div.resizable({\n",
- " start: function(event, ui) {\n",
- " pass_mouse_events = false;\n",
- " },\n",
- " resize: function(event, ui) {\n",
- " fig.request_resize(ui.size.width, ui.size.height);\n",
- " },\n",
- " stop: function(event, ui) {\n",
- " pass_mouse_events = true;\n",
- " fig.request_resize(ui.size.width, ui.size.height);\n",
- " },\n",
- " });\n",
- "\n",
- " function mouse_event_fn(event) {\n",
- " if (pass_mouse_events)\n",
- " return fig.mouse_event(event, event['data']);\n",
- " }\n",
- "\n",
- " rubberband.mousedown('button_press', mouse_event_fn);\n",
- " rubberband.mouseup('button_release', mouse_event_fn);\n",
- " // Throttle sequential mouse events to 1 every 20ms.\n",
- " rubberband.mousemove('motion_notify', mouse_event_fn);\n",
- "\n",
- " rubberband.mouseenter('figure_enter', mouse_event_fn);\n",
- " rubberband.mouseleave('figure_leave', mouse_event_fn);\n",
- "\n",
- " canvas_div.on(\"wheel\", function (event) {\n",
- " event = event.originalEvent;\n",
- " event['data'] = 'scroll'\n",
- " if (event.deltaY < 0) {\n",
- " event.step = 1;\n",
- " } else {\n",
- " event.step = -1;\n",
- " }\n",
- " mouse_event_fn(event);\n",
- " });\n",
- "\n",
- " canvas_div.append(canvas);\n",
- " canvas_div.append(rubberband);\n",
- "\n",
- " this.rubberband = rubberband;\n",
- " this.rubberband_canvas = rubberband[0];\n",
- " this.rubberband_context = rubberband[0].getContext(\"2d\");\n",
- " this.rubberband_context.strokeStyle = \"#000000\";\n",
- "\n",
- " this._resize_canvas = function(width, height) {\n",
- " // Keep the size of the canvas, canvas container, and rubber band\n",
- " // canvas in synch.\n",
- " canvas_div.css('width', width)\n",
- " canvas_div.css('height', height)\n",
- "\n",
- " canvas.attr('width', width);\n",
- " canvas.attr('height', height);\n",
- "\n",
- " rubberband.attr('width', width);\n",
- " rubberband.attr('height', height);\n",
- " }\n",
- "\n",
- " // Set the figure to an initial 600x600px, this will subsequently be updated\n",
- " // upon first draw.\n",
- " this._resize_canvas(600, 600);\n",
- "\n",
- " // Disable right mouse context menu.\n",
- " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n",
- " return false;\n",
- " });\n",
- "\n",
- " function set_focus () {\n",
- " canvas.focus();\n",
- " canvas_div.focus();\n",
- " }\n",
- "\n",
- " window.setTimeout(set_focus, 100);\n",
- "}\n",
- "\n",
- "mpl.figure.prototype._init_toolbar = function() {\n",
- " var fig = this;\n",
- "\n",
- " var nav_element = $('')\n",
- " nav_element.attr('style', 'width: 100%');\n",
- " this.root.append(nav_element);\n",
- "\n",
- " // Define a callback function for later on.\n",
- " function toolbar_event(event) {\n",
- " return fig.toolbar_button_onclick(event['data']);\n",
- " }\n",
- " function toolbar_mouse_event(event) {\n",
- " return fig.toolbar_button_onmouseover(event['data']);\n",
- " }\n",
- "\n",
- " for(var toolbar_ind in mpl.toolbar_items) {\n",
- " var name = mpl.toolbar_items[toolbar_ind][0];\n",
- " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
- " var image = mpl.toolbar_items[toolbar_ind][2];\n",
- " var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
- "\n",
- " if (!name) {\n",
- " // put a spacer in here.\n",
- " continue;\n",
- " }\n",
- " var button = $('');\n",
- " button.addClass('ui-button ui-widget ui-state-default ui-corner-all ' +\n",
- " 'ui-button-icon-only');\n",
- " button.attr('role', 'button');\n",
- " button.attr('aria-disabled', 'false');\n",
- " button.click(method_name, toolbar_event);\n",
- " button.mouseover(tooltip, toolbar_mouse_event);\n",
- "\n",
- " var icon_img = $('');\n",
- " icon_img.addClass('ui-button-icon-primary ui-icon');\n",
- " icon_img.addClass(image);\n",
- " icon_img.addClass('ui-corner-all');\n",
- "\n",
- " var tooltip_span = $('');\n",
- " tooltip_span.addClass('ui-button-text');\n",
- " tooltip_span.html(tooltip);\n",
- "\n",
- " button.append(icon_img);\n",
- " button.append(tooltip_span);\n",
- "\n",
- " nav_element.append(button);\n",
- " }\n",
- "\n",
- " var fmt_picker_span = $('');\n",
- "\n",
- " var fmt_picker = $('');\n",
- " fmt_picker.addClass('mpl-toolbar-option ui-widget ui-widget-content');\n",
- " fmt_picker_span.append(fmt_picker);\n",
- " nav_element.append(fmt_picker_span);\n",
- " this.format_dropdown = fmt_picker[0];\n",
- "\n",
- " for (var ind in mpl.extensions) {\n",
- " var fmt = mpl.extensions[ind];\n",
- " var option = $(\n",
- " '', {selected: fmt === mpl.default_extension}).html(fmt);\n",
- " fmt_picker.append(option)\n",
- " }\n",
- "\n",
- " // Add hover states to the ui-buttons\n",
- " $( \".ui-button\" ).hover(\n",
- " function() { $(this).addClass(\"ui-state-hover\");},\n",
- " function() { $(this).removeClass(\"ui-state-hover\");}\n",
- " );\n",
- "\n",
- " var status_bar = $('');\n",
- " nav_element.append(status_bar);\n",
- " this.message = status_bar[0];\n",
- "}\n",
- "\n",
- "mpl.figure.prototype.request_resize = function(x_pixels, y_pixels) {\n",
- " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
- " // which will in turn request a refresh of the image.\n",
- " this.send_message('resize', {'width': x_pixels, 'height': y_pixels});\n",
- "}\n",
- "\n",
- "mpl.figure.prototype.send_message = function(type, properties) {\n",
- " properties['type'] = type;\n",
- " properties['figure_id'] = this.id;\n",
- " this.ws.send(JSON.stringify(properties));\n",
- "}\n",
- "\n",
- "mpl.figure.prototype.send_draw_message = function() {\n",
- " if (!this.waiting) {\n",
- " this.waiting = true;\n",
- " this.ws.send(JSON.stringify({type: \"draw\", figure_id: this.id}));\n",
- " }\n",
- "}\n",
- "\n",
- "\n",
- "mpl.figure.prototype.handle_save = function(fig, msg) {\n",
- " var format_dropdown = fig.format_dropdown;\n",
- " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
- " fig.ondownload(fig, format);\n",
- "}\n",
- "\n",
- "\n",
- "mpl.figure.prototype.handle_resize = function(fig, msg) {\n",
- " var size = msg['size'];\n",
- " if (size[0] != fig.canvas.width || size[1] != fig.canvas.height) {\n",
- " fig._resize_canvas(size[0], size[1]);\n",
- " fig.send_message(\"refresh\", {});\n",
- " };\n",
- "}\n",
- "\n",
- "mpl.figure.prototype.handle_rubberband = function(fig, msg) {\n",
- " var x0 = msg['x0'];\n",
- " var y0 = fig.canvas.height - msg['y0'];\n",
- " var x1 = msg['x1'];\n",
- " var y1 = fig.canvas.height - msg['y1'];\n",
- " x0 = Math.floor(x0) + 0.5;\n",
- " y0 = Math.floor(y0) + 0.5;\n",
- " x1 = Math.floor(x1) + 0.5;\n",
- " y1 = Math.floor(y1) + 0.5;\n",
- " var min_x = Math.min(x0, x1);\n",
- " var min_y = Math.min(y0, y1);\n",
- " var width = Math.abs(x1 - x0);\n",
- " var height = Math.abs(y1 - y0);\n",
- "\n",
- " fig.rubberband_context.clearRect(\n",
- " 0, 0, fig.canvas.width, fig.canvas.height);\n",
- "\n",
- " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
- "}\n",
- "\n",
- "mpl.figure.prototype.handle_figure_label = function(fig, msg) {\n",
- " // Updates the figure title.\n",
- " fig.header.textContent = msg['label'];\n",
- "}\n",
- "\n",
- "mpl.figure.prototype.handle_cursor = function(fig, msg) {\n",
- " var cursor = msg['cursor'];\n",
- " switch(cursor)\n",
- " {\n",
- " case 0:\n",
- " cursor = 'pointer';\n",
- " break;\n",
- " case 1:\n",
- " cursor = 'default';\n",
- " break;\n",
- " case 2:\n",
- " cursor = 'crosshair';\n",
- " break;\n",
- " case 3:\n",
- " cursor = 'move';\n",
- " break;\n",
- " }\n",
- " fig.rubberband_canvas.style.cursor = cursor;\n",
- "}\n",
- "\n",
- "mpl.figure.prototype.handle_message = function(fig, msg) {\n",
- " fig.message.textContent = msg['message'];\n",
- "}\n",
- "\n",
- "mpl.figure.prototype.handle_draw = function(fig, msg) {\n",
- " // Request the server to send over a new figure.\n",
- " fig.send_draw_message();\n",
- "}\n",
- "\n",
- "mpl.figure.prototype.handle_image_mode = function(fig, msg) {\n",
- " fig.image_mode = msg['mode'];\n",
- "}\n",
- "\n",
- "mpl.figure.prototype.updated_canvas_event = function() {\n",
- " // Called whenever the canvas gets updated.\n",
- " this.send_message(\"ack\", {});\n",
- "}\n",
- "\n",
- "// A function to construct a web socket function for onmessage handling.\n",
- "// Called in the figure constructor.\n",
- "mpl.figure.prototype._make_on_message_function = function(fig) {\n",
- " return function socket_on_message(evt) {\n",
- " if (evt.data instanceof Blob) {\n",
- " /* FIXME: We get \"Resource interpreted as Image but\n",
- " * transferred with MIME type text/plain:\" errors on\n",
- " * Chrome. But how to set the MIME type? It doesn't seem\n",
- " * to be part of the websocket stream */\n",
- " evt.data.type = \"image/png\";\n",
- "\n",
- " /* Free the memory for the previous frames */\n",
- " if (fig.imageObj.src) {\n",
- " (window.URL || window.webkitURL).revokeObjectURL(\n",
- " fig.imageObj.src);\n",
- " }\n",
- "\n",
- " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
- " evt.data);\n",
- " fig.updated_canvas_event();\n",
- " fig.waiting = false;\n",
- " return;\n",
- " }\n",
- " else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n",
- " fig.imageObj.src = evt.data;\n",
- " fig.updated_canvas_event();\n",
- " fig.waiting = false;\n",
- " return;\n",
- " }\n",
- "\n",
- " var msg = JSON.parse(evt.data);\n",
- " var msg_type = msg['type'];\n",
- "\n",
- " // Call the \"handle_{type}\" callback, which takes\n",
- " // the figure and JSON message as its only arguments.\n",
- " try {\n",
- " var callback = fig[\"handle_\" + msg_type];\n",
- " } catch (e) {\n",
- " console.log(\"No handler for the '\" + msg_type + \"' message type: \", msg);\n",
- " return;\n",
- " }\n",
- "\n",
- " if (callback) {\n",
- " try {\n",
- " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
- " callback(fig, msg);\n",
- " } catch (e) {\n",
- " console.log(\"Exception inside the 'handler_\" + msg_type + \"' callback:\", e, e.stack, msg);\n",
- " }\n",
- " }\n",
- " };\n",
- "}\n",
- "\n",
- "// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
- "mpl.findpos = function(e) {\n",
- " //this section is from http://www.quirksmode.org/js/events_properties.html\n",
- " var targ;\n",
- " if (!e)\n",
- " e = window.event;\n",
- " if (e.target)\n",
- " targ = e.target;\n",
- " else if (e.srcElement)\n",
- " targ = e.srcElement;\n",
- " if (targ.nodeType == 3) // defeat Safari bug\n",
- " targ = targ.parentNode;\n",
- "\n",
- " // jQuery normalizes the pageX and pageY\n",
- " // pageX,Y are the mouse positions relative to the document\n",
- " // offset() returns the position of the element relative to the document\n",
- " var x = e.pageX - $(targ).offset().left;\n",
- " var y = e.pageY - $(targ).offset().top;\n",
- "\n",
- " return {\"x\": x, \"y\": y};\n",
- "};\n",
- "\n",
- "/*\n",
- " * return a copy of an object with only non-object keys\n",
- " * we need this to avoid circular references\n",
- " * http://stackoverflow.com/a/24161582/3208463\n",
- " */\n",
- "function simpleKeys (original) {\n",
- " return Object.keys(original).reduce(function (obj, key) {\n",
- " if (typeof original[key] !== 'object')\n",
- " obj[key] = original[key]\n",
- " return obj;\n",
- " }, {});\n",
- "}\n",
- "\n",
- "mpl.figure.prototype.mouse_event = function(event, name) {\n",
- " var canvas_pos = mpl.findpos(event)\n",
- "\n",
- " if (name === 'button_press')\n",
- " {\n",
- " this.canvas.focus();\n",
- " this.canvas_div.focus();\n",
- " }\n",
- "\n",
- " var x = canvas_pos.x;\n",
- " var y = canvas_pos.y;\n",
- "\n",
- " this.send_message(name, {x: x, y: y, button: event.button,\n",
- " step: event.step,\n",
- " guiEvent: simpleKeys(event)});\n",
- "\n",
- " /* This prevents the web browser from automatically changing to\n",
- " * the text insertion cursor when the button is pressed. We want\n",
- " * to control all of the cursor setting manually through the\n",
- " * 'cursor' event from matplotlib */\n",
- " event.preventDefault();\n",
- " return false;\n",
- "}\n",
- "\n",
- "mpl.figure.prototype._key_event_extra = function(event, name) {\n",
- " // Handle any extra behaviour associated with a key event\n",
- "}\n",
- "\n",
- "mpl.figure.prototype.key_event = function(event, name) {\n",
- "\n",
- " // Prevent repeat events\n",
- " if (name == 'key_press')\n",
- " {\n",
- " if (event.which === this._key)\n",
- " return;\n",
- " else\n",
- " this._key = event.which;\n",
- " }\n",
- " if (name == 'key_release')\n",
- " this._key = null;\n",
- "\n",
- " var value = '';\n",
- " if (event.ctrlKey && event.which != 17)\n",
- " value += \"ctrl+\";\n",
- " if (event.altKey && event.which != 18)\n",
- " value += \"alt+\";\n",
- " if (event.shiftKey && event.which != 16)\n",
- " value += \"shift+\";\n",
- "\n",
- " value += 'k';\n",
- " value += event.which.toString();\n",
- "\n",
- " this._key_event_extra(event, name);\n",
- "\n",
- " this.send_message(name, {key: value,\n",
- " guiEvent: simpleKeys(event)});\n",
- " return false;\n",
- "}\n",
- "\n",
- "mpl.figure.prototype.toolbar_button_onclick = function(name) {\n",
- " if (name == 'download') {\n",
- " this.handle_save(this, null);\n",
- " } else {\n",
- " this.send_message(\"toolbar_button\", {name: name});\n",
- " }\n",
- "};\n",
- "\n",
- "mpl.figure.prototype.toolbar_button_onmouseover = function(tooltip) {\n",
- " this.message.textContent = tooltip;\n",
- "};\n",
- "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
- "\n",
- "mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n",
- "\n",
- "mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n",
- " // Create a \"websocket\"-like object which calls the given IPython comm\n",
- " // object with the appropriate methods. Currently this is a non binary\n",
- " // socket, so there is still some room for performance tuning.\n",
- " var ws = {};\n",
- "\n",
- " ws.close = function() {\n",
- " comm.close()\n",
- " };\n",
- " ws.send = function(m) {\n",
- " //console.log('sending', m);\n",
- " comm.send(m);\n",
- " };\n",
- " // Register the callback with on_msg.\n",
- " comm.on_msg(function(msg) {\n",
- " //console.log('receiving', msg['content']['data'], msg);\n",
- " // Pass the mpl event to the overriden (by mpl) onmessage function.\n",
- " ws.onmessage(msg['content']['data'])\n",
- " });\n",
- " return ws;\n",
- "}\n",
- "\n",
- "mpl.mpl_figure_comm = function(comm, msg) {\n",
- " // This is the function which gets called when the mpl process\n",
- " // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
- "\n",
- " var id = msg.content.data.id;\n",
- " // Get hold of the div created by the display call when the Comm\n",
- " // socket was opened in Python.\n",
- " var element = $(\"#\" + id);\n",
- " var ws_proxy = comm_websocket_adapter(comm)\n",
- "\n",
- " function ondownload(figure, format) {\n",
- " window.open(figure.imageObj.src);\n",
- " }\n",
- "\n",
- " var fig = new mpl.figure(id, ws_proxy,\n",
- " ondownload,\n",
- " element.get(0));\n",
- "\n",
- " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
- " // web socket which is closed, not our websocket->open comm proxy.\n",
- " ws_proxy.onopen();\n",
- "\n",
- " fig.parent_element = element.get(0);\n",
- " fig.cell_info = mpl.find_output_cell(\"\");\n",
- " if (!fig.cell_info) {\n",
- " console.error(\"Failed to find cell for figure\", id, fig);\n",
- " return;\n",
- " }\n",
- "\n",
- " var output_index = fig.cell_info[2]\n",
- " var cell = fig.cell_info[0];\n",
- "\n",
- "};\n",
- "\n",
- "mpl.figure.prototype.handle_close = function(fig, msg) {\n",
- " fig.root.unbind('remove')\n",
- "\n",
- " // Update the output cell to use the data from the current canvas.\n",
- " fig.push_to_output();\n",
- " var dataURL = fig.canvas.toDataURL();\n",
- " // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
- " // the notebook keyboard shortcuts fail.\n",
- " IPython.keyboard_manager.enable()\n",
- " $(fig.parent_element).html('');\n",
- " fig.close_ws(fig, msg);\n",
- "}\n",
- "\n",
- "mpl.figure.prototype.close_ws = function(fig, msg){\n",
- " fig.send_message('closing', msg);\n",
- " // fig.ws.close()\n",
- "}\n",
- "\n",
- "mpl.figure.prototype.push_to_output = function(remove_interactive) {\n",
- " // Turn the data on the canvas into data in the output cell.\n",
- " var dataURL = this.canvas.toDataURL();\n",
- " this.cell_info[1]['text/html'] = '';\n",
- "}\n",
- "\n",
- "mpl.figure.prototype.updated_canvas_event = function() {\n",
- " // Tell IPython that the notebook contents must change.\n",
- " IPython.notebook.set_dirty(true);\n",
- " this.send_message(\"ack\", {});\n",
- " var fig = this;\n",
- " // Wait a second, then push the new image to the DOM so\n",
- " // that it is saved nicely (might be nice to debounce this).\n",
- " setTimeout(function () { fig.push_to_output() }, 1000);\n",
- "}\n",
- "\n",
- "mpl.figure.prototype._init_toolbar = function() {\n",
- " var fig = this;\n",
- "\n",
- " var nav_element = $('')\n",
- " nav_element.attr('style', 'width: 100%');\n",
- " this.root.append(nav_element);\n",
- "\n",
- " // Define a callback function for later on.\n",
- " function toolbar_event(event) {\n",
- " return fig.toolbar_button_onclick(event['data']);\n",
- " }\n",
- " function toolbar_mouse_event(event) {\n",
- " return fig.toolbar_button_onmouseover(event['data']);\n",
- " }\n",
- "\n",
- " for(var toolbar_ind in mpl.toolbar_items){\n",
- " var name = mpl.toolbar_items[toolbar_ind][0];\n",
- " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
- " var image = mpl.toolbar_items[toolbar_ind][2];\n",
- " var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
- "\n",
- " if (!name) { continue; };\n",
- "\n",
- " var button = $('');\n",
- " button.click(method_name, toolbar_event);\n",
- " button.mouseover(tooltip, toolbar_mouse_event);\n",
- " nav_element.append(button);\n",
- " }\n",
- "\n",
- " // Add the status bar.\n",
- " var status_bar = $('');\n",
- " nav_element.append(status_bar);\n",
- " this.message = status_bar[0];\n",
- "\n",
- " // Add the close button to the window.\n",
- " var buttongrp = $('');\n",
- " var button = $('');\n",
- " button.click(function (evt) { fig.handle_close(fig, {}); } );\n",
- " button.mouseover('Stop Interaction', toolbar_mouse_event);\n",
- " buttongrp.append(button);\n",
- " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n",
- " titlebar.prepend(buttongrp);\n",
- "}\n",
- "\n",
- "mpl.figure.prototype._root_extra_style = function(el){\n",
- " var fig = this\n",
- " el.on(\"remove\", function(){\n",
- "\tfig.close_ws(fig, {});\n",
- " });\n",
- "}\n",
- "\n",
- "mpl.figure.prototype._canvas_extra_style = function(el){\n",
- " // this is important to make the div 'focusable\n",
- " el.attr('tabindex', 0)\n",
- " // reach out to IPython and tell the keyboard manager to turn it's self\n",
- " // off when our div gets focus\n",
- "\n",
- " // location in version 3\n",
- " if (IPython.notebook.keyboard_manager) {\n",
- " IPython.notebook.keyboard_manager.register_events(el);\n",
- " }\n",
- " else {\n",
- " // location in version 2\n",
- " IPython.keyboard_manager.register_events(el);\n",
- " }\n",
- "\n",
- "}\n",
- "\n",
- "mpl.figure.prototype._key_event_extra = function(event, name) {\n",
- " var manager = IPython.notebook.keyboard_manager;\n",
- " if (!manager)\n",
- " manager = IPython.keyboard_manager;\n",
- "\n",
- " // Check for shift+enter\n",
- " if (event.shiftKey && event.which == 13) {\n",
- " this.canvas_div.blur();\n",
- " // select the cell after this one\n",
- " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n",
- " IPython.notebook.select(index + 1);\n",
- " }\n",
- "}\n",
- "\n",
- "mpl.figure.prototype.handle_save = function(fig, msg) {\n",
- " fig.ondownload(fig, null);\n",
- "}\n",
- "\n",
- "\n",
- "mpl.find_output_cell = function(html_output) {\n",
- " // Return the cell and output element which can be found *uniquely* in the notebook.\n",
- " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
- " // IPython event is triggered only after the cells have been serialised, which for\n",
- " // our purposes (turning an active figure into a static one), is too late.\n",
- " var cells = IPython.notebook.get_cells();\n",
- " var ncells = cells.length;\n",
- " for (var i=0; i= 3 moved mimebundle to data attribute of output\n",
- " data = data.data;\n",
- " }\n",
- " if (data['text/html'] == html_output) {\n",
- " return [cell, data, j];\n",
- " }\n",
- " }\n",
- " }\n",
- " }\n",
- "}\n",
- "\n",
- "// Register the function which deals with the matplotlib target/channel.\n",
- "// The kernel may be null if the page has been refreshed.\n",
- "if (IPython.notebook.kernel != null) {\n",
- " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n",
- "}\n"
- ],
+ "application/javascript": "/* Put everything inside the global mpl namespace */\nwindow.mpl = {};\n\nmpl.get_websocket_type = function() {\n if (typeof(WebSocket) !== 'undefined') {\n return WebSocket;\n } else if (typeof(MozWebSocket) !== 'undefined') {\n return MozWebSocket;\n } else {\n alert('Your browser does not have WebSocket support.' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.');\n };\n}\n\nmpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = (this.ws.binaryType != undefined);\n\n if (!this.supports_binary) {\n var warnings = document.getElementById(\"mpl-warnings\");\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent = (\n \"This browser does not support binary websocket messages. \" +\n \"Performance may be slow.\");\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = $('');\n this._root_extra_style(this.root)\n this.root.attr('style', 'display: inline-block');\n\n $(parent_element).append(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n fig.send_message(\"send_image_mode\", {});\n fig.send_message(\"refresh\", {});\n }\n\n this.imageObj.onload = function() {\n if (fig.image_mode == 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function() {\n this.ws.close();\n }\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n}\n\nmpl.figure.prototype._init_header = function() {\n var titlebar = $(\n '');\n var titletext = $(\n '');\n titlebar.append(titletext)\n this.root.append(titlebar);\n this.header = titletext[0];\n}\n\n\n\nmpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n\n}\n\n\nmpl.figure.prototype._root_extra_style = function(canvas_div) {\n\n}\n\nmpl.figure.prototype._init_canvas = function() {\n var fig = this;\n\n var canvas_div = $('');\n\n canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n\n function canvas_keyboard_event(event) {\n return fig.key_event(event, event['data']);\n }\n\n canvas_div.keydown('key_press', canvas_keyboard_event);\n canvas_div.keyup('key_release', canvas_keyboard_event);\n this.canvas_div = canvas_div\n this._canvas_extra_style(canvas_div)\n this.root.append(canvas_div);\n\n var canvas = $('');\n canvas.addClass('mpl-canvas');\n canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n\n this.canvas = canvas[0];\n this.context = canvas[0].getContext(\"2d\");\n\n var rubberband = $('');\n rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n\n var pass_mouse_events = true;\n\n canvas_div.resizable({\n start: function(event, ui) {\n pass_mouse_events = false;\n },\n resize: function(event, ui) {\n fig.request_resize(ui.size.width, ui.size.height);\n },\n stop: function(event, ui) {\n pass_mouse_events = true;\n fig.request_resize(ui.size.width, ui.size.height);\n },\n });\n\n function mouse_event_fn(event) {\n if (pass_mouse_events)\n return fig.mouse_event(event, event['data']);\n }\n\n rubberband.mousedown('button_press', mouse_event_fn);\n rubberband.mouseup('button_release', mouse_event_fn);\n // Throttle sequential mouse events to 1 every 20ms.\n rubberband.mousemove('motion_notify', mouse_event_fn);\n\n rubberband.mouseenter('figure_enter', mouse_event_fn);\n rubberband.mouseleave('figure_leave', mouse_event_fn);\n\n canvas_div.on(\"wheel\", function (event) {\n event = event.originalEvent;\n event['data'] = 'scroll'\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n mouse_event_fn(event);\n });\n\n canvas_div.append(canvas);\n canvas_div.append(rubberband);\n\n this.rubberband = rubberband;\n this.rubberband_canvas = rubberband[0];\n this.rubberband_context = rubberband[0].getContext(\"2d\");\n this.rubberband_context.strokeStyle = \"#000000\";\n\n this._resize_canvas = function(width, height) {\n // Keep the size of the canvas, canvas container, and rubber band\n // canvas in synch.\n canvas_div.css('width', width)\n canvas_div.css('height', height)\n\n canvas.attr('width', width);\n canvas.attr('height', height);\n\n rubberband.attr('width', width);\n rubberband.attr('height', height);\n }\n\n // Set the figure to an initial 600x600px, this will subsequently be updated\n // upon first draw.\n this._resize_canvas(600, 600);\n\n // Disable right mouse context menu.\n $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n return false;\n });\n\n function set_focus () {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n}\n\nmpl.figure.prototype._init_toolbar = function() {\n var fig = this;\n\n var nav_element = $('')\n nav_element.attr('style', 'width: 100%');\n this.root.append(nav_element);\n\n // Define a callback function for later on.\n function toolbar_event(event) {\n return fig.toolbar_button_onclick(event['data']);\n }\n function toolbar_mouse_event(event) {\n return fig.toolbar_button_onmouseover(event['data']);\n }\n\n for(var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n // put a spacer in here.\n continue;\n }\n var button = $('');\n button.addClass('ui-button ui-widget ui-state-default ui-corner-all ' +\n 'ui-button-icon-only');\n button.attr('role', 'button');\n button.attr('aria-disabled', 'false');\n button.click(method_name, toolbar_event);\n button.mouseover(tooltip, toolbar_mouse_event);\n\n var icon_img = $('');\n icon_img.addClass('ui-button-icon-primary ui-icon');\n icon_img.addClass(image);\n icon_img.addClass('ui-corner-all');\n\n var tooltip_span = $('');\n tooltip_span.addClass('ui-button-text');\n tooltip_span.html(tooltip);\n\n button.append(icon_img);\n button.append(tooltip_span);\n\n nav_element.append(button);\n }\n\n var fmt_picker_span = $('');\n\n var fmt_picker = $('');\n fmt_picker.addClass('mpl-toolbar-option ui-widget ui-widget-content');\n fmt_picker_span.append(fmt_picker);\n nav_element.append(fmt_picker_span);\n this.format_dropdown = fmt_picker[0];\n\n for (var ind in mpl.extensions) {\n var fmt = mpl.extensions[ind];\n var option = $(\n '', {selected: fmt === mpl.default_extension}).html(fmt);\n fmt_picker.append(option)\n }\n\n // Add hover states to the ui-buttons\n $( \".ui-button\" ).hover(\n function() { $(this).addClass(\"ui-state-hover\");},\n function() { $(this).removeClass(\"ui-state-hover\");}\n );\n\n var status_bar = $('');\n nav_element.append(status_bar);\n this.message = status_bar[0];\n}\n\nmpl.figure.prototype.request_resize = function(x_pixels, y_pixels) {\n // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n // which will in turn request a refresh of the image.\n this.send_message('resize', {'width': x_pixels, 'height': y_pixels});\n}\n\nmpl.figure.prototype.send_message = function(type, properties) {\n properties['type'] = type;\n properties['figure_id'] = this.id;\n this.ws.send(JSON.stringify(properties));\n}\n\nmpl.figure.prototype.send_draw_message = function() {\n if (!this.waiting) {\n this.waiting = true;\n this.ws.send(JSON.stringify({type: \"draw\", figure_id: this.id}));\n }\n}\n\n\nmpl.figure.prototype.handle_save = function(fig, msg) {\n var format_dropdown = fig.format_dropdown;\n var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n fig.ondownload(fig, format);\n}\n\n\nmpl.figure.prototype.handle_resize = function(fig, msg) {\n var size = msg['size'];\n if (size[0] != fig.canvas.width || size[1] != fig.canvas.height) {\n fig._resize_canvas(size[0], size[1]);\n fig.send_message(\"refresh\", {});\n };\n}\n\nmpl.figure.prototype.handle_rubberband = function(fig, msg) {\n var x0 = msg['x0'];\n var y0 = fig.canvas.height - msg['y0'];\n var x1 = msg['x1'];\n var y1 = fig.canvas.height - msg['y1'];\n x0 = Math.floor(x0) + 0.5;\n y0 = Math.floor(y0) + 0.5;\n x1 = Math.floor(x1) + 0.5;\n y1 = Math.floor(y1) + 0.5;\n var min_x = Math.min(x0, x1);\n var min_y = Math.min(y0, y1);\n var width = Math.abs(x1 - x0);\n var height = Math.abs(y1 - y0);\n\n fig.rubberband_context.clearRect(\n 0, 0, fig.canvas.width, fig.canvas.height);\n\n fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n}\n\nmpl.figure.prototype.handle_figure_label = function(fig, msg) {\n // Updates the figure title.\n fig.header.textContent = msg['label'];\n}\n\nmpl.figure.prototype.handle_cursor = function(fig, msg) {\n var cursor = msg['cursor'];\n switch(cursor)\n {\n case 0:\n cursor = 'pointer';\n break;\n case 1:\n cursor = 'default';\n break;\n case 2:\n cursor = 'crosshair';\n break;\n case 3:\n cursor = 'move';\n break;\n }\n fig.rubberband_canvas.style.cursor = cursor;\n}\n\nmpl.figure.prototype.handle_message = function(fig, msg) {\n fig.message.textContent = msg['message'];\n}\n\nmpl.figure.prototype.handle_draw = function(fig, msg) {\n // Request the server to send over a new figure.\n fig.send_draw_message();\n}\n\nmpl.figure.prototype.handle_image_mode = function(fig, msg) {\n fig.image_mode = msg['mode'];\n}\n\nmpl.figure.prototype.updated_canvas_event = function() {\n // Called whenever the canvas gets updated.\n this.send_message(\"ack\", {});\n}\n\n// A function to construct a web socket function for onmessage handling.\n// Called in the figure constructor.\nmpl.figure.prototype._make_on_message_function = function(fig) {\n return function socket_on_message(evt) {\n if (evt.data instanceof Blob) {\n /* FIXME: We get \"Resource interpreted as Image but\n * transferred with MIME type text/plain:\" errors on\n * Chrome. But how to set the MIME type? It doesn't seem\n * to be part of the websocket stream */\n evt.data.type = \"image/png\";\n\n /* Free the memory for the previous frames */\n if (fig.imageObj.src) {\n (window.URL || window.webkitURL).revokeObjectURL(\n fig.imageObj.src);\n }\n\n fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n evt.data);\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n }\n else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\n fig.imageObj.src = evt.data;\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n }\n\n var msg = JSON.parse(evt.data);\n var msg_type = msg['type'];\n\n // Call the \"handle_{type}\" callback, which takes\n // the figure and JSON message as its only arguments.\n try {\n var callback = fig[\"handle_\" + msg_type];\n } catch (e) {\n console.log(\"No handler for the '\" + msg_type + \"' message type: \", msg);\n return;\n }\n\n if (callback) {\n try {\n // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n callback(fig, msg);\n } catch (e) {\n console.log(\"Exception inside the 'handler_\" + msg_type + \"' callback:\", e, e.stack, msg);\n }\n }\n };\n}\n\n// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\nmpl.findpos = function(e) {\n //this section is from http://www.quirksmode.org/js/events_properties.html\n var targ;\n if (!e)\n e = window.event;\n if (e.target)\n targ = e.target;\n else if (e.srcElement)\n targ = e.srcElement;\n if (targ.nodeType == 3) // defeat Safari bug\n targ = targ.parentNode;\n\n // jQuery normalizes the pageX and pageY\n // pageX,Y are the mouse positions relative to the document\n // offset() returns the position of the element relative to the document\n var x = e.pageX - $(targ).offset().left;\n var y = e.pageY - $(targ).offset().top;\n\n return {\"x\": x, \"y\": y};\n};\n\n/*\n * return a copy of an object with only non-object keys\n * we need this to avoid circular references\n * http://stackoverflow.com/a/24161582/3208463\n */\nfunction simpleKeys (original) {\n return Object.keys(original).reduce(function (obj, key) {\n if (typeof original[key] !== 'object')\n obj[key] = original[key]\n return obj;\n }, {});\n}\n\nmpl.figure.prototype.mouse_event = function(event, name) {\n var canvas_pos = mpl.findpos(event)\n\n if (name === 'button_press')\n {\n this.canvas.focus();\n this.canvas_div.focus();\n }\n\n var x = canvas_pos.x;\n var y = canvas_pos.y;\n\n this.send_message(name, {x: x, y: y, button: event.button,\n step: event.step,\n guiEvent: simpleKeys(event)});\n\n /* This prevents the web browser from automatically changing to\n * the text insertion cursor when the button is pressed. We want\n * to control all of the cursor setting manually through the\n * 'cursor' event from matplotlib */\n event.preventDefault();\n return false;\n}\n\nmpl.figure.prototype._key_event_extra = function(event, name) {\n // Handle any extra behaviour associated with a key event\n}\n\nmpl.figure.prototype.key_event = function(event, name) {\n\n // Prevent repeat events\n if (name == 'key_press')\n {\n if (event.which === this._key)\n return;\n else\n this._key = event.which;\n }\n if (name == 'key_release')\n this._key = null;\n\n var value = '';\n if (event.ctrlKey && event.which != 17)\n value += \"ctrl+\";\n if (event.altKey && event.which != 18)\n value += \"alt+\";\n if (event.shiftKey && event.which != 16)\n value += \"shift+\";\n\n value += 'k';\n value += event.which.toString();\n\n this._key_event_extra(event, name);\n\n this.send_message(name, {key: value,\n guiEvent: simpleKeys(event)});\n return false;\n}\n\nmpl.figure.prototype.toolbar_button_onclick = function(name) {\n if (name == 'download') {\n this.handle_save(this, null);\n } else {\n this.send_message(\"toolbar_button\", {name: name});\n }\n};\n\nmpl.figure.prototype.toolbar_button_onmouseover = function(tooltip) {\n this.message.textContent = tooltip;\n};\nmpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n\nmpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n\nmpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n // Create a \"websocket\"-like object which calls the given IPython comm\n // object with the appropriate methods. Currently this is a non binary\n // socket, so there is still some room for performance tuning.\n var ws = {};\n\n ws.close = function() {\n comm.close()\n };\n ws.send = function(m) {\n //console.log('sending', m);\n comm.send(m);\n };\n // Register the callback with on_msg.\n comm.on_msg(function(msg) {\n //console.log('receiving', msg['content']['data'], msg);\n // Pass the mpl event to the overriden (by mpl) onmessage function.\n ws.onmessage(msg['content']['data'])\n });\n return ws;\n}\n\nmpl.mpl_figure_comm = function(comm, msg) {\n // This is the function which gets called when the mpl process\n // starts-up an IPython Comm through the \"matplotlib\" channel.\n\n var id = msg.content.data.id;\n // Get hold of the div created by the display call when the Comm\n // socket was opened in Python.\n var element = $(\"#\" + id);\n var ws_proxy = comm_websocket_adapter(comm)\n\n function ondownload(figure, format) {\n window.open(figure.imageObj.src);\n }\n\n var fig = new mpl.figure(id, ws_proxy,\n ondownload,\n element.get(0));\n\n // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n // web socket which is closed, not our websocket->open comm proxy.\n ws_proxy.onopen();\n\n fig.parent_element = element.get(0);\n fig.cell_info = mpl.find_output_cell(\"\");\n if (!fig.cell_info) {\n console.error(\"Failed to find cell for figure\", id, fig);\n return;\n }\n\n var output_index = fig.cell_info[2]\n var cell = fig.cell_info[0];\n\n};\n\nmpl.figure.prototype.handle_close = function(fig, msg) {\n fig.root.unbind('remove')\n\n // Update the output cell to use the data from the current canvas.\n fig.push_to_output();\n var dataURL = fig.canvas.toDataURL();\n // Re-enable the keyboard manager in IPython - without this line, in FF,\n // the notebook keyboard shortcuts fail.\n IPython.keyboard_manager.enable()\n $(fig.parent_element).html('');\n fig.close_ws(fig, msg);\n}\n\nmpl.figure.prototype.close_ws = function(fig, msg){\n fig.send_message('closing', msg);\n // fig.ws.close()\n}\n\nmpl.figure.prototype.push_to_output = function(remove_interactive) {\n // Turn the data on the canvas into data in the output cell.\n var dataURL = this.canvas.toDataURL();\n this.cell_info[1]['text/html'] = '';\n}\n\nmpl.figure.prototype.updated_canvas_event = function() {\n // Tell IPython that the notebook contents must change.\n IPython.notebook.set_dirty(true);\n this.send_message(\"ack\", {});\n var fig = this;\n // Wait a second, then push the new image to the DOM so\n // that it is saved nicely (might be nice to debounce this).\n setTimeout(function () { fig.push_to_output() }, 1000);\n}\n\nmpl.figure.prototype._init_toolbar = function() {\n var fig = this;\n\n var nav_element = $('')\n nav_element.attr('style', 'width: 100%');\n this.root.append(nav_element);\n\n // Define a callback function for later on.\n function toolbar_event(event) {\n return fig.toolbar_button_onclick(event['data']);\n }\n function toolbar_mouse_event(event) {\n return fig.toolbar_button_onmouseover(event['data']);\n }\n\n for(var toolbar_ind in mpl.toolbar_items){\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) { continue; };\n\n var button = $('');\n button.click(method_name, toolbar_event);\n button.mouseover(tooltip, toolbar_mouse_event);\n nav_element.append(button);\n }\n\n // Add the status bar.\n var status_bar = $('');\n nav_element.append(status_bar);\n this.message = status_bar[0];\n\n // Add the close button to the window.\n var buttongrp = $('');\n var button = $('');\n button.click(function (evt) { fig.handle_close(fig, {}); } );\n button.mouseover('Stop Interaction', toolbar_mouse_event);\n buttongrp.append(button);\n var titlebar = this.root.find($('.ui-dialog-titlebar'));\n titlebar.prepend(buttongrp);\n}\n\nmpl.figure.prototype._root_extra_style = function(el){\n var fig = this\n el.on(\"remove\", function(){\n\tfig.close_ws(fig, {});\n });\n}\n\nmpl.figure.prototype._canvas_extra_style = function(el){\n // this is important to make the div 'focusable\n el.attr('tabindex', 0)\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n }\n else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n\n}\n\nmpl.figure.prototype._key_event_extra = function(event, name) {\n var manager = IPython.notebook.keyboard_manager;\n if (!manager)\n manager = IPython.keyboard_manager;\n\n // Check for shift+enter\n if (event.shiftKey && event.which == 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n}\n\nmpl.figure.prototype.handle_save = function(fig, msg) {\n fig.ondownload(fig, null);\n}\n\n\nmpl.find_output_cell = function(html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i=0; i= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] == html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n}\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel != null) {\n IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n}\n",
"text/plain": [
""
]
diff --git a/examples/l5pc/L5PC_arbor.ipynb b/examples/l5pc/L5PC_arbor.ipynb
index f736a8e1..0f977c9a 100644
--- a/examples/l5pc/L5PC_arbor.ipynb
+++ b/examples/l5pc/L5PC_arbor.ipynb
@@ -1,6 +1,7 @@
{
"cells": [
{
+ "attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
@@ -8,6 +9,7 @@
]
},
{
+ "attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
@@ -31,6 +33,7 @@
]
},
{
+ "attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
@@ -143,8 +146,6 @@
"%load_ext autoreload\n",
"%autoreload\n",
"\n",
- "from __future__ import print_function\n",
- "\n",
"!nrnivmodl mechanisms\n",
"import bluepyopt as bpopt\n",
"import bluepyopt.ephys as ephys\n",
@@ -157,6 +158,7 @@
]
},
{
+ "attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
@@ -176,6 +178,7 @@
]
},
{
+ "attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
@@ -183,6 +186,7 @@
]
},
{
+ "attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
@@ -190,6 +194,7 @@
]
},
{
+ "attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
@@ -249,7 +254,7 @@
},
{
"data": {
- "image/png": "iVBORw0KGgoAAAANSUhEUgAAANYAAAEbCAYAAACm4nrwAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/YYfK9AAAACXBIWXMAAAsTAAALEwEAmpwYAABHtElEQVR4nO2dd3hU17W33z0z0qg31FABiSKEAFEN2HQDNq7YcTc2JnHiexOnVzs337VzE9+U6zg38Y0dx3HBFTuu2KYYm2Js00GI3oQAgVDvdcr+/lgz1kiogqQZSed9nnlGc86ec/YZnd/Za6+99tpKa42BgUH3YvJ2BQwM+iOGsAwMegBDWAYGPYAhLAODHsAQloFBD2AIy8CgBzCEZWDQAxjC6iaUUnFKqb8opU4opRqUUmeVUquVUte69luVUk8qpYqVUjVKqZVKqaRWjnOPUipLKVXvKvtSi/3jlFKblFJ1rnP8p1JKeewfo5R6SymVo5TSSqlHWznHbNf5z7rKLOvg2p5xlftpG/t/rJRyKKUea2Xfo67vtvaKbe+8fRlDWN2AUioF2A1cDTwMZAILgI+Av7uK/S9wC3AXMAsIAz5USpk9jvN94H+Ax4GxwDzgfY/9YcA6oAC4DPgB8DPgxx7VCQJygV8BJ9uocgiw3/X9ug6u7VZgKnCunWL3A78Hlnlej4vHgcEtXpuAjVrrwvbO3afRWhuvS3wBq4CzQEgr+yKAcKARWOKxPRlwAld7lKsBFrZznm8DlUCgx7Zfuc6tWim/H3i0g7pXA8va2DfUdezRiFh/2kqZyxGh+wHHges7OF8y4ADu7qDcPcAOoAooBP4FJHrs9wP+igi+ATgD/N5jvz/w38Ap1/4c4Pu9dU8YLdYlopSKAhYBf9NaV7fcr7UuByYjN8LHHtvPAIeAK1ybrgLMQJxS6qDLTHtXKTXM43CXA5u11p6tzFogAUjptosClFIW4HXgt1rrQ+0U/SawQmttA15xfW6P+4Ey4O0OyvkDjwDjgeuBaFd93HwfuBm4ExgJ3AEc8di/HFiKtOajXect7+Cc3Yalt07UjxkBKEQkbRGPPKWLW2wvcO0DGIaY5r8CfgiUAv8JbFBKjdZa17rK5rVyDPc52jL9LoZfA8Va66fbKqCUCgFuR0xWgJeBXyql4rXW51spbwa+AbystW5o7+Ra6+c9PuYopb4NHFJKJWmt85DW9CjyoNHAaeBL13lGIoK7Rmu9xn2MDq+4GzFarEtHdVykU5iQVu37Wus1WuvtwBIgFrihm87RKZRSc4FlyFO+Pe4E8rTWOwG01icQ8+2+NsovQkzBZztRh0lKqfeVUqeUUlXATteuIa73F4EJwFGl1N+UUtcppdz380TEzN7Q0Xl6CkNYl84xQCPmRlucR8y86Bbb41z7APJd7wfdO7XWFUgfwn0znXd9p+Ux3Pu6i7mIkyFfKWVXStmRFuIPSinPFvObwCh3GVe5y2lbkA8AX2qtD7axHwClVDBi4tYC9yKOmkWu3f4AWuvdiPn7MHIfLwfWeYjLu/RmJ7+/voDViAA6cl7c7bE9iebOizREoPM9yoQgN9ftrs9u50WAR5lf0s3OC6SVHNvidRbx8I1ylRnjqu+CFuUuc13r7BbHTADsLc/VRp0mu46d6rHta65tc9v4zjTX/jSP33KR1+4Jb9+U/eGF9I/ygcPAbcAoIN0lhNOuMk8j/aMFiKmyAcgCzB7Hec8lhhlABuIJywWCXPvDkZZphesm/ppLaD/xOIY/YiJNQLx0f3f9PcKjTIhHmVqkLzcBGNLONebi4RUE/gzsbqPsauClFtt+BVS4r6XFvkTXb3ez63MMUA/8yfXbXgcc8BQW4pS4C7EURgB/8Tw+8Ibr974FSEWGOO7ttXvC2zdlf3khptOTSCe5AWnBVrufmoDVtb/EdTN/ACS3OEYo0v8oRTxnHwDDW5QZB3zmuvHyEc+Z8tif4roBW742epSZ20aZF9u5vq+E5RJvEfDLNsp+w3WN4a7PCnGsPNVGeXedl3lsuwM44brO7cgYoaewvoWMHVYhD5dNwBUe37cCf0Ra2gbXsb7bW/eDclXCwMCgG/GNjp6BQT/DEJaBQQ9gCMvAoAcwhGVg0AMMuJCm6OhonZKS4u1qGPQTdu3aVay1jmm5fcAJKyUlhZ07d3Zc0MCgEyilTrW23TAFDQx6AENY/Yyyk2XY6+3ersaAZ8CZgn2ZmsIaDv7rIGarmYxbMwiICLigTO7GXE59forhC4cz7s5xXqilARgtVp/CbrMz7p5xKLPi459/3GqZ+sp6gqKCGDpraC/XzsATQ1h9BK01eTvy+PLxL5mwbAKRKZHseXFPszL5Wfnkbs4ldnws/qH+nNx0kmOrj1FbUsuxtcfY8OsNHHy/3RkbBt2EIaw+glKKMTeNoa6yjvLT5Uz59hROf3Yap8MJgL3BzvantqOdmnM7zrH/jf3seWUP/mH+7H9jP0WHiijPLSdm1AWeYQB2PbeLstyyr47naHRgbzD6aheL0cfqYyRPTSZ3Qy4Tl00kPCWcvG15DLliCCfWnSAyJZLkacmExIeQcmUK/qH+FB8uJnlWMtue2Ma1f70Wa6i12fFqS2tRZsXJ9SfxD/bn+NrjWKwWgmKCiMuMIyI5wjsX2scxWqw+xKnPTpE8M5lzu85hq7Mx8tqRnPj4BE6Hk5x1OaTOTaWhpgG/ID/8A/3J35lP0tQkdv59JyOuGdFMVE67k9NfnOaz33zG+l+tp7a4lmMfHaPkUAlBMUE4Gh2GqC4BQ1h9iOj0aAr3FTJkxhC2PLGFuPFx2OvsHFt7DMzwxeNfkLMuh7gJcdQU1WCvtxM3Lo6IpAhM1ub/apPFxIG3D3By00lObz/NqMWjuOHZG7jqiasozy1n9OL2Mg0YdIQhrD5EcGww9eX1jL1jLCarieOrjzN48mCyXshi6OyhVBdXkzA5gaDIILRTY/aX3JkmPxPBg4IvOF5cZhxV+VXEZ8Qz7TvT8AvwQzs1AeEBKFN35cgZmHhFWEqp55VShUqp/R7bopRS65RSx1zvka7tSin1V6XUcaVUtlJqksd37nOVP6aUaiszUL/C5GfC0ehg7G1jOfnJSYYtHEbZ0TLCh4QTPy6eipMVNNY00ljdSGV+JTv+sYOz284SO+bCbM6VpyvBARm3ZXy1LeeTnFbLGnQNb7VYL9KUdcfNQ8CnWuuRwKeuzwDXIAkZRyJZfp6GrxJlPoIkEZkKPOIWY3/GYrVQV1lHxNAIQhJCOL35NKFDQsn7Mo+YMTHUltdSdKCI0KRQGssbMfubiR8fT2Bk4AXHUiZF5NBIRiwcAUDV+Spqi2sZPHFwb19Wv8MrwtJaf4bkdfBkMZLCCtf7TR7bX9LCViBCKTUYyYGwTmtdqrUuQ3KatxRrvyNpWhL52yRTmtPmJD8rn9R5qRTtLyI0IRRriJWy3DLqiusIiAygaH8RVeeqWj1W0eEikmYmYfYzo52ao6uOMua2Mb15Of0WX+pjxWmt3bn1PPPnJSJ5ud3kuba1tf0ClFIPKKV2KqV2FhUVdW+te5nQwaHUV9XL3wmh2GpslOWWETcxjtOfnyZ8SDh5X+ZRcboCi9VC7PhYastrLziOo9GBo85B0nRZ8GTfin2kLUrDYjVGYLoDXxLWV2jJcNNtWW601v/QWk/RWk+JiWl9gLQv4R/kT01xDfET43E4HNjr7Iy/bzyVeZWU5pbSWNOIdmpsNTbyvshr1bRzOpw4bU6CBgWxb8U+otOjCU0I9cLV9E98SVgFLhMP17t7iZezSFpiN0mubW1t7/cMv2o4R96T/P8RiREU7C2g/FQ5IxaMoHB/IYHRgfiH+1NbVotWmpDBIRccI/u1bBprGsnZkENESgQJkxJ6+zL6Nb4krJU05fy+j6Z1oVYCS13ewelAhctkXAtcpZSKdDktrnJt6/f4Bfox8f6JlJ8sZ8/yPfgF+XHwjYNk3ptJQFgAeV/mUX2umqq8KtKuTZN8uy60U1OVX8WRlUewRlhJvz6d5OnyfHI6nOz/1/42zmrQFbzlbn8d2ILk/c5TSrkXLluolDqGZIv9vav4KiQJ5nEkmeV3ALTWpcBvkCT8O4D/cm0bEDTWNJK3Tcy8oOggKvMqMfuZmbBsAqXHSyk+VIx2atKuS6PqvDgv8rbnkf1KNodXHqb6fDXx4+NJvlxE5XA42Lt8L8nTkts7rUEn8UpPVWt9Vxu75rdSVgMPtnGc54HnW9vX3zn8/mFKj5Vyw7M3cGbLGTb/YTOHVx4m865Mtjyxhe1/305wTDCOegf+wf7sfXkvkamRjF86nhcXvIg1zMrsX84GZKrJgTcPMPK6kYQNDvPylfUPfMkUNOgCeVvyCIgMICgqiPQb0gkbHEbWy1n4h/iTMjeFilMVBMUGYQm0YAmwYLfZGTJzCAfePUBNYQ0TvzGRIx8cYfvT2zn49kHG3TnOEFU3YgirjzLmtjHUV9azb8U+ADJuzsBWbePQykPglIHk+vJ6lFI0lDcQnhxO7me5bP/rdmIyYqgtrGXorKFM/fZUJn19Ev4h/l6+ov6FIaw+SsqcFOInxHN45WEAIkdGYg21svkPmwkbGkZ4YjgVpyqw1dqoLqqm7FgZB98+iMlkwi/Aj/FLxxMUHeTlq+i/GMLqw2TckkFtSS0V5yqISY+hOr8aZ6OTxqpGQpND0VpzeM1hTm48yfGPjxOaGEpAZACTvzW51RAng+7DEFYfJiY9hkHDB7HuJ+uwWC1opwYLnNl6BmuYleDoYD575DNiM2K54+07GHXtKCwBMonRoGcxhNWHUUox/QfTKTxQyJEPjhA9JprawloqTlfQWNeIw+4gIDKAG/9xIyazidNbTmOymIgYGuHtqvd7DGH1caJGRFGZV8kX//sFk+6fhL3BTk1hDWXHyghNDGX6D6ZjtpipLqgmf2c+KXNTjHjAXsAQVh/H7Gfmumeuwz/In7qSOvwD/HE0OAhPDaexvJHhC4fjdDjZ89weUJBxa0bHBzW4ZAxh9QPG3TaOhEkJnNl6BnutnaD4IPJ35mMNsxIQFsCxNcc4v+c8E+6dgDXE2vEBDS4ZQ1j9hBHXjqDkeAkAgycMxlZno6aohuqiag69dYj4yfEkzzDClXoLQ1j9hCGXDyEoIginw0lAaAB+QX5UF1RTtK+IurI6Mr5mmIC9iSGsfoLZYmbw+ME46h3UltYSECV53U9uOElIfAiD0gZ5uYYDC0NY/YhRN43CEmjBaXcSFhdGeEo4RQeKGLFohLerNuAwhNWPCB0cSkRqBHVFdQDYq+2Y/Eykzkv1cs0GHoaw+hnWUCsNlQ3YamzYam1Ep0cTEH7hcj8GPYshrH5G9OhobLU2yvPKMVlNjLpplLerNCAxhNXPiEyNpLG2EVuFjehR0SRMNHJZeAMjtqWfYY2wop0ah81B2g1p3q7OgMVosfoZ9eX1oCEwNpDAcGNqiLcwhNXPKNhbgNnfjF+AX6trFBv0Doaw+hl1ZeJqN5vMmPyNf6+38JlfXik1SimV5fGqVEr9UCn1qFLqrMf2az2+87BrFZIjSqmrvVl/XyFyWCSWQAvWCCsN5Q3ers6AxWecF1rrI8AEAKWUGclq+y7wdeDPWuvHPcsrpTKAO4ExQALwiVIqTWvt6M16+xoBYQGYLWZqS2sxmXzmuTng8NVffj5wQmt9qp0yi4EVWusGrfVJJKHn1F6pnQ9jtpi/WjyurqLO29UZsPiqsO4EXvf4/F3XonPPe6yBNSBXG+mIqoIqTBYTdaV1WAJ8xiAZcPicsJRS/sCNwL9cm54GhiNmYj7wp64es7+tNtIegVGBaK0xW81Yw4xJjd7C54SFrOC4W2tdAKC1LtBaO7TWTiR3u9vcG7CrjbRHbZH0rSz+FiMJpxfxRWHdhYcZ6F7ax8XNgHs5jJXAnUopq1IqFVlKdXuv1dJHsdXY0Gi0XaOUsUC3t/ApI1wpFQwsBP7NY/MflVITkIXoct37tNYHlFJvAgcBO/DgQPcINtY0Yqu1ydKnWksUhoFX8Clhaa1rgEEttt3bTvnHgMd6ul59BYfNQXBMMBZ/C8qsqC29cIlUg97Bp4RlcGnY6+3UldVhsphQZoWzwdnxlwx6BF/sYxlcJHXFdTjtTkISQmioaMCJISxvYQirH2Grt2ENteK0O3E6ncbCB17EEFY/wmQxgYaGqgYxBRuNFstbGMLqR9SX1GNrtDFo1CC0XeMfboxjeQtDWP0J17BVUGQQsZmx1BTUeLc+AxhDWP0I9/Ko+fvyqcyvpOhgERVnK9i3Yh/VhdXert6AwhBWP8IcaCYgNABdr4kZGcPEb0ykILuAIVcMISQ2xNvVG1AY41j9CHudHafTSXVRNYNGDSJiSASRQyM7/qJBt2O0WP2IilMV+AX5YQ4w01DdIEFgBl7BEFY/wuRvQimFQmEym9DaUJa3MITVj6g4XYElwELi1EQayhporGn0dpUGLIaw+hBaa7b9bRv2Bnur++31dqryq7DV2ogcGYl/sDGO5S0MYfUh3GbegTcPtLrfHXyrzIqGsgZMZuPf6y2MX76PkXFrBvnZ+WS/lk3ZyTIKDxaindKXqjhVQXB0MM5GJxHDIrxb0QGO4W7vA+RuyqWupA6H3YHT5qS2oJbP//g5wTHBJE5NBAfM+fUcnHYn2qEJHRxKbYkxF8ubGC2Wj2NvtHNq4ykaaxqpK6nDXmdn1n/MYun6pYQlhjH121MJjA5k51M7sTfY8Qvyo/xkOdYQI5GMNzFaLB/H4m9hziNzWt03YdkEtjyxhRkPzeCj731E5alKJt4/EVudDWXqXL4L7dSdLmvQeYwWqw+TOjeVlCtT+Ow3nzHmjjGYg8zk78oHDX7Bfm1+z2FzcGz1MTY8soFTn7WXE9XgYjFarD7OqOtHUVVYRcmREkKiQjBbzdRX16PMF7ZCjkYHB985yOnPThOaFMqY28cQOybWC7Xu/xjC6gdMWDKBN+56A12nMfmZsDfY0Y6mqAutNXlb88h+OZvo0dHMe2weQZFBXqxx/8fnhKWUygWqAAdg11pPUUpFAW8AKUgKtNu11mVKEuf9BbgWqAWWaa13e6Pe3sRitTD+nvFsemQTcZlxRA6PpORYCQClJ0rZ8dQOnE4nU78/lZj0/p0J2Ffw1T7WPK31BK31FNfnh4BPtdYjgU9dn0Gy5o50vR5A0lEPSNIWpYFDcgtqh6axqpGC/QVs/t1mMm7L4Jo/X2OIqhfxVWG1ZDGw3PX3cuAmj+0vaWErENEic+6AwT/InyGzhmCvsxOdHo05wMy6h9cx/KrhJE9P7vgABt2KLwpLAx8rpXYppR5wbYvTWue7/j4PxLn+7tSKIwNltZHQpFDqS+sJiAqg6HARjgYHAREBVJyp8HbVBhy+KKyZWutJiJn3oFJqtudOLXMhujQfoj+tNrJjR9v7TCYT/mH+lOeUU3OuhuGLhnN45WGUnzFO1dv4nLC01mdd74XIio5TgQK3ied6L3QVH3Arjhw9CnVtrCcXOSISe72dXc/tIjItEmuolSnfmkJYfFjvVtLAt4SllApWSoW6/wauQlYXWQnc5yp2H/C+6++VwFIlTAcqPEzGfsncOZpNm+TvxupGnI6m3IHxE+KpK60jND4UpRTFB4qJHx/vpZoObHzN3R4HvOtafsYCvKa1XqOU2gG8qZS6HzgF3O4qvwpxtR9H3O1f7/0q9y6FH2dx5IMy/LdATWEN2CE4LpjRt44mblwcsWNjyc/Ox1ZnI31xurerO2DxKWFprXOA8a1sL0HWJW65XQMP9kLVfIbMezPZc66AEfMcxE+IAw0F+ws48OYBdj61k5rSGooOFGEJsVBVUOXt6g5YfEpYBh1j9jOTPC0BWzz4B8u25OnJJE1L4uBbBznx8QkCwgIIjA5k8IQBOfLgE/hUH8ugc1x5JWzaBA0NTduUUoy5bQzjl41HKUVDeQPWcGPqiLcwhNUHMZvh+uth7doL90WmRBKdHo1/kD/lueW9XjcDwRBWHyU2FmpbmSQcEh9CdEY0TruTxiojS5O3MITVh2lt7W5lUlTnV1NXVkf+nn498uDTGMLqI1RUwPbtzbc5HPLyRClF6OBQHA0Oqs71nldw/XpYsQIKCnrtlD6NIaw+Qng4ZGWBZ3Lb4ODWzcHEyxMJjgvm7PazvZIN99gxiQa5806Ii+u4/EDAEFYfQik4d67pc2goFBdfWC4mTeIhHXUOynLKerROpaXw7LMw/4JRxoGNIaw+xNKlsHkzfPyxfM7MlNjBlgwaNQizv5m68jrO7zvfY/XZuxdefBGSkyEgoMdO0ycxhNWHsFrF3PL3h3XrYNAgKGulQVJKkTovFVudjfKc8i6dY9euC/ttraE1bNgA6emwbFmXTjEgMITVB5k7V0T12mtw8GDzgWI32qkJiQ3BGtm1QeKiotbF2pLsbJg4Ea69VkxSg+YYwuqjTJoES5bArbe2Pkcr6fIkokdFc3Tl0S6tOlJdDWGdmGVy+DBcdlkXKjzAMITlw9hbX1SkGePGwfHjzb2FAKlXplJ4oJCwhDCq8jvvdrfZxNRsj717ITISgoxET21iCMuHWbkS9u1rv4xSMHNmk0PDTX15PUNmDaHkeAlBg7pXAdu3w1VXdesh+x2GsHyYq2ZUs/+TfHbtan8sasQIKCxs7nSwhlkJigrCEmChtrjzCyS0Fs3hyYcfGiZgZzCE5cMExwYzLrGErc/uZfvLh9tccA5gxgxYs6bpc2BUIOWnywkMD+TkJyc7fc72xpN37YLAQJgwodOHG7AYwvJh3FNB5l0XwqbVNWx5ejfFh1sZEQaGDYPKSigvb/pu8hXJOOwOlKXzyWTaarFKSuDAAWMguLMYwvJxlFJk3DCCJb8aRvZBP9b/v/V88h+fcG73uQvK3nQTvPMONLqcgPZ6O0opSo6WdOpcWrcurKoqOe6dd17ChQwwDGH1ERIyIhl560SSly0AYM/zezjwrwM4nU3JZAIDIe7EZt5+W+w5p032hcSHdOocNTUyCO1JXh689RbcdVfH3kKDJgxh9TJOp7w6Q0Nl85HfhQvhRFkUiYunEpoQyp4X9vDe0vfYt2KfrOaoNSGBThISFFu2QOLURCrPVlKaU9qpYNyiInGjuykvh08+gfvug5DOaZOTne/O9Wt8RlhKqWSl1Aal1EGl1AGl1A9c2x9VSp1VSmW5Xtd6fOdhpdRxpdQRpdTV3qt95ykshNWrO1f28MrDzWYBKyXm2Jf7wrj8JzOY8u9TsARZ2PLEFpYvWM7eV/dSVVzFrFlOcnLgyMenaKhu4OjKo2S/mt2huI4cgYyMps+rV8Ptt4OpC3dJy6ktAxVfSiZjB36itd7tyi24Sym1zrXvz1rrxz0LK6UygDuBMUAC8IlSKk1r3YlIN+8RHy8R4Z1h3F3j2PCfG4gaEcX4peMxmU1YLDB2LBw6YibzxnRGXD2CkuMlHHzrIDv+bwe2WhvvffM95v1oDq+/40dEoB/RGdEUHiwkd1MuqXNT2zxfaSm4EwUXFYnp19VB4NpaGWT2a3vduwGBz7RYWut89xI8Wusq4BCt5GH3YDGwQmvdoLU+ieQWnNrzNb10rNYmB0N7mMwm5vznHGoKa1j9w9Wc3HAS7dRMmQKHDkkZi9VC3Jg45j0yj7s/uJvpP5iOxc/Clj9upP6zLZTV+uEX4EfcuDgOvHWArOVZbZ7PbJb33Fz44APJq9FVrrsOXnih/evrhSliXsdnhOWJUioFmAhsc236rlIqWyn1vFLK3Qvo1IIIruN5dVEEp7N5eNKYMTIm1BksVgszfj6DyQ9M5uSnJ/noux+x5587qS+rpaGqgfy9+RQdKsLeYCc4JphJ90/ixmduJDIlktAYK7WltUz8j6s5u+UsmXdnUny4mPNZTVNJagprqDpfRUODtFA2mySpWbr0QkdGZ4iNlQmYzz7b+n6HA156qfP9zL6KzwlLKRUCvA38UGtdiax5NRyYAOQDf+rqMb29KEJlpUxbd5ORASdOwNlOZplXShE/Lp4rf3sl8/97PmarGb1xEy/esYpjHx5j+5Pb2f70dpz2prtV2zWBYYEMSQ8iOzeKqd+dyr7X9xEzIYa9L+2lrlwSwNeW1VJ2ooysLEhLEw/g7Nlg6WInwemE99+H5cslfnHwYPEytizzyivihOlKv60v4lOXp5TyQ0T1qtb6HQCtdYHW2qG1dgLP0mTu9ZkFESIiIDFRprBDkxNi9erOmYSeBEYEMnHZRO57fRH+tywm88HZXPfUdTRWNpL1YtZX4oqfFE9Jbgnn95yjsbyWQWmDyFySSdHeIgZPHkzWC1lorTm/+zxx4+I4eRJGj5aWdfTortVp0yZ49VWYPFk8iJmZEr/4/vtNLZPTKWXmzIGEhK4dvy/SaWEppd5TSl2vlOoRMbqWPX0OOKS1fsJju2c615uRRRJAFkS4UyllVUqlIqs6+qxPavZs2Lmz6bPFAldfLYG2F4NSijvvNrFqlUwbueInV1B0qIh3lr7DK9e9wrqH1tFY20jyZclUbdnH+fOQNDUJp91JbVkt1eer2bN8D06HE2uYFa3h009h/AUJvttn61ZxdNx7LyQlNW2PjYWpU+GNN6R1/uc/JdFoSsrFXW9foysiqUHWAc5TSv23UmpkN9dlBnAvcGUL1/oflVL7lFLZwDzgRwBa6wPAm8BBYA3woC97BM3mC6MakpNlfGjLlq4fz1Zrw+rn5O67JYPTqnfqsQRbKMkpIX5yPMmzkrEGWqkpqmHq3EDefktTUamY8//m0FDaQOy4WI5+eJRzeySCo7ZWMixlZnbu/KdPS19JKZkT1hrDhklLtX49fPOb0moPFDptSWutlyilwoAlyKoeDymlPgf+CfxLa93Gqk2dPv7nQGuRaqva+c5jwGOXct7eJDxcPG6eT+1Fi6TfMX5811zbNUU15HySQ9CgIOIabJTvrqQgJpWvfzoL/2B/tFPz1pK30A5N8b58vvGbTJYvh6uvtjIoPZriI0X4B/ljq7KR/Vo2WTszeOKvHd8OFRWSFsBigbvvbr8vVlMD+/fD/ff3/z5VS7p0uVrrSq3101rrqcA4YBfwDJCvlHpGKdVF63xgsXChRDK0ZNy4znsJ3UQMjWDS/ZMYtmAYo24cxS1PXMHw+cNYv1nijmx1NgaNHITT4aRwfyGBgZKbIuvDM3z2bjFnt55lxs9nsOB3C6g+X03g9o1gt7V7zv37ZdrIggUSl9iRg2PPHmmpR4zo2rX1By7qOaKUSkDGka5HBnbfRhwJ2Uqpn3Zf9foXFov0PSpaLAmcmQk5ORd3TP8Qf/wD/VFKMW2abFu5EkxWf7RDo1AERgZSfLgYR2UNfgf3kjJ/BJaZ0wiMCiRoUBBX/PgKHFGxfPbYZ81iD92cOiVm36lTkg4gIqLjetls0jrPnSsBvC2vub/TFeeFn1LqVqXUKmTxt5uAPwKDtdb3a62vBW4BftUjNe0nZGbKk9wTpbovUmHRIjErX3gBLAmxWEOtVORWcH7vefa8uIfkK5K59puJBIco3n9fYWt0sncvhM0cR/rN6ZSfLP/qWE6neC6zssTsu+66ztfjgw/EM7hgAcybJy3dQKIroxX5SB/oNeAhrXV2K2U+A3o2Q2QfJz4eXn4ZZs1qinQAiUwvL+9ca9ARQ4eK2/ut10ZReXIv1IuJV11QzaRvTmLXLiiptpKSVMI/Hz6PKW0k6emKxMlN3oWSEhnTuvJKGNmOm0praZ1KSuD8eelXbdwomaPcEyLDwro+LtbX6crl/ghxUtS3VUBrXQ60HYxmQECABKqeOCEDsm5SUyUyfOLE7jmPvz9cdUUVL7waTVRYI9ue2obZZCZ/dz6TFwwngmDWvF6OrqtnyxfiLndTVQVvvy3b3A4Vp1NiCRsapMU9fRqio2UcLjBQouItFoiKktRs3/pW0xhWeXnnMj/1J7riFXy5JysykEhMlMgET0aOFLOru4QFcH5PPiE1BRSWKcxlxQSMSSEgUlLWBpsbGB9/lsjJI9nxooQvORzi8SsthXvuEcFs3CgRIiaTiCMoSFrV8nKJfG/JO+9c6NiIjJTok4HEAGugfYOHHxYz6957m27A4ODWE29eDNmvZZN+Szq1xbU4yqtozKtEmS2Ez5xGaHIUAOZgM7kbcpn505nMOiETGpcvl37RokXy+dNPRehz5zY//v79MrjdEq1lPKzlQ8Nk6lx23f7EABtd8A2GDpWb96WXmospIkKiGC6VsKQw1v9yPaU5pUSkRhAU7kfIxBGkzhjKmo2BOJ1O1v1kHUPnDkVrmYd1/Li0QJWVUq8DByTsqrUB47Fjm6aXeLJvX9uu9YE2jcQQlpcYPBgWL5bgXPfTPDm5e2bgpsxOYdGfFhESF8K5veewVdu47OpYDr57EFNtBW8+uBGHzUFQdBCl5+vIzRXz9I03pC733istUlei27WG3bsljKmt/QMJwxT0IoMGiSt6+XK4/HLp7F/MVI22CEsKIyg8CFOUCeUHYSd3c2D5exAfzdCR/liDrARFBDJyJFxxRdeDbz05elRaq7YiLAaasIwWy8sMGQJf/7oEs1ZUdD63RGcw+5kJiQ0hZmwMxz8+ToDVhN+sSajaOnBCRV4FBTtyqTldRER9PpXnKnE6XK68zqyM4EF2tuSTb4uOEoH2NLW1rS/S11MYwvIBlJJVOz76SFqx7iJhcgJxmXGc+eIMMaNjuP3N20kNr0XPupKJSycSlxlH/q58rAf3cuyjY2z67SY+/PcPqftiV/NQ/A44eFC8he3FOnrbeXHwoESC9BaGsHyEuDhxHqxd29xs+mry4gcfdNltWF9Zz6nPTxESG8LQK4aSvzsfPz8nY28cyvEDdQybP4zBkwfjTEhkwvdnc8NTNzA4M4rnbvmQU5ZhnTpHYyN8+aU8GNqipMS7A8QFBRI9ktqLI6yGsHyIKVNkEuCmTU3bsl93ZVeaOrXDuKCWKagDIwNJmp6EUznZ+8pe7PV2Jn9zMleMr+XYlgJ2/3MPgVGBhE8bLUuw5ucz5cwHTFycwscPrWfb37aRuzkX7Wy7g7Rnj9S7PVPv888vdNn3JqtXi7kdGNh75zSE5WPMmiV9rb175XPS9CSyXsyiSgdJU+YOg28lavfwW/txbJG5nuW55ZzeeprdL++mYGcBdocdh81BcHww2cv3kjo1gYrhk4kbG4dSYLI1wBNPoOZfyYxn7uO+9fdh8begUGx7ahunPz/dan0PHBD3e1s4neLC90JGBEBCrMLDm4eP9QaGV9AHcbvh4+MhbmQ0kSmR7H9jP4lT5hJ9aLP4tU+dgl//+qs7RmvN2awCSlbswh53jMbQKEqPl6LrNEGDg1A2RVlOGdXF1eTvzyc61caZMxJlUVMDQwq2yd0/fz7U1uIfHMSIa0ZwfO1xLnvgMkx+Fz6Ds7LEk9iembd6tTwsvMXGjW0PAfQkRovlYzgdThprGrn5Ztj5P+vRhw5j9jOTeXcmeTvzKEqfKTMHa2uxf+12qv7vBWrPlbLpN5soyykj/O5riQh1kBp0nrG3j2X4wuGkXZPGvP+ax5R/m8KJT06AE67/+/VMmgR//7tkjXJ8uEpil95446s5HuFJ4aQuTCXnkxxUK7begQN8NVWlNbQW4XprOn5xscQ9emPmstFi+RjaqTn8vohp5K3j2frBQS7ftRNltTJ+eDJHss9ybuc5TtXOJWpuBJt/tgH9gz8TMTqWsKGR5KzJITw5kcqKMvL/dy2NodHkbcsjLDmMzY9t5syOMyRdLskpMjPhqScdXL/3t/if3QofvicBgSdOfJV1MzwhnLzNeRfUs7xcxtzamxn88ccwfXrP/E4dUV0tyWzuucc75zeE5WO4W6fq89Xkbsol63QEOiSYpPGDMB07RtDna6nRg8j9sIS9pTKPJ3GIhSsid2O96nqK/BI5lziNEUMaKdz3NLVnS9F+Vk5/fpqYcTGMu28cRz88itYaVVjIv2/7IWHnj+L3z0fgzTfFC5GU9FUzY7aYSV+c3qyOTqcUveuutq/DbpcWo7WYwt5g7Vr42te6d8C9KxjC8lFC4kMYe8dYUq+H999xUHWikMxxYyj5/CBq+uXcNPwwAYEQfd911Nn8OLXmENm/fpWywgYsSWtYEzqS2jMmVFEN8ROCCY4JZsTCEcRGK2LiT5OdcTuZxZsIHX4lL9/zCQtjI5l+Y+t18Q9pvszImjUinPXrpT/YGlu2tD9g3JOcOSP9Ps8FHnobo4/l4wQHw933mqkPj2fHyvPUny7CVFlGRUomfjiwV9Vhq7dhjgxl4oofUh8SR86hSsz7shg9wYIt1Z+cfbvZe2gr/v/3BPZrbiD+fx9msM7nxB/fYvsPVpA2LZITJzpXn5wciWD4zndkPtaGDReWKSyUwdiuhkh1ZSWWtigrk7wiF5Meuzvp88JSSi1yrTZyXCn1kLfr01OkOQ5THx5N1bQFZHz8Z9IKPqcgcAhHf/cuxYeKiUiNoK64gaBpw4hMH8xloceZmf8KGaceZZ5jOfdkPUnIl+8S7V8J6enEvvkURfYI/Bz1KCUTMDuKYiooECHdcot8njFDHBTr1jUvt2ZN63O1OuLLLyVC4lJYuVLSCPS2e70lfdoUVEqZgb8BC5Hc7TuUUiu11pf47/E9hi0YxmjVSNHRFDYU3U/GqvdIn1MGpnPwzKcQFUXc175G2m1mtq4LIqYihfD3PsJh0Uy226gEiv1iCQ9uwO/Xv4bnn6ds1PdJtp8g128MCxbIJMVly5oGez/7TFz+aWniXfvwQ8np7ukgvPJKGXN77TVxZNTXS0vW2b5NVZV8x2YTE27mzEv7nfz9vdev8qRPCwtJN31ca50DoJRagWSP6nfCsubnwuHDxAQEcPUvp3L4bSt7Vm0gM6YCc26u2FAPP4w+fYayhnmMST1Lo38g88MDcRaeJwqw2yo5WxTG8dfPM+OuRUxc8SS7/S6jfnYaDQ1+zJ3l4M1nKrh9iT8n3sumNOAyZs+WiVTr1sFtt7U+r2r8eHlpDY8/LtPzX3lF+jjjxkmgcUvKyqRl8/OT1tJuF0/e9u0XP+508mTXlx3qKfq6sFpbceSCkRWl1APAAwBDWvsv9wVGjZKXi/QfJJF/+3W8tAbu+AcEHc2CzZvZ89pBwnPOEXpqP3WDInD6RWILNFFbV80gUy3hcy8jfOPjHNg+jjNDZhJVvpUhm18h+PAkYseN5NSpaF64186oG0Zy0z1ye5SUSIvSUd6KmhpxJt54o4isuFgCRTZtguHDZbwsPFzc8KWl4vhwC2HtWsnrvm+f9CvHjGn9HPX1Mug8YULz2L+CAgmdWrLkon/hbqWvC6tTaK3/AfwDYMqUKX1iZtC5c2KGtTdONHgw3DZ0Gzu+d4SUK1Owp11NyayRzKv7FZwLJPDfvsWHcd9l5s+mQrCVhimZhCy6msjUFCb86y2CjpVzYshcvvSfxc+OvUpRQBSBNSZGLZ6KJWMUKOn3HDki/ZaO+PDDpmBcpSSQY9Ei+bx/v4hnwwZpwYYNk0UiIiObMlSlpcnr9delrxURIUG+lZXNf4d58yQ/oxu7Xcas7rvPdzLu9nVh9ZkVR7pKVZWYSqNHi2l0QWe8sBA++oigkWlk/GwRq14px9FoY9myIZhWFKJtdo7f9DMG/ehxdl73KLYde7jp2LvwxBFITcUydAimy25lbME5qj/eTOmIJOpXfsrUH92GOvAsW3ZP5JmsJQwfLgGstbUSkBEeLlZnfX1zs+vECZlL1lqrprUIrbYWfv97OYbNJmL74AOZyzVtmqRPi4uT8bG6OhGU2SxTadoK8m1okPCvxYt9o2/lRnVm0WdfRSllAY4C8xFB7QDudi2Y0CpTpkzRO7sw18ibOJ1y82VliWk0ebJrx4cfyp19002c3V1ARXEj9rAockvDGXffGKJthVSFJuC8fAYWNL8K/z/G137B90avh5/9DJ58Ev72N2rLammYOotHC77DlF8s5J6qp1DBwdQvvoMj9/wXJ6KnMfzHN3HkiMynWrRIhPP005JVyj3463RKnox7773wAXDokIxpjRols6TdLUphofTDLrtMYglLSmQKmKdncupUadna4+WXpV7eCvJVSu3SWk9pub1Pt1haa7tS6rvAWsAMPN+eqPoaJpOEHWVmyjI4W79wMP3sW0y4ZxzmcRmUlMDnJyUQbkgkRNWcJqHhNJsX/j9mH/gn/uYi+OEPqXvKTOHwGeD8RGy7hx6CJUuo+N6jNGzL5k79JEc2+qGeXEbD937KOyVXMfuZ35G73cHatfDtb0NoqIjrn/8Ud7tnJqZNm6TF8RRVaSmsWiXbbDbZ7xbVJ59I63fDDVKdyy6TVskzSsOdhXfXLjmv3S4tXVCQJONJTBQT0mr1nqjao08LC0BrvYp2ViTpL8yZA7WrN2NdNJufPDcYp1NSk91yC4RZG+Cdd9j/17U46+qY8fkTFAQmYh0yidgZMzD9HeptZrj/PnHbDR0KaWmYnnmOLethxtHnOPhuNpX2OWz2X0T6vjfZnPBDrrzSzOKvNdXh/ffhqquai8rplEHjefPkc3m5jCWFh8sgbUSEtFjf+Y5EQ4wbJxFT7rGwkBBxZtzYIurDZLowpXV1tZiHJ06Ii7+mRs69bZvMCfP22JUnfV5YA4HSUjGbysvnMiNerLmgIHFNv/+Og1tWfYughgqGnNqPc1A0FanjqYwcRbF9JDlbxWzLzUVcczfeKE3QSy8Rl5iI0wmfBN+Mf/5j/HnJLqoS5vHdojXcdadu1rFZs0aySLWchbtmjSSiATh8WHJ3zJwpM1v+8hdJ5xYUJONfkyaJ8DxXdBw8WFqv8+fFWdMeISHy8vy+0ylR9itWiGg7u75XT2MIy0fRWrpRWVnwj39IizBliqxf7Obq+XZqn3yWU41xFExYyrTk1ZzdcRbGjCUlM5YxGWF8XClePT8/5A5eu1YGmn73O2x/+ivbt5tIS4sid+4D3HTqLyT87AfUvRDH1iV/YfhvvkHM8DDKy6WlcHv43DidIli3iXjihAjlwAG5ye+6S4ToOfbV2jjT7bdLUO+kSW272dvCZJJzjRvXte/1NH3aeXEx9LbzwuGQf75S0k8oL5f+ituD5V5U4Px5SZp5+rSYNO6B0zVr4NFH23iav/669PyTkihd9QUlf34Jm8nK8cS5BJ89yvnUyxnx9Tk895wcd80aRGVhYZz/zT/YHbmAvSEzaGyUMaCZqWcZWfg5sWG1+Nnr2TNmKSMnBJOdLanNHA4RU3a2CO3wYTH5oqKkVXzwQbm2i8HpFJPQ4ejaqibepl86L3yJ9evFnVxVJZ/NZrlZHI4m299slhuxvFzE1Ngo7yEh8qRPSZEQITcOh/QrWhXVa69Jc5CUBAUFRIU4iJqdBEeOkPrf86n90xFOjw5l9aci2EOHRFgLF45i/XqoDr+GjD2vcWjONEqqLDz4IJSWJlJUdQdZZ8WEO7EWqt+WeqalfaVhJk6U8ai77mry9l1qpluTSVrELVsktOraa+XB0lcxWqxuwmaTsZeQkO4bpHz1VVkF0nMwtF0eflhmF48YAT/5CXz/+zB0KDt2wH/9l7SOw4ZJlEJqimZRzlMUn2sk/ZkfkZwsrU1rdS8rE5N09Wox1bQWkQ0f3j3X2ZL8fLFY77vP+/kIO8JosXoYP7/uzU++bZt00jstKodDmkL30vXl5eJtQNzZM2ZIqxUSAnfcATNmKF547tssyfop/uyH8LYzwkRGSh9v+HB4913YsUNOV1kpURRRUSKAoiJxn8+cKeFMTqeMewUEyG8THCwtcHBw+3kyBg+WvuTOnVL3voghLB+ksVFEsGxZF76Uny92ZkCA2KUtmp+lS+HnP5djpqWJAMZmmvC3ThLfdXupllwMGQI/+IHksfnyS+kTnT8v40nR0bLfLYiICGnB8/PlvbFRnDHuVVXq6+XBMX9+6+eaOhVefFFaSF8JrO0KhrB8jIoKCazoxH3enKoquQO1ljvy979vtjshQbxuubnSqB0/LmKjOrFLWW9BhsGGDpW/nU6Z7pGXJwG0IGI5f17EByIqdz9zyJCmlR7feksWuAsMlNwYUVHNz7N4sXgLly71nRjAzmIIy8d44w0ZPO1yqum4OLG/iovFdvMc7HExZYqML9XUSNQD0LTi3UViMjUJbepUMRNfeUUSeV53HaSntz5tBODWW6UulZVNzp85c6S8Uk0m6CuvuB4CfQhDWN1AcbGYQpeK0yk300Xlb8/Olk7OihVtehXS0mR3XJxHPoiEBLHNOjNC2wF+fjJYfMUV4jE8cUKi1LdsEaE4HOLtCw9v+k5wsLzcIvv8c+kaup0WQ4dKFQ8ebD6G5+sYwuoGdu6UINLrrru0xblLSprfdF3i73+X/pXFIivGtUJcnLRYv/2tx0aTSWyzzz67uPn0bWC1ihA8xVBVJQs/REeLM6Vlyufg4NazOs2fL9bt8OG+FcHeHoawuoFFi0QU69dLp9xtiVVWyn3e6rSPVigru0jPYmOjeAm+9z159LeBw9HkoWvGyJFNHaIeJDRUNJ+TI/GE7ukkpaXSv9JazOCW9VNKcr9/+WVTTKKv08e6hN1LTY1EJNTXX/qxBg2SPHZ33900TT05WXIwvPSSeMY6Yt8+j6khXcFigUceaVdUINHmUVFNg9hf4Q4N6SWGDROX/513yvhfeLg8G6DtNaxSU8Xx0leGXQdsi2W3yw2fliaBo+4oifBwiX64FBdvQkKT7yAxUcT7i1+ISZOcLOcym+UmT0pqas1KSzue/t4qJlPzkI1WKC+XMaQxY+S9GZMnN7nqegmtZUwsPV2cKiCzplevlv/DrFnN+1ogZQ8e7Ho8oTcYsMIymeCmmy5c4d096l9XJ//8hATpD/j7t3qYdmlokADajAx44gm5od2xgPX14vLetk2EZrNJ8GpPddJXrZLrzcpqRVhe6LisWiW/rVtUIJ/vvFN+t82b4Uc/Eu/lwoXygJowQbymhrB8GJPpQlGBbLv55qbPx45J7JrdLv2TjAyZLt8Zy8nPD775zaZOekRE284Nh0NuqLNnZXD4hhsuTsytUV4udQgKkpu3pqZ7jtsaTqf0n266qe0yBw6IcFpGy7uxWmHBAvEuPvustGLu31Br6ct20EB7nQErrM4ycqS8QG7+rVvlqWkyScszcaJ0yqOjLxSbydT5xc7MZrnx3YOrb78tx5s4sVlypotiyxZpdUFu2gtarG7CbpeA+/YWmVu7Vt6/8Q3417/aLxsUJBHzW7bIA27aNDnHM8+I2ZiQALNne3e1yLbwwSr5LmZz0w0K4gTYskWe0mVlst8d2hMXJzdGV2a11tWJGMPDZUhp+nT44guZ0JiQIN7Fi5mWUVLSFHPo59cz6wE7nbB8ubjL3eGKLTl0SEze66+X8jExHY8BWizS35o5U77r7y+ZBbZtk9kAr7zSNMqgtfTDgoPF8ujNFRwvqLf3Tt33CQ2Vqepu6urkJj56VJLAVFTIPzw4WPoHgwa1L4yRI+XmGzlSHBmBgWISaS2DrR9/LH2zmBjpd3TGHD10qHlOCPf8r+6kokJan6uualtUIF5P9xR8k0mi119/vXNR7Eo1mcZDhkiLlZcnsY9aN5nSR45Ii797t1ynwyHhYb3smzGE1Z0EBsqN5XlzOZ0ituzspsQoDofcKFareCVzc+WmMZkkr8TYsTJmM2hQU+tSWip/Wyxygx47Jk96t/vZ3TLabHIc9/bNmyVM6I03xKXtaRZ2B5WV0gddsqT9FiInh69yxLuxWuVVUyNR911h+nRxgGzbJiaixSKvliucaC0t/rPPSqs/apR4Xs+elf5yV/w2e/bIIHVnPLfGfCwvUl8vHfnwcBHW++9La5WaKjfDnDmy3W4X07CrfYk9e+Rm9nxaZ2dLyrD/+Z9Lr39xsbjMlyzpeHhi+fIL876DCG7//guTyXSWTZvkwXTvvR0H6paVybkqK0Vkx4/L/yAgQKwNP7+mY7hlYbc3DVjn5kqf7vLLm47Z1nwstNZefwH/AxwGsoF3gQjX9hSgDshyvf7u8Z3JwD7gOPBXXA+Jjl6TJ0/WvojTqfXBg917zNde09rhaH6O/fu1XrxY67KySzt2cbHWzz6rdW1t58q/+mrb+1at0nrz5uZ17QqHD2v93nsX992uYLdfWEdgp27lPvMVU3Ad8LCWPIF/AB4GfuHad0JrPaGV7zwNfAvYhqQ/WwRcfJi2l1Gq6+tJtYfTKcd0P4HPnJGss/X1sn3nTum/uXFPXCwpkTLueVYxMeLuPn26KYPt4MGyAEFSEvz1r/L9yMimOEetm8xRi0WOffgwvPeetA7BwfI+erSYgNdcI32i556ThRe6Gm85apQkAH3jDWlhQIJ9u3vhua44onzOFFRK3QzcqrVeopRKAT7UWo9tUWYwsEFrne76fBcwV2v9bx0d35dMwUuhrExu0LbGunJyxNSprhaTbetWeOop+NOfpE/ywgsy1uRe6M1kkuOFhYmgBg2SfefOyfvo0dIvCQuTiY61tSLQuDj5bl6emFNKSV8uPV1Cu5xOqaM7LXVlpZSrqpLzeQbi19SI+CIjxbt4sXkCq6vlYWC3y1hZT67s2JYp6IvC+gB4Q2v9iktYB5A00pXAr7TWm5VSU4Dfa60XuL4zC/iF1rrVdfxarDYy+dSpU71wJT2H3S7pxgIDmxwG7pvabJYW4o03xDHy05/KTfyf/ymexIgImTBcWipC6wm0lhbx+PGmMER3mFd1tQhs0CCZEtJavzEnR5LVLFzY9lyuzuCeNJqaKs6Onpgs6fWcF0qpT4DWJvz8h9b6fVeZ/wDswKuuffnAEK11iVJqMvCeUqrLAS3aC6uNHDkiwabdmQcDxKX8+uvi2o6Pl7+rq8UMs1qbcp+bTNKh//RTMbtmzBAxXn45PPYY/PKXTVHl3Y1SkqvCna/C6RSRnTsndQkJkdCxnTvlIeHvL2NSY8fKg2LYMBHUJ66M2EFB0vK4W2e3mau1iKesTB4YkZFNzhF3Fqz588Xp8OKLkntwzBg5Tk8PKvtMi6WUWgb8GzBfa91qjLNSaiPwU2QBBJ82BU+ckClOS5deeurj3Fy5ITdskJvp6qvliZ+bK8Jwu3/Ly8UTePiwZIT1dKu/8IKEAZWVyXefe076IRe7yFt34nDIg+jwYXlwgPxm7luzrEzG4+x2aeXcM4y1lnHBiIgmgfn5ifAsFtlnNss+94qR+flyzEmTmvI32mxiFl/MgLLXW6z2UEotAn4OzPEUlVIqBijVWjuUUsOAkUCO1rpUKVWplJqOOC+WAk96o+5tMXy4/NNeekme3F3OYeFCa5nuvn69PG1DQ8UJ4c5b6BnXGBEhUQqFhReOVZWVyXHcM0vcadB8AbP5wkmRWjc5QdzYbDIud/asBOSnpV1cy1NUJGNgsbEypFFfL2kab7vtImcXtEZrrsLefiEu8zO0cKsDtyB9rCxgN3CDx3emAPuBE8D/4aPudqdT6zVrtH733a65kx0OrTdu1Pqll7TeskWOU1MjLl/3/vJyrZ97TuuGhqbv7dun9c6drdfjD39o+vzRR1o//vhFXZLXaWiQ3+TFF7X+4AOtS0ou7jhnz2q9YoXWBQXy29bUdP0YtOFu9xlTsLfwllfw5Ekx5e64Q7xh7aG1JOucNKnjKSTnzkmG27Q0aRlXr5ZOf2vnWLdOvHtJSdL/+Pd/l35XX8uA5MmpUzKY7h7gnTnzq3SKvYJPm4IDgdRU6dusWCF9m9amrIDc8K+/LlMm3FH17ZGQIPFyR4/KdI316+UYTqc4NKZOlf5IVJQIzk1NjfTb+rKooHkqNrf56AsYwupFwsLEmfH++3JTX3ddkxfL6RS7v7xctncl65PJJONG7nGl2bNl+7lzsHGjOAQsFulDuM8XECDeRM/c8n0dpXwnJXUff171Pfz8xIGQmirzitxzo0wmEcQ991x8KrU9e5o7LRISJDnL3XeL88QdmeB0yrnj4sShYdD9GC2WlxgzRkyYlSvFzTt//qV5pOrqmnJptEZGhpiGy5c3RZjPni1BqdOnX/x5DVrHEJYXCQmR1qSkRBwb5eXiBp85s+smze7dHWd4SkwU8UVFiZinTRM3s0H3YwjLBxg0SPKUg0zreOUVGWNZuLDzzoWamguXMW3JO+/IwPGQIRLWFBt7aQlGDdrG6GP5GJmZEoqUliaDy+XlnfvelCkykbIt9u+XwNlp08Rr6J4/1Vcyy/Y1jBbLR0lNlSkba9eKWfi1r7VfPjKydRHW14sXMjRUHCMgY1w7d4pZePKkRDR0d0zjQMdosXyYkBDx6qWny/hXe7kqlBKHyMaNTdv27JExsSuvlLEzN9HR4sgYNEhMzddfl1Ahg+7DaLH6ABkZYrq9/bZ8Dg2V6faJic3LTZok0+5rasQZYrfL4HFrjpCEBPl+crJMrd++Xdai8hzrMrh4DGH1EVJS5AUSPJuVJSFKt9zSlPmpoUFaoocegh//uH1nRmioTIB0M3WqODNeeEEWHujIEWLQPoaw+iBxcTJ1pKZGzLhrrpG5SxaLzNEaNqxjYaSnSz/L4ZC+lskkwl26VPp1GzfKUIDh3Lg4DGH1YYKDJdf5Rx/JYK9bTAUFTWJpi+homSafmCihT+6UbRaLhFQVFop7vrFRpth3Ji+f0ymt4P79MGLEpc3+7esYwurjhIRIxLwnLecxtYXWIsZTpy5MtBkbK6sygrRer78ufTaLpWkuGEh/zB3xYbfL4HN6+iUvDtnnMYTVz3A4Op/pVrtWU9m+vf1y7eVXN2gdw93ezzhxomvzkQICmqbDG3QfRovVzxgxovPCcrvV+8u0EV/CaLH6GZ1dOshub55G2aB7MYQ1QLFYxKMIEojbU2tmDVQMYRng7989C5wbNGEIywCrVSZKGnQfPiEspdSjSqmzSqks1+taj30PK6WOK6WOKKWu9ti+yLXtuFLqIe/UvH9QVdWN+fQMAN/yCv5Za/245walVAZwJzAGSAA+UUqluXb/DVgI5AE7lFIrtdYHe7PC/YWQEJmr5V4txODS8SVhtcZiYIXWugE4qZQ6DriTIh/XWucAKKVWuMoawroI+lOmJl/BJ0xBF99VSmUrpZ5XSrkXXklEMuS6yXNta2t7qyilHlBK7VRK7SwqKuruevd5amqMKfrdTa8JSyn1iVJqfyuvxcgicsOBCcgKI3/qznNrrf+htZ6itZ4S47nStQEgjou21tkyuDh6zRTUrrWsOkIp9SzwoevjWcAzjiDJtY12tht0AV/JHNvf8AlT0LVCo5ubkcUOAFYCdyqlrEqpVGS1ke3ADmCkUipVKeWPODhW9mad+wulpYZHsCfwFefFH5VSEwAN5CLrZKG1PqCUehNxStiBB7XWDgCl1HeBtYAZeF5rfcAL9e7zHD/euRzxBl3DJ4Sltb63nX2PAY+1sn0Vsqi3wSVQUGAIqyfwCVPQwHvU1ck4lkH3YghrgNPQYOQU7AkMYQ1wAgJkgNigezGENcAZNAjOn/d2LfofhrAGOImJsli2QfdiCGuAYwirZzCENcAJDTXmYvUEhrAMsPjEaGb/whDWAEdrI/1ZT2AIa4BjjGP1DIawBjglJbLAnUH3YghrgLN3L4we7e1a9D8MYQ1w3CuOGHQvhrAGOMbqjT2DIawBjrGSSM9gCGuAExfn7Rr0TwxhGRj0AIawDAx6AKUHWJoepVQRcMoLp44Gijss1X8YKNc7VGt9wUjggBOWt1BK7dRaT/F2PXqLgXa9LTFMQQODHsAQloFBD2AIq/f4h7cr0MsMtOtthtHHMjDoAYwWy8CgBzCEZWDQAxjC6gX647KuSqlcpdQ+19K2O13bopRS65RSx1zvka7tSin1V9f1ZyulJnm39j2PIaweRillRpZ1vQbIAO5yLQHbH5intZ7gMV71EPCp1nok8KnrM8i1j3S9HkDWQ+vXGMLqeabiWtZVa90IuJd17Y8sBpa7/l4O3OSx/SUtbAUiWizd1O8whNXzdGlZ1z6EBj5WSu1SSj3g2hantc53/X0ecMfO99ffoE2MxFcGF8tMrfVZpVQssE4pddhzp9ZaK6UG7FiO0WL1PO0t99pn0Vqfdb0XAu8iJm+B28RzvRe6ivfL36A9DGH1PP1uWVelVLBSKtT9N3AVsrztSuA+V7H7gPddf68Elrq8g9OBCg+TsV9imII9jNba3g+XdY0D3lWSMMMCvKa1XqOU2gG8qZS6H5mac7ur/CrgWuA4UAt8vfer3LsYIU0GBj2AYQoaGPQAhrAMDHoAQ1gGBj2AISwDgx7AEJaBQQ9gCMvAoAcwhGVg0AMYwjIw6AEMYRkAoJSKUUrlK6Ue8diWqZSqV0rd5s269UWMyAuDr1BKXQ18AMwBsoCdwHatdb8PQepuDGEZNEMp9b/AjcAmYBYwQWtd7dVK9UEMYRk0QyllBfYi0+iv0Fpv83KV+iRGH8ugJSnI3CkNDPNuVfouRotl8BVKKT9gK3AU2AY8AozXWp/2asX6IIawDL5CKfV74G4gE6gAVgMBwJVaa6c369bXMExBAwCUUnOAnwBLtdblWp64y5CUbb/wZt36IkaLZWDQAxgtloFBD2AIy8CgBzCEZWDQAxjCMjDoAQxhGRj0AIawDAx6AENYBgY9gCEsA4Me4P8DHE1QKQqkXdUAAAAASUVORK5CYII=\n",
+ "image/png": "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",
"text/plain": [
"