Source code for snntoolbox.bin.gui.gui
# -*- coding: utf-8 -*-
"""
Graphical User interface for the SNN conversion toolbox.
Features
--------
- Allows setting parameters and what tools to use during an experiment.
- Performs basic checks that specified parameters are valid.
- Preferences can be saved and reloaded.
- Tooltips explain the functionality.
- Automatically recognizes result plots and allows displaying them in a
separate window.
Note
----
Due to rapid extensions in the main toolbox, we have not always been able
to update the GUI to cover all functionality of the toolbox. We are
currently not maintaining the GUI and recommend using the terminal to run
experiments.
@author: rbodo
"""
import json
import os
import sys
import threading
import webbrowser
from textwrap import dedent
import matplotlib.gridspec as gridspec
import matplotlib.image as mpimg
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.backends.backend_tkagg import NavigationToolbar2Tk
from snntoolbox.bin.utils import run_pipeline
from snntoolbox.bin.gui.tooltip import ToolTip
if sys.version_info[0] < 3:
# noinspection PyPep8Naming,PyUnresolvedReferences,PyPackageRequirements
import Tkinter as tk
# noinspection PyPep8Naming,PyUnresolvedReferences
import tkFileDialog as filedialog
# noinspection PyPep8Naming,PyUnresolvedReferences
import tkMessageBox as messagebox
# noinspection PyPep8Naming,PyUnresolvedReferences
import tkFont as font
# noinspection PyCompatibility,PyUnresolvedReferences
from Queue import Queue
else:
import tkinter as tk
# noinspection PyCompatibility
from tkinter import filedialog, messagebox, font
# noinspection PyCompatibility
from queue import Queue
# noinspection PyAttributeOutsideInit
[docs]class SNNToolboxGUI:
def __init__(self, root, config):
self.initialized = False
self.root = root
self.config = config
self.toolbox_root = os.getcwd()
self.default_path_to_pref = os.path.join(self.toolbox_root,
'preferences')
self.define_style()
self.declare_parameter_vars()
self.load_settings()
self.main_container = tk.Frame(root, bg='white')
self.main_container.pack(side='top', fill='both', expand=True)
self.globalparams_widgets()
self.cellparams_widgets()
self.simparams_widgets()
self.tools_widgets()
self.graph_widgets()
self.top_level_menu()
self.toggle_state_pynn(self.settings['simulator'].get())
self.toggle_poisson_input_state()
self.initialized = True
[docs] def define_style(self):
"""Define apperance style."""
self.padx = 10
self.pady = 5
font_family = 'clearlyu devagari'
self.header_font = (font_family, '11', 'bold')
font.nametofont('TkDefaultFont').configure(family=font_family, size=11)
font.nametofont('TkMenuFont').configure(family=font_family, size=11,
weight=font.BOLD)
font.nametofont('TkTextFont').configure(family=font_family, size=11)
self.kwargs = {'fill': 'both', 'expand': True,
'padx': self.padx, 'pady': self.pady}
[docs] def initialize_thread(self):
"""Separate thread for conversion process."""
self.res_queue = Queue()
# Create thread for performing the conversion in the background.
# Make it a daemon so it is killed when the main application is closed.
if sys.version_info[0] < 3:
self.process_thread = threading.Thread(target=run_pipeline,
args=(self.res_queue,),
name='conversion process')
self.process_thread.daemon = True
else:
self.process_thread = threading.Thread(target=run_pipeline,
args=(self.res_queue,),
name='conversion process',
daemon=True)
[docs] def globalparams_widgets(self):
"""Global parameters widgets."""
# Create a container for individual parameter widgets
self.globalparams_frame = tk.LabelFrame(self.main_container,
labelanchor='nw',
text="Global parameters",
relief='raised',
borderwidth='3', bg='white')
self.globalparams_frame.pack(side='left', fill=None, expand=False)
tip = dedent("""\
Specify general properties of your model and the steps to
include in your experiment.""")
ToolTip(self.globalparams_frame, text=tip, wraplength=750, delay=1499)
# Data-set path
dataset_frame = tk.Frame(self.globalparams_frame, bg='white')
dataset_frame.pack(**self.kwargs)
tk.Label(dataset_frame, text="Dataset", bg='white').pack(
fill='both', expand=True)
tk.Button(dataset_frame, text="Dataset path",
command=self.set_dataset_path,
font=self.header_font).pack(side='top')
self.dataset_entry = tk.Entry(
dataset_frame, textvariable=self.settings['dataset_path'],
width=20, validate='focusout', bg='white',
validatecommand=(dataset_frame.register(self.check_dataset_path),
'%P'))
self.dataset_entry.pack(fill='both', expand=True, side='left')
scroll_x = tk.Scrollbar(dataset_frame, orient=tk.HORIZONTAL,
command=self.__scroll_handler)
scroll_x.pack(fill='x', expand=True, side='right')
self.dataset_entry['xscrollcommand'] = scroll_x.set
tip = dedent("""\
Select a directory where the toolbox will find the samples to test.
Two input formats are supported:
A) .npz: Compressed numpy format.
B) .jpg: Images in directories corresponding to their class.
A) Provide at least two compressed numpy files called 'x_test.npz'
and 'y_test.npz' containing the testset and groundtruth. In
addition, if the network should be normalized, put a file
'x_norm.npz' in the folder. This can be a the training set x_train,
or a subset of it. Take care of memory limitations: If numpy can
allocate a 4 GB float32 container for the activations to be
computed during normalization, x_norm should contain not more than
4*1e9*8bit/(fc*fx*fy*32bit) = 1/n samples, where (fc, fx, fy) is
the shape of the largest layer, and n = fc*fx*fy its total cell
count.
B) The images are stored in subdirectories of the selected
'dataset_path', where the names of the subdirectories represent
their class label. The toolbox will then use
Keras.ImageDataGenerator to load and process the files batchwise.
With original data of the form (channels, num_rows, num_cols),
x_norm and x_test have dimension
(num_samples, channels*num_rows*num_cols) for a fully-connected
network, and (num_samples, channels, num_rows, num_cols) otherwise.
y_train and y_test have dimension (num_samples, num_classes).
See snntoolbox.datasets for examples how to prepare a
dataset for use in the toolbox.""")
ToolTip(dataset_frame, text=tip, wraplength=750)
# Data-set format
format_frame = tk.Frame(self.globalparams_frame, bg='white')
format_frame.pack(**self.kwargs)
tk.Radiobutton(format_frame, variable=self.settings['dataset_format'],
text='.npz', value='npz', bg='white').pack(
fill='both', side='left', expand=True)
tk.Radiobutton(format_frame, variable=self.settings['dataset_format'],
text='.jpg',
value='jpg', bg='white').pack(
fill='both', side='right', expand=True)
tip = dedent("""\
Select a directory where the toolbox will find the samples to test.
Two input formats are supported:
A) .npz: Compressed numpy format.
B) .jpg: Images in directories corresponding to their class.
See dataset path tooltip for more details.""")
ToolTip(format_frame, text=tip, wraplength=750)
# Model library
model_libs = eval(self.config.get('restrictions', 'model_libs'))
model_lib_frame = tk.Frame(self.globalparams_frame, bg='white')
model_lib_frame.pack(**self.kwargs)
tip = "The neural network library used to create the input model."
ToolTip(model_lib_frame, text=tip, wraplength=750)
tk.Label(model_lib_frame, text="Model library",
bg='white').pack(fill='both', expand=True)
model_lib_om = tk.OptionMenu(model_lib_frame,
self.settings['model_lib'],
*list(model_libs))
model_lib_om.pack(fill='both', expand=True)
# Batch size
batch_size_frame = tk.Frame(self.globalparams_frame, bg='white')
batch_size_frame.pack(**self.kwargs)
tk.Label(batch_size_frame, text="Batch size",
bg='white').pack(fill='both', expand=True)
batch_size_sb = tk.Spinbox(batch_size_frame, bg='white',
textvariable=self.settings['batch_size'],
from_=1, to_=1e9, increment=1, width=10)
batch_size_sb.pack(fill='y', expand=True, ipady=5)
tip = dedent("""\
If the builtin simulator 'INI' is used, the batch size specifies
the number of test samples that will be simulated in parallel.
Important: When using 'INI' simulator, the batch size can only be
run using the batch size it has been converted with. To run it
with a different batch size, convert the ANN from scratch.""")
ToolTip(batch_size_frame, text=tip, wraplength=700)
# Verbosity
verbose_frame = tk.Frame(self.globalparams_frame, bg='white')
verbose_frame.pack(**self.kwargs)
tk.Label(verbose_frame, text="Verbosity", bg='white').pack(fill='both',
expand=True)
[tk.Radiobutton(verbose_frame, variable=self.settings['verbose'],
text=str(i), value=i, bg='white').pack(fill='both',
side='left',
expand=True)
for i in range(4)]
tip = dedent("""\
0: No intermediate results or status reports.
1: Print progress of simulation and intermediate results.
2: Record spiketrains of all layers for one sample, and save
various plots (spiketrains, spikerates, activations,
correlations, ...)
3: Record, plot and return the membrane potential of all layers
for the last test sample. Very time consuming. Works only with
pyNN simulators.""")
ToolTip(verbose_frame, text=tip, wraplength=750)
# Set and display working directory
path_frame = tk.Frame(self.globalparams_frame, bg='white')
path_frame.pack(**self.kwargs)
tk.Button(path_frame, text="Set working dir", font=self.header_font,
command=self.set_cwd).pack(side='top')
self.path_entry = tk.Entry(
path_frame, textvariable=self.settings['path_wd'], width=20,
validate='focusout', bg='white',
validatecommand=(path_frame.register(self.check_path), '%P'))
self.path_entry.pack(fill='both', expand=True, side='left')
scroll_x2 = tk.Scrollbar(path_frame, orient=tk.HORIZONTAL,
command=self.__scroll_handler)
scroll_x2.pack(fill='x', expand=True, side='bottom')
self.path_entry['xscrollcommand'] = scroll_x2.set
tip = dedent("""\
Specify the working directory. There, the toolbox will look for
ANN models to convert or SNN models to test, load the parameters
it needs and store (normalized) parameters.""")
ToolTip(path_frame, text=tip, wraplength=750)
# Specify filename base
filename_frame = tk.Frame(self.globalparams_frame)
filename_frame.pack(**self.kwargs)
tk.Label(filename_frame, text="Filename base:", bg='white').pack(
fill='both', expand=True)
self.filename_entry = tk.Entry(
filename_frame, bg='white', width=20, validate='focusout',
textvariable=self.settings['filename_ann'],
validatecommand=(filename_frame.register(self.check_file), '%P'))
self.filename_entry.pack(fill='both', expand=True, side='bottom')
tip = dedent("""\
Base name of all loaded and saved files during this run. The ANN
model to be converted is expected to be named '<basename>'.
The toolbox will save and load converted SNN models under the
name 'snn_<basename>'. When exporting a converted spiking net to
test it in a specific simulator, the toolbox writes the exported
SNN to files named ``snn_<basename>_<simulator>``.""")
ToolTip(filename_frame, text=tip, wraplength=750)
[docs] def cellparams_widgets(self):
"""Create a container for individual parameter widgets."""
self.cellparams_frame = tk.LabelFrame(
self.main_container, labelanchor='nw', text="Cell\n parameters",
relief='raised', borderwidth='3', bg='white')
self.cellparams_frame.pack(side='left', fill=None, expand=False)
tip = dedent("""\
Specify parameters of individual neuron cells in the
converted spiking network. Some are simulator specific.""")
ToolTip(self.cellparams_frame, text=tip, wraplength=750, delay=1499)
# Threshold
v_thresh_frame = tk.Frame(self.cellparams_frame, bg='white')
v_thresh_frame.pack(**self.kwargs)
tk.Label(v_thresh_frame, text="v_thresh", bg='white').pack(fill='both',
expand=True)
v_thresh_sb = tk.Spinbox(v_thresh_frame,
textvariable=self.settings['v_thresh'],
from_=-1e3, to_=1e3, increment=1e-3, width=10)
v_thresh_sb.pack(fill='y', expand=True, ipady=3)
tip = "Threshold in mV defining the voltage at which a spike is fired."
ToolTip(v_thresh_frame, text=tip, wraplength=750)
# Refractory time constant
tau_refrac_frame = tk.Frame(self.cellparams_frame, bg='white')
tau_refrac_frame.pack(**self.kwargs)
tk.Label(tau_refrac_frame, text="tau_refrac",
bg='white').pack(fill='both', expand=True)
tau_refrac_sb = tk.Spinbox(tau_refrac_frame,
textvariable=self.settings['tau_refrac'],
width=10, from_=0, to_=1e3, increment=0.01)
tau_refrac_sb.pack(fill='y', expand=True, ipady=3)
tip = dedent("""\
Duration of refractory period in milliseconds of the neurons
after spiking.""")
ToolTip(tau_refrac_frame, text=tip, wraplength=750)
# Reset
v_reset_frame = tk.Frame(self.cellparams_frame, bg='white')
v_reset_frame.pack(**self.kwargs)
self.v_reset_label = tk.Label(v_reset_frame, text="v_reset",
state=self.settings['state_pyNN'].get(),
bg='white')
self.v_reset_label.pack(fill='both', expand=True)
self.v_reset_sb = tk.Spinbox(
v_reset_frame, disabledbackground='#eee', width=10,
textvariable=self.settings['v_reset'], from_=-1e3, to_=1e3,
increment=0.1, state=self.settings['state_pyNN'].get())
self.v_reset_sb.pack(fill='y', expand=True, ipady=3)
tip = "Reset potential in mV of the neurons after spiking."
ToolTip(v_reset_frame, text=tip, wraplength=750)
# Resting potential
v_rest_frame = tk.Frame(self.cellparams_frame, bg='white')
v_rest_frame.pack(**self.kwargs)
self.v_rest_label = tk.Label(v_rest_frame, text="v_rest", bg='white',
state=self.settings['state_pyNN'].get())
self.v_rest_label.pack(fill='both', expand=True)
self.v_rest_sb = tk.Spinbox(
v_rest_frame, disabledbackground='#eee', width=10,
textvariable=self.settings['v_rest'], from_=-1e3, to_=1e3,
increment=0.1, state=self.settings['state_pyNN'].get())
self.v_rest_sb.pack(fill='y', expand=True, ipady=3)
tip = dedent("""\
Resting membrane potential in mV.
Only relevant in pyNN-simulators.""")
ToolTip(v_rest_frame, text=tip, wraplength=750)
# e_rev_E
e_rev_exc_frame = tk.Frame(self.cellparams_frame, bg='white')
e_rev_exc_frame.pack(**self.kwargs)
self.e_rev_E_label = tk.Label(e_rev_exc_frame, text="e_rev_E",
state=self.settings['state_pyNN'].get(),
bg='white')
self.e_rev_E_label.pack(fill='both', expand=True)
self.e_rev_E_sb = tk.Spinbox(
e_rev_exc_frame, disabledbackground='#eee', width=10,
textvariable=self.settings['e_rev_E'], from_=-1e-3, to_=1e3,
increment=0.1, state=self.settings['state_pyNN'].get())
self.e_rev_E_sb.pack(fill='y', expand=True, ipady=3)
tip = dedent("""\
Reversal potential for excitatory input in mV.
Only relevant in pyNN-simulators.""")
ToolTip(e_rev_exc_frame, text=tip, wraplength=750)
# e_rev_I
e_rev_inh_frame = tk.Frame(self.cellparams_frame, bg='white')
e_rev_inh_frame.pack(**self.kwargs)
self.e_rev_I_label = tk.Label(e_rev_inh_frame, text="e_rev_I",
state=self.settings['state_pyNN'].get(),
bg='white')
self.e_rev_I_label.pack(fill='both', expand=True)
self.e_rev_I_sb = tk.Spinbox(
e_rev_inh_frame, disabledbackground='#eee', width=10,
textvariable=self.settings['e_rev_I'], from_=-1e3, to_=1e3,
increment=0.1, state=self.settings['state_pyNN'].get())
self.e_rev_I_sb.pack(fill='y', expand=True, ipady=3)
tip = dedent("""\
Reversal potential for inhibitory input in mV.
Only relevant in pyNN-simulators.""")
ToolTip(e_rev_inh_frame, text=tip, wraplength=750)
# i_offset
i_offset_frame = tk.Frame(self.cellparams_frame, bg='white')
i_offset_frame.pack(**self.kwargs)
self.i_offset_label = tk.Label(
i_offset_frame, text="i_offset", bg='white',
state=self.settings['state_pyNN'].get())
self.i_offset_label.pack(fill='both', expand=True)
self.i_offset_sb = tk.Spinbox(i_offset_frame, width=10,
textvariable=self.settings['i_offset'],
from_=-1e3, to_=1e3, increment=1,
state=self.settings['state_pyNN'].get(),
disabledbackground='#eee')
self.i_offset_sb.pack(fill='y', expand=True, ipady=3)
tip = dedent("""\
Offset current in nA.
Only relevant in pyNN-simulators.""")
ToolTip(i_offset_frame, text=tip, wraplength=750)
# Membrane capacitance
cm_frame = tk.Frame(self.cellparams_frame, bg='white')
cm_frame.pack(**self.kwargs)
self.cm_label = tk.Label(cm_frame, text="C_mem", bg='white',
state=self.settings['state_pyNN'].get(), )
self.cm_label.pack(fill='both', expand=True)
self.cm_sb = tk.Spinbox(cm_frame, textvariable=self.settings['cm'],
from_=1e-3, to_=1e3, increment=1e-3, width=10,
state=self.settings['state_pyNN'].get(),
disabledbackground='#eee')
self.cm_sb.pack(fill='y', expand=True, ipady=3)
tip = dedent("""\
Membrane capacitance in nF.
Only relevant in pyNN-simulators.""")
ToolTip(cm_frame, text=tip, wraplength=750)
# tau_m
tau_m_frame = tk.Frame(self.cellparams_frame, bg='white')
tau_m_frame.pack(**self.kwargs)
self.tau_m_label = tk.Label(tau_m_frame, text="tau_m", bg='white',
state=self.settings['state_pyNN'].get())
self.tau_m_label.pack(fill='both', expand=True)
self.tau_m_sb = tk.Spinbox(tau_m_frame, disabledbackground='#eee',
textvariable=self.settings['tau_m'],
from_=1, to_=1e6, increment=1, width=10,
state=self.settings['state_pyNN'].get())
self.tau_m_sb.pack(fill='y', expand=True, ipady=3)
tip = dedent("""\
Membrane time constant in milliseconds.
Only relevant in pyNN-simulators.""")
ToolTip(tau_m_frame, text=tip, wraplength=750)
# tau_syn_E
tau_syn_exc_frame = tk.Frame(self.cellparams_frame, bg='white')
tau_syn_exc_frame.pack(**self.kwargs)
self.tau_syn_E_label = tk.Label(
tau_syn_exc_frame, text="tau_syn_E", bg='white',
state=self.settings['state_pyNN'].get())
self.tau_syn_E_label.pack(fill='both', expand=True)
self.tau_syn_E_sb = tk.Spinbox(tau_syn_exc_frame, width=10,
textvariable=self.settings['tau_syn_E'],
from_=1e-3, to_=1e3, increment=1e-3,
state=self.settings['state_pyNN'].get(),
disabledbackground='#eee')
self.tau_syn_E_sb.pack(fill='y', expand=True, ipady=3)
tip = dedent("""\
Decay time of the excitatory synaptic conductance in
milliseconds.
Only relevant in pyNN-simulators.""")
ToolTip(tau_syn_exc_frame, text=tip, wraplength=750)
# tau_syn_I
tau_syn_inh_frame = tk.Frame(self.cellparams_frame, bg='white')
tau_syn_inh_frame.pack(**self.kwargs)
self.tau_syn_I_label = tk.Label(
tau_syn_inh_frame, text="tau_syn_I", bg='white',
state=self.settings['state_pyNN'].get())
self.tau_syn_I_label.pack(fill='both', expand=True)
self.tau_syn_I_sb = tk.Spinbox(tau_syn_inh_frame, width=10,
textvariable=self.settings['tau_syn_I'],
from_=1e-3, to_=1e3, increment=1e-3,
state=self.settings['state_pyNN'].get(),
disabledbackground='#eee')
self.tau_syn_I_sb.pack(fill='y', expand=True, ipady=3)
tip = dedent("""\
Decay time of the inhibitory synaptic conductance in
milliseconds.
Only relevant in pyNN-simulators.""")
ToolTip(tau_syn_inh_frame, text=tip, wraplength=750)
[docs] def simparams_widgets(self):
"""Create a container for individual parameter widgets."""
self.simparams_frame = tk.LabelFrame(self.main_container,
labelanchor='nw',
text="Simulation\n parameters",
relief='raised',
borderwidth='3', bg='white')
self.simparams_frame.pack(side='left', fill=None, expand=False)
tip = dedent("""\
Specify parameters concerning the simulation of the converted
spiking network. Some are simulator specific.""")
ToolTip(self.simparams_frame, text=tip, wraplength=750, delay=1499)
# Simulator
simulators = eval(self.config.get('restrictions', 'simulators'))
simulator_frame = tk.Frame(self.simparams_frame, bg='white')
simulator_frame.pack(**self.kwargs)
tip = dedent("""\
Choose a simulator to run the converted spiking network with.""")
ToolTip(simulator_frame, text=tip, wraplength=750)
tk.Label(simulator_frame, text="Simulator", bg='white').pack(
fill='both', expand=True)
simulator_om = tk.OptionMenu(simulator_frame,
self.settings['simulator'],
*list(simulators),
command=self.toggle_state_pynn)
simulator_om.pack(fill='both', expand=True)
# Time resolution
dt_frame = tk.Frame(self.simparams_frame, bg='white')
dt_frame.pack(**self.kwargs)
tk.Label(dt_frame, text="dt", bg='white').pack(fill='x', expand=True)
dt_sb = tk.Spinbox(dt_frame, textvariable=self.settings['dt'],
from_=1e-3, to_=1e3, increment=1e-3, width=10)
dt_sb.pack(fill='y', expand=True, ipady=3)
tip = "Time resolution of spikes in milliseconds."
ToolTip(dt_frame, text=tip, wraplength=750)
# Duration
duration_frame = tk.Frame(self.simparams_frame, bg='white')
duration_frame.pack(**self.kwargs)
tk.Label(duration_frame, text="duration", bg='white').pack(fill='y',
expand=True)
duration_sb = tk.Spinbox(duration_frame, width=10, increment=1,
from_=self.settings['dt'].get(), to_=1e9,
textvariable=self.settings['duration'])
duration_sb.pack(fill='y', expand=True, ipady=3)
tip = "Runtime of simulation of one input in milliseconds."
ToolTip(duration_frame, text=tip, wraplength=750)
# Poisson input
poisson_input_cb = tk.Checkbutton(
self.simparams_frame, text="Poisson input", bg='white',
variable=self.settings['poisson_input'], height=2, width=20,
command=self.toggle_poisson_input_state)
poisson_input_cb.pack(**self.kwargs)
tip = dedent("""\
If enabled, the input samples will be converted to Poisson
spiketrains. The probability for a input neuron to fire is
proportional to the analog value of the corresponding pixel, and
limited by the parameter 'input_rate' below. For instance,
with an 'input_rate' of 700, a fully-on pixel will elicit a
Poisson spiketrain of 700 Hz. Turn off for a less noisy
simulation. Currently, turning off Poisson input is only possible
in INI simulator.""")
ToolTip(poisson_input_cb, text=tip, wraplength=750)
# Maximum input firing rate
input_rate_frame = tk.Frame(self.simparams_frame, bg='white')
input_rate_frame.pack(**self.kwargs)
self.input_rate_label = tk.Label(input_rate_frame, text="input_rate",
bg='white')
self.input_rate_label.pack(fill='both', expand=True)
self.input_rate_sb = tk.Spinbox(
input_rate_frame, textvariable=self.settings['input_rate'],
from_=1, to_=10000, increment=1, width=10,
disabledbackground='#eee')
self.input_rate_sb.pack(fill='y', expand=True, ipady=3)
tip = dedent("""\
Poisson spike rate in Hz for a fully-on pixel of input image. Only
relevant when 'Poisson input' checkbutton enabled. Note that the
input_rate is limited by the maximum firing rate supported by the
simulator (given by the inverse time resolution 1000 * 1 / dt Hz).
""")
ToolTip(input_rate_frame, text=tip, wraplength=750)
# Reset mechanism
reset_frame = tk.Frame(self.simparams_frame, bg='white')
reset_frame.pack(**self.kwargs)
tk.Label(reset_frame, text="Reset mechanism", bg='white').pack(
fill='both', expand=True)
tk.Radiobutton(reset_frame, variable=self.settings['reset'],
text='Reset to zero', value='Reset to zero',
bg='white').pack(fill='both', side='top', expand=True)
tk.Radiobutton(reset_frame, variable=self.settings['reset'],
text='Reset by subtraction',
value='Reset by subtraction', bg='white').pack(
fill='both', side='bottom', expand=True)
tip = dedent("""\
Reset to zero:
After spike, the membrane potential is set to the resting
potential.
Reset by subtraction:
After spike, the membrane potential is reduced by a value
equal to the threshold.""")
ToolTip(reset_frame, text=tip, wraplength=750)
# Binarize weights
binarize_weights_cb = tk.Checkbutton(
self.simparams_frame, text="Binarize weights", bg='white',
variable=self.settings['binarize_weights'], height=2, width=20)
binarize_weights_cb.pack(**self.kwargs)
tip = dedent("""\
If enabled, the weights are binarized.""")
ToolTip(binarize_weights_cb, text=tip, wraplength=750)
# MaxPool
maxpool_types = eval(self.config.get('restrictions', 'maxpool_types'))
maxpool_frame = tk.Frame(self.simparams_frame, bg='white')
maxpool_frame.pack(**self.kwargs)
tip = dedent("""\
Implementation variants of spiking MaxPooling layers.
fir_max: accumulated absolute firing rate
exp_max: exponentially decaying average of firing rate
avg_max: moving average of firing rate
binary_tanh: Sign function, used in BinaryNet.
binary_sigmoid: Step function, used in BinaryNet.""")
ToolTip(maxpool_frame, text=tip, wraplength=750)
tk.Label(maxpool_frame, text="MaxPool type", bg='white').pack(
fill='both', expand=True)
maxpool_om = tk.OptionMenu(
maxpool_frame, self.settings['maxpool_type'], *list(maxpool_types))
maxpool_om.pack(fill='both', expand=True)
# Delay
delay_frame = tk.Frame(self.simparams_frame, bg='white')
delay_frame.pack(**self.kwargs)
self.delay_label = tk.Label(delay_frame, text="delay", bg='white',
state=self.settings['state_pyNN'].get())
self.delay_label.pack(fill='both', expand=True)
self.delay_sb = tk.Spinbox(delay_frame, disabledbackground='#eee',
textvariable=self.settings['delay'],
from_=self.settings['dt'].get(), to_=1000,
increment=1, width=10,
state=self.settings['state_pyNN'].get())
self.delay_sb.pack(fill='y', expand=True, ipady=3)
tip = dedent("""\
Delay in milliseconds. Must be equal to or greater than the
resolution. Only relevant in pyNN-simulators.""")
ToolTip(delay_frame, text=tip, wraplength=750)
# Number of samples to test
num_to_test_frame = tk.Frame(self.simparams_frame, bg='white')
num_to_test_frame.pack(**self.kwargs)
self.num_to_test_label = tk.Label(
num_to_test_frame, bg='white', text="num_to_test",
state=self.settings['state_num_to_test'].get())
self.num_to_test_label.pack(fill='both', expand=True)
self.num_to_test_sb = tk.Spinbox(
num_to_test_frame, state=self.settings['state_num_to_test'].get(),
textvariable=self.settings['num_to_test'], from_=1, to_=1e9,
increment=1, width=10, disabledbackground='#eee')
self.num_to_test_sb.pack(fill='y', expand=True, ipady=3)
tip = dedent("""\
Number of samples to test. Only relevant in pyNN-simulators.""")
ToolTip(num_to_test_frame, text=tip, wraplength=750)
# Test specific samples
sample_frame = tk.Frame(self.simparams_frame, bg='white')
sample_frame.pack(**self.kwargs)
tk.Label(sample_frame, text="Samples to test:", bg='white').pack(
fill='both', expand=True)
self.sample_entry = tk.Entry(
sample_frame, bg='white', width=20, validate='key',
textvariable=self.settings['sample_idxs_to_test'],
validatecommand=(sample_frame.register(self.check_sample), '%P'))
self.sample_entry.pack(fill='both', expand=True, side='bottom')
tip = dedent(
"""List the indices of specific samples you want to test.""")
ToolTip(sample_frame, text=tip, wraplength=750)
# Name of directory where to save plots
runlabel_frame = tk.Frame(self.simparams_frame, bg='white')
runlabel_frame.pack(**self.kwargs)
tk.Label(runlabel_frame, text='run label', bg='white').pack(
fill='both', expand=True)
runlabel_entry = tk.Entry(
runlabel_frame, bg='white', textvariable=self.settings['runlabel'],
validate='focusout', validatecommand=(
runlabel_frame.register(self.check_runlabel), '%P'))
runlabel_entry.pack(fill='both', expand=True, side='bottom')
tip = dedent("""\
Give your simulation run a name. If verbosity is high, the
resulting plots will be saved in <cwd>/log/gui/<runlabel>.""")
ToolTip(runlabel_frame, text=tip, wraplength=750)
[docs] def tools_widgets(self):
"""Create tools widgets."""
self.tools_frame = tk.LabelFrame(self.main_container, labelanchor='nw',
text='Tools', relief='raised',
borderwidth='3', bg='white')
self.tools_frame.pack(side='left', fill=None, expand=False)
tip = dedent("""\
Specify the tools to apply in your experiment.""")
ToolTip(self.tools_frame, text=tip, wraplength=750, delay=1499)
# Evaluate ANN
self.evaluate_ann_cb = tk.Checkbutton(
self.tools_frame, text="Evaluate ANN", bg='white',
variable=self.settings['evaluate_ann'], height=2, width=20)
self.evaluate_ann_cb.pack(**self.kwargs)
tip = dedent("""\
If enabled, test the input model before and after it is parsed, to
ensure we do not lose performance. (Parsing extracts all necessary
information from the input model and creates a new network with
some simplifications in preparation for conversion to SNN.)
If you also enabled 'normalization' (see parameter 'normalize'
below), then the network will be evaluated again after
normalization. This operation should preserve accuracy as well.""")
ToolTip(self.evaluate_ann_cb, text=tip, wraplength=750)
# Normalize
self.normalize_cb = tk.Checkbutton(
self.tools_frame, text="Normalize", height=2, width=20,
bg='white', variable=self.settings['normalize'])
self.normalize_cb.pack(**self.kwargs)
tip = dedent("""\
Only relevant when converting a network, not during simulation.
If enabled, the parameters of the spiking network will be
normalized by the highest activation value, or by the ``n``-th
percentile (see parameter ``percentile`` below).""")
ToolTip(self.normalize_cb, text=tip, wraplength=750)
# Convert ANN
convert_cb = tk.Checkbutton(self.tools_frame, text="Convert",
variable=self.settings['convert'],
height=2, width=20, bg='white')
convert_cb.pack(**self.kwargs)
tip = dedent("""\
If enabled, load an ANN from working directory (see setting
'working dir') and convert it to spiking.""")
ToolTip(convert_cb, text=tip, wraplength=750)
# Simulate
simulate_cb = tk.Checkbutton(self.tools_frame, text="Simulate",
variable=self.settings['simulate'],
height=2, width=20, bg='white')
simulate_cb.pack(**self.kwargs)
tip = dedent("""\
If enabled, try to load SNN from working directory (see setting
'working dir') and test it on the specified simulator (see
parameter 'simulator').""")
ToolTip(simulate_cb, text=tip, wraplength=750)
# Overwrite
overwrite_cb = tk.Checkbutton(self.tools_frame, text="Overwrite",
variable=self.settings['overwrite'],
height=2, width=20, bg='white')
overwrite_cb.pack(**self.kwargs)
tip = dedent("""\
If disabled, the save methods will ask for permission to
overwrite files before writing parameters, activations, models
etc. to disk.""")
ToolTip(overwrite_cb, text=tip, wraplength=750)
# Start experiment
self.start_processing_bt = tk.Button(
self.tools_frame, text="Start", font=self.header_font,
foreground='#008000', command=self.start_processing,
state=self.start_state.get())
self.start_processing_bt.pack(**self.kwargs)
tip = dedent("""\
Start processing the steps specified above. Settings can not be
changed during the run.""")
ToolTip(self.start_processing_bt, text=tip, wraplength=750)
# Stop experiment
self.stop_processing_bt = tk.Button(
self.tools_frame, text="Stop", font=self.header_font,
foreground='red', command=self.stop_processing)
self.stop_processing_bt.pack(**self.kwargs)
tip = dedent("""\
Stop the process at the next opportunity. This will usually be
between steps of normalization, evaluation, conversion and
simulation.""")
ToolTip(self.stop_processing_bt, text=tip, wraplength=750)
[docs] def edit_normalization_settings(self):
"""Settings menu for parameter normalization"""
self.normalization_settings_container = tk.Toplevel(bg='white')
self.normalization_settings_container.geometry('300x400')
self.normalization_settings_container.wm_title(
'Normalization settings')
self.normalization_settings_container.protocol(
'WM_DELETE_WINDOW', self.normalization_settings_container.destroy)
tk.Button(self.normalization_settings_container, text='Save and close',
command=self.normalization_settings_container.destroy).pack()
# Percentile
percentile_frame = tk.Frame(self.normalization_settings_container,
bg='white')
percentile_frame.pack(**self.kwargs)
self.percentile_label = tk.Label(percentile_frame, text="Percentile",
bg='white')
self.percentile_label.pack(fill='both', expand=True)
self.percentile_sb = tk.Spinbox(
percentile_frame, bg='white', from_=0, to_=100, increment=0.001,
textvariable=self.settings['percentile'], width=10,
disabledbackground='#eee')
self.percentile_sb.pack(fill='y', expand=True, ipady=5)
tip = dedent("""\
Use the activation value in the specified percentile for
normalization. Set to '50' for the median, '100' for the max.
Typical values are 99, 99.9, 100.""")
ToolTip(percentile_frame, text=tip, wraplength=700)
# Normalization schedule
normalization_schedule_cb = tk.Checkbutton(
self.normalization_settings_container,
text="Normalization schedule",
variable=self.settings['normalization_schedule'],
height=2, width=20, bg='white')
normalization_schedule_cb.pack(**self.kwargs)
tip = dedent("""\
Reduce the normalization factor each layer.""")
ToolTip(normalization_schedule_cb, text=tip, wraplength=750)
# Online normalization
online_normalization_cb = tk.Checkbutton(
self.normalization_settings_container, text="Online normalization",
variable=self.settings['online_normalization'],
height=2, width=20, bg='white')
online_normalization_cb.pack(**self.kwargs)
tip = dedent("""\
The converted spiking network performs best if the average firing
rates of each layer are not higher but also not much lower than the
maximum rate supported by the simulator (inverse time resolution).
Normalization eliminates saturation but introduces undersampling
(parameters are normalized with respect to the highest value in a
batch). To overcome this, the spikerates of each layer are
monitored during simulation. If they drop below the maximum firing
rate by more than 'diff to max rate', we set the threshold of
the layer to its highest rate.""")
ToolTip(online_normalization_cb, text=tip, wraplength=750)
# Difference to maximum firing rate
diff_to_max_rate_frame = tk.Frame(
self.normalization_settings_container, bg='white')
diff_to_max_rate_frame.pack(**self.kwargs)
self.diff_to_max_rate_label = tk.Label(
diff_to_max_rate_frame, bg='white', text="diff_to_max_rate")
self.diff_to_max_rate_label.pack(fill='both', expand=True)
self.diff_to_max_rate_sb = tk.Spinbox(
diff_to_max_rate_frame, from_=0, to_=10000, increment=1, width=10,
textvariable=self.settings['diff_to_max_rate'])
self.diff_to_max_rate_sb.pack(fill='y', expand=True, ipady=3)
tip = dedent("""\
If the highest firing rate of neurons in a layer drops below the
maximum firing rate by more than 'diff to max rate', we set the
threshold of the layer to its highest rate.
Set the parameter in Hz.""")
ToolTip(diff_to_max_rate_frame, text=tip, wraplength=750)
# Minimum firing rate
diff_to_min_rate_frame = tk.Frame(
self.normalization_settings_container, bg='white')
diff_to_min_rate_frame.pack(**self.kwargs)
self.diff_to_min_rate_label = tk.Label(
diff_to_min_rate_frame, bg='white', text="diff_to_min_rate")
self.diff_to_min_rate_label.pack(fill='both', expand=True)
self.diff_to_min_rate_sb = tk.Spinbox(
diff_to_min_rate_frame, from_=0, to_=10000, increment=1, width=10,
textvariable=self.settings['diff_to_min_rate'])
self.diff_to_min_rate_sb.pack(fill='y', expand=True, ipady=3)
tip = dedent("""\
When The firing rates of a layer are below this value, the weights
will NOT be modified in the feedback mechanism described in
'online_normalization'. This is useful in the beginning of a
simulation, when higher layers need some time to integrate up a
sufficiently high membrane potential.""")
ToolTip(diff_to_min_rate_frame, text=tip, wraplength=750)
# Time-step fraction
timestep_fraction_frame = tk.Frame(
self.normalization_settings_container, bg='white')
timestep_fraction_frame.pack(**self.kwargs)
self.timestep_fraction_label = tk.Label(
timestep_fraction_frame, bg='white', text="timestep_fraction")
self.timestep_fraction_label.pack(fill='both', expand=True)
self.timestep_fraction_sb = tk.Spinbox(
timestep_fraction_frame, from_=0, to_=1000, increment=1, width=10,
textvariable=self.settings['timestep_fraction'])
self.timestep_fraction_sb.pack(fill='y', expand=True, ipady=3)
tip = dedent("""\
If set to 10 (default), the parameter modification mechanism
described in 'online_normalization' will be performed at every 10th
timestep.""")
ToolTip(timestep_fraction_frame, text=tip, wraplength=750)
[docs] def edit_experimental_settings(self):
"""Settings menu for experimental features."""
self.experimental_settings_container = tk.Toplevel(bg='white')
self.experimental_settings_container.geometry('300x400')
self.experimental_settings_container.wm_title('Experimental settings')
self.experimental_settings_container.protocol(
'WM_DELETE_WINDOW', self.experimental_settings_container.destroy)
tk.Button(self.experimental_settings_container, text='Save and close',
command=self.experimental_settings_container.destroy).pack()
experimental_settings_cb = tk.Checkbutton(
self.experimental_settings_container,
text="Enable experimental settings",
variable=self.settings['experimental_settings'],
height=2, width=20, bg='white')
experimental_settings_cb.pack(expand=True)
tip = dedent("""Enable experimental settings.""")
ToolTip(experimental_settings_cb, text=tip, wraplength=750)
[docs] def edit_dataset_settings(self):
"""Settings menu for dataset parameters."""
dataset_settings_container = tk.Toplevel(bg='white')
dataset_settings_container.wm_title('Dataset settings')
dataset_settings_container.protocol(
'WM_DELETE_WINDOW', dataset_settings_container.destroy)
tk.Button(dataset_settings_container, text='Save and close',
command=dataset_settings_container.destroy).pack()
datagen_frame = tk.Frame(dataset_settings_container, bg='white')
datagen_frame.pack(**self.kwargs)
tk.Label(datagen_frame, text="Datagen kwargs:", bg='white').pack(
fill='both', expand=True)
datagen_settings_entry = tk.Entry(
datagen_frame, bg='white', width=20,
textvariable=self.settings['datagen_kwargs'])
datagen_settings_entry.pack(expand=True, fill='x')
tip = dedent("""\
Specify keyword arguments for the data generator that will be used
to load image files from subdirectories in the 'dataset_path'.
Need to be given in form of a python dictionary. See Keras
'ImageDataGenerator' for possible values.""")
ToolTip(datagen_frame, text=tip, wraplength=750)
dataflow_frame = tk.Frame(dataset_settings_container, bg='white')
dataflow_frame.pack(**self.kwargs)
tk.Label(dataflow_frame, text="Dataflow kwargs:", bg='white').pack(
fill='both', expand=True)
dataflow_settings_entry = tk.Entry(
dataflow_frame, bg='white', width=20,
textvariable=self.settings['dataflow_kwargs'])
dataflow_settings_entry.pack(expand=True, fill='x')
tip = dedent("""\
Specify keyword arguments for the data flow that will get the
samples from the ImageDataGenerator.
Need to be given in form of a python dictionary. See
keras.preprocessing.image.ImageDataGenerator.flow_from_directory
for possible values. Note that the 'directory' argument needs not
be given because it is set to 'dataset_path' automatically.""")
ToolTip(dataflow_frame, text=tip, wraplength=750)
[docs] def graph_widgets(self):
"""Create graph widgets."""
# Create a container for buttons that display plots for individual
# layers.
if hasattr(self, 'graph_frame'):
self.graph_frame.pack_forget()
self.graph_frame.destroy()
self.graph_frame = tk.Frame(self.main_container, background='white')
self.graph_frame.pack(side='left', fill=None, expand=False)
tip = dedent("""\
Select a layer to display plots like Spiketrains, Spikerates,
Membrane Potential, Correlations, etc.""")
ToolTip(self.graph_frame, text=tip, wraplength=750)
self.select_plots_dir_rb()
if hasattr(self, 'selected_plots_dir'):
self.select_layer_rb()
[docs] def select_plots_dir_rb(self):
"""Select plots directory."""
self.plot_dir_frame = tk.LabelFrame(self.graph_frame, labelanchor='nw',
text="Select dir", relief='raised',
borderwidth='3', bg='white')
self.plot_dir_frame.pack(side='top', fill=None, expand=False)
self.gui_log.set(os.path.join(self.settings['path_wd'].get(),
'log', 'gui'))
if os.path.isdir(self.gui_log.get()):
plot_dirs = [d for d in sorted(os.listdir(self.gui_log.get()))
if os.path.isdir(os.path.join(self.gui_log.get(), d))]
self.selected_plots_dir = tk.StringVar(value=plot_dirs[0])
[tk.Radiobutton(self.plot_dir_frame, bg='white', text=name,
value=name, command=self.select_layer_rb,
variable=self.selected_plots_dir).pack(
fill='both', side='bottom', expand=True)
for name in plot_dirs]
open_new_cb = tk.Checkbutton(self.graph_frame, bg='white', height=2,
width=20, text='open in new window',
variable=self.settings['open_new'])
open_new_cb.pack(**self.kwargs)
tip = dedent("""\
If unchecked, the window showing graphs for a certain layer will
close and be replaced each time you select a layer to plot.
If checked, an additional window will pop up instead.""")
ToolTip(open_new_cb, text=tip, wraplength=750)
[docs] def select_layer_rb(self):
"""Select layer."""
if hasattr(self, 'layer_frame'):
self.layer_frame.pack_forget()
self.layer_frame.destroy()
self.layer_frame = tk.LabelFrame(self.graph_frame, labelanchor='nw',
text="Select layer", relief='raised',
borderwidth='3', bg='white')
self.layer_frame.pack(side='bottom', fill=None, expand=False)
self.plots_dir = os.path.join(self.gui_log.get(),
self.selected_plots_dir.get())
if os.path.isdir(self.plots_dir):
layer_dirs = [d for d in sorted(os.listdir(self.plots_dir))
if d != 'normalization' and
os.path.isdir(os.path.join(self.plots_dir, d))]
[tk.Radiobutton(self.layer_frame, bg='white', text=name,
value=name, command=self.display_graphs,
variable=self.layer_to_plot).pack(
fill='both', side='bottom', expand=True)
for name in layer_dirs]
[docs] def draw_canvas(self):
"""Draw canvas figure."""
# Create figure with subplots, a canvas to hold them, and add
# matplotlib navigation toolbar.
if self.layer_to_plot.get() is '':
return
if hasattr(self, 'plot_container') \
and not self.settings['open_new'].get() \
and not self.is_plot_container_destroyed:
self.plot_container.wm_withdraw()
self.plot_container = tk.Toplevel(bg='white')
self.plot_container.geometry('1920x1080')
self.is_plot_container_destroyed = False
self.plot_container.wm_title('Results from simulation run {}'.format(
self.selected_plots_dir.get()))
self.plot_container.protocol('WM_DELETE_WINDOW', self.close_window)
tk.Button(self.plot_container, text='Close Window',
command=self.close_window).pack()
f = plt.figure(figsize=(30, 15))
f.subplots_adjust(left=0.01, bottom=0.05, right=0.99, top=0.99,
wspace=0.01, hspace=0.01)
num_rows = 3
num_cols = 5
gs = gridspec.GridSpec(num_rows, num_cols)
self.a = [plt.subplot(gs[i, 0:-2]) for i in range(3)]
self.a += [plt.subplot(gs[i, -2]) for i in range(3)]
self.a += [plt.subplot(gs[i, -1]) for i in range(3)]
self.canvas = FigureCanvasTkAgg(f, self.plot_container)
graph_widget = self.canvas.get_tk_widget()
graph_widget.pack(side='top', fill='both', expand=True)
self.toolbar = NavigationToolbar2TkAgg(self.canvas, graph_widget)
[docs] def close_window(self):
"""Close window function."""
plt.close()
self.plot_container.destroy()
self.is_plot_container_destroyed = True
[docs] def display_graphs(self):
"""Display graphs."""
self.draw_canvas()
if self.layer_to_plot.get() is '':
msg = ("Failed to load images. Please select a layer to plot, and "
"make sure your working directory contains appropriate "
"image files.")
messagebox.showerror(title="Loading Error", message=msg)
return
path_to_plots = os.path.join(self.plots_dir, self.layer_to_plot.get())
if not os.path.isdir(path_to_plots):
msg = ("Failed to load images. Please set a working directory "
"that contains appropriate image files.")
messagebox.showerror(title="Loading Error", message=msg)
return
saved_plots = sorted(os.listdir(path_to_plots))
[a.clear() for a in self.a]
for name in saved_plots:
i = int(name[:1])
self.a[i].imshow(mpimg.imread(os.path.join(path_to_plots, name)))
layer_idx = int(self.layer_to_plot.get()[:2])
plots_dir_norm = os.path.join(self.plots_dir, 'normalization')
if os.path.exists(plots_dir_norm):
normalization_plots = sorted(os.listdir(plots_dir_norm))
else:
normalization_plots = []
activation_distr = None
weight_distr = None
for i in range(len(normalization_plots)):
if int(normalization_plots[i][:2]) == layer_idx:
activation_distr = normalization_plots[i]
weight_distr = normalization_plots[i + 1]
break
if activation_distr and weight_distr:
self.a[3].imshow(mpimg.imread(os.path.join(self.plots_dir,
'normalization',
activation_distr)))
self.a[6].imshow(mpimg.imread(os.path.join(self.plots_dir,
'normalization',
weight_distr)))
self.a[-1].imshow(mpimg.imread(os.path.join(self.plots_dir,
'Pearson.png')))
for a in self.a:
a.get_xaxis().set_visible(False)
a.get_yaxis().set_visible(False)
self.canvas.draw()
self.toolbar.update()
# noinspection PyProtectedMember
self.canvas._tkcanvas.pack(side='left', fill='both', expand=True)
[docs] @staticmethod
def documentation():
"""Open documentation."""
webbrowser.open(os.path.join(sys.exec_prefix, 'docs',
'Documentation.html'))
[docs] @staticmethod
def about():
"""About message."""
msg = ("This is a collection of tools to convert analog neural "
"networks to fast and high-performing spiking nets.\n\n"
"Developed at the Institute of Neuroinformatics, \n"
"University / ETH Zurich.\n\n"
"Contact: Bodo Rueckauer \n"
"bodo.rueckauer@gmail.com \n\n"
"Version: {} \n\n".format('0.1dev') +
"2016")
messagebox.showinfo(title="About SNN Toolbox", message=msg)
[docs] def quit_toolbox(self):
"""Quit toolbox function."""
self.store_last_settings = True
self.save_settings()
self.root.destroy()
self.root.quit()
[docs] def declare_parameter_vars(self):
"""Preferenece collection."""
# These will be written to disk as preferences.
self.settings = {'dataset_path': tk.StringVar(),
'dataset_format': tk.StringVar(),
'datagen_kwargs': tk.StringVar(),
'dataflow_kwargs': tk.StringVar(),
'model_lib': tk.StringVar(),
'path_wd': tk.StringVar(value=self.toolbox_root),
'filename_parsed_model': tk.StringVar(),
'filename_ann': tk.StringVar(),
'filename_snn': tk.StringVar(),
'batch_size': tk.IntVar(),
'sample_idxs_to_test': tk.StringVar(),
'evaluate_ann': tk.BooleanVar(),
'normalize': tk.BooleanVar(),
'percentile': tk.DoubleVar(),
'convert': tk.BooleanVar(),
'simulate': tk.BooleanVar(),
'overwrite': tk.BooleanVar(),
'verbose': tk.IntVar(),
'v_thresh': tk.DoubleVar(),
'tau_refrac': tk.DoubleVar(),
'v_reset': tk.DoubleVar(),
'v_rest': tk.DoubleVar(),
'e_rev_E': tk.DoubleVar(),
'e_rev_I': tk.DoubleVar(),
'i_offset': tk.IntVar(),
'cm': tk.DoubleVar(),
'tau_m': tk.IntVar(),
'tau_syn_E': tk.DoubleVar(),
'tau_syn_I': tk.DoubleVar(),
'dt': tk.DoubleVar(),
'simulator': tk.StringVar(),
'duration': tk.IntVar(),
'poisson_input': tk.BooleanVar(),
'num_poisson_events_per_sample': tk.IntVar(),
'num_dvs_events_per_sample': tk.IntVar(),
'eventframe_width': tk.IntVar(),
'label_dict': tk.Variable(),
'chip_size': tk.Variable(),
'target_size': tk.Variable(),
'reset': tk.StringVar(),
'input_rate': tk.IntVar(),
'diff_to_max_rate': tk.IntVar(),
'timestep_fraction': tk.IntVar(),
'diff_to_min_rate': tk.IntVar(),
'delay': tk.IntVar(),
'num_to_test': tk.IntVar(),
'runlabel': tk.StringVar(),
'open_new': tk.BooleanVar(value=True),
'log_dir_of_current_run': tk.StringVar(),
'state_pyNN': tk.StringVar(value='normal'),
'state_num_to_test': tk.StringVar(value='normal'),
'experimental_settings': tk.BooleanVar(),
'online_normalization': tk.BooleanVar(),
'normalization_schedule': tk.BooleanVar(),
'scaling_factor': tk.IntVar(),
'maxpool_type': tk.StringVar(),
'max2avg_pool': tk.BooleanVar(),
'payloads': tk.BooleanVar(),
'binarize_weights': tk.BooleanVar(),
'custom_activation': tk.StringVar(),
'softmax_to_relu': tk.BooleanVar(),
'reset_between_nth_sample': tk.IntVar(),
'filename_clamp_indices': tk.StringVar(),
'log_vars': tk.Variable(),
'plot_vars': tk.Variable()}
# These will not be written to disk as preferences.
self.is_plot_container_destroyed = True
self.store_last_settings = False
self.restore_last_pref = True
self.layer_rb_set = False
self.layer_rbs = []
self.layer_to_plot = tk.StringVar()
self.start_state = tk.StringVar(value='normal')
self.stop_state = tk.StringVar(value='normal')
self.percentile_state = tk.StringVar()
self.poisson_input_state = tk.StringVar()
self.console_output = tk.StringVar()
self.gui_log = tk.StringVar()
[docs] def restore_default_params(self):
"""Restore default parameters."""
self.set_preferences(self.config)
self.toggle_state_pynn(self.settings['simulator'].get())
[docs] def set_preferences(self, p):
"""Set preferences."""
[self.settings[key].set(p[key]) for key in p]
if self.settings['path_wd'] == '':
self.settings['path_wd'] = os.getcwd()
[docs] def save_settings(self):
"""Save current settings."""
s = {key: self.settings[key].get() for key in self.settings}
if self.store_last_settings:
if not os.path.exists(self.default_path_to_pref):
os.makedirs(self.default_path_to_pref)
with open(os.path.join(self.default_path_to_pref,
'_last_settings.json'), 'w') as f:
f.write(json.dumps(s))
self.store_last_settings = False
else:
path_to_pref = filedialog.asksaveasfilename(
defaultextension='.json', filetypes=[("json files", '*.json')],
initialdir=self.default_path_to_pref,
title="Choose filename")
with open(path_to_pref, 'w') as f:
f.write(json.dumps(s))
[docs] def load_settings(self, s=None):
"""Load a perferences settings."""
if s is None:
if self.restore_last_pref:
self.restore_last_pref = False
if not os.path.isdir(self.default_path_to_pref):
return
path_to_pref = os.path.join(self.default_path_to_pref,
'_last_settings.json')
if not os.path.isfile(path_to_pref):
return
else:
path_to_pref = filedialog.askopenfilename(
defaultextension='.json', filetypes=[("json files",
'*.json')],
initialdir=self.default_path_to_pref,
title="Choose filename")
s = json.load(open(path_to_pref))
self.set_preferences(s)
[docs] def start_processing(self):
"""Start processing."""
if self.settings['filename_ann'].get() == '':
messagebox.showwarning(title="Warning",
message="Please specify a filename base.")
return
if self.settings['dataset_path'].get() == '':
messagebox.showwarning(title="Warning",
message="Please set the dataset path.")
return
self.store_last_settings = True
self.save_settings()
self.check_runlabel(self.settings['runlabel'].get())
self.config.read_dict(self.settings)
self.initialize_thread()
self.process_thread.start()
self.toggle_start_state(True)
self.update()
[docs] def stop_processing(self):
"""Stop processing."""
if self.process_thread.is_alive():
self.res_queue.put('stop')
self.toggle_stop_state(True)
[docs] def update(self):
"""Update GUI with items from the queue."""
if self.process_thread.is_alive():
# Schedule next update
self.root.after(1000, self.update)
else:
# Stop loop of watching process_thread.
self.toggle_start_state(False)
self.toggle_stop_state(False)
[docs] def check_sample(self, p):
"""Check samples."""
if not self.initialized:
return True
elif p == '':
self.toggle_num_to_test_state(True)
return True
else:
samples = [int(i) for i in p.split() if i.isnumeric()]
self.settings['num_to_test'].set(len(samples))
self.toggle_num_to_test_state(False)
return True
[docs] def check_file(self, p):
"""Check files."""
if not os.path.exists(self.settings['path_wd'].get()) or \
not any(p in fname for fname in
os.listdir(self.settings['path_wd'].get())):
msg = ("Failed to set filename base:\n"
"Either working directory does not exist or contains no "
"files with base name \n '{}'".format(p))
messagebox.showwarning(title="Warning", message=msg)
return False
else:
return True
[docs] def check_path(self, p):
"""Check path."""
if not self.initialized:
result = True
elif not os.path.exists(p):
msg = "Failed to set working directory:\n" + \
"Specified directory does not exist."
messagebox.showwarning(title="Warning", message=msg)
result = False
elif self.settings['model_lib'].get() == 'caffe':
if not any(fname.endswith('.caffemodel') for fname in
os.listdir(p)):
msg = "No '*.caffemodel' file found in \n {}".format(p)
messagebox.showwarning(title="Warning", message=msg)
result = False
elif not any(fname.endswith('.prototxt') for fname in
os.listdir(p)):
msg = "No '*.prototxt' file found in \n {}".format(p)
messagebox.showwarning(title="Warning", message=msg)
result = False
else:
result = True
elif not any(fname.endswith('.json') for fname in os.listdir(p)):
msg = "No model file '*.json' found in \n {}".format(p)
messagebox.showwarning(title="Warning", message=msg)
result = False
else:
result = True
if result:
self.settings['path_wd'].set(p)
self.gui_log.set(os.path.join(p, 'log', 'gui'))
# Look for plots in working directory to display
self.graph_widgets()
return result
[docs] def check_runlabel(self, p):
"""Check runlabel."""
if self.initialized:
# Set path to plots for the current simulation run
self.settings['log_dir_of_current_run'].set(
os.path.join(self.gui_log.get(), p))
if not os.path.exists(
self.settings['log_dir_of_current_run'].get()):
os.makedirs(self.settings['log_dir_of_current_run'].get())
[docs] def check_dataset_path(self, p):
"""
Parameters
----------
p :
Returns
-------
"""
if not self.initialized:
result = True
elif not os.path.exists(p):
msg = "Failed to set dataset directory:\n" + \
"Specified directory does not exist."
messagebox.showwarning(title="Warning", message=msg)
result = False
elif self.settings['normalize'] and \
self.settings['dataset_format'] == 'npz' and not \
os.path.exists(os.path.join(p, 'x_norm.npz')):
msg = "No data set file 'x_norm.npz' found.\n" + \
"Add it, or disable normalization."
messagebox.showerror(title="Error", message=msg)
result = False
elif self.settings['dataset_format'] == 'npz' and not \
(os.path.exists(os.path.join(p, 'x_test.npz')) and
os.path.exists(os.path.join(p, 'y_test.npz'))):
msg = "Data set file 'x_test.npz' or 'y_test.npz' was not found."
messagebox.showerror(title="Error", message=msg)
result = False
else:
result = True
if result:
self.settings['dataset_path'].set(p)
return result
[docs] def set_cwd(self):
"""Set current working directory."""
p = filedialog.askdirectory(title="Set directory",
initialdir=self.toolbox_root)
self.check_path(p)
[docs] def set_dataset_path(self):
"""Set path to dataset."""
p = filedialog.askdirectory(title="Set directory",
initialdir=self.toolbox_root)
self.check_dataset_path(p)
def __scroll_handler(self, *l):
op, how_many = l[0], l[1]
if op == 'scroll':
units = l[2]
self.path_entry.xview_scroll(how_many, units)
elif op == 'moveto':
self.path_entry.xview_moveto(how_many)
[docs] def toggle_state_pynn(self, val):
"""Toogle state for pyNN."""
simulators_pynn = eval(self.config.get('restrictions',
'simulators_pyNN'))
if val not in list(simulators_pynn) + ['brian2']:
self.settings['state_pyNN'].set('disabled')
else:
self.settings['state_pyNN'].set('normal')
# for name in pyNN_keys:
# getattr(self, name + '_label').configure(
# state=self.settings['state_pyNN'].get())
# getattr(self, name + '_sb').configure(
# state=self.settings['state_pyNN'].get())
[docs] def toggle_start_state(self, val):
"""Toggle start state."""
if val:
self.start_state.set('disabled')
else:
self.start_state.set('normal')
self.start_processing_bt.configure(state=self.start_state.get())
[docs] def toggle_stop_state(self, val):
"""Toggle stop state."""
if val:
self.stop_state.set('disabled')
else:
self.stop_state.set('normal')
self.stop_processing_bt.configure(state=self.stop_state.get())
[docs] def toggle_num_to_test_state(self, val):
"""Toggle number to test state."""
if val:
self.settings['state_num_to_test'].set('normal')
else:
self.settings['state_num_to_test'].set('disabled')
self.num_to_test_label.configure(
state=self.settings['state_num_to_test'].get())
self.num_to_test_sb.configure(
state=self.settings['state_num_to_test'].get())
[docs] def toggle_poisson_input_state(self):
"""Toggle poisson input."""
if self.settings['poisson_input'].get():
self.poisson_input_state.set('normal')
else:
self.poisson_input_state.set('disabled')
self.input_rate_label.configure(state=self.poisson_input_state.get())
self.input_rate_sb.configure(state=self.poisson_input_state.get())
[docs]def main():
from snntoolbox.bin.utils import load_config
config = load_config(os.path.abspath(os.path.join(os.path.dirname(
__file__), '..', '..', 'config_defaults')))
root = tk.Tk()
root.title("SNN Toolbox")
app = SNNToolboxGUI(root, config)
root.protocol('WM_DELETE_WINDOW', app.quit_toolbox)
root.mainloop()
if __name__ == '__main__':
# main_thread = threading.Thread(target=main, name='main thread')
# main_thread.setDaemon(True)
# main_thread.start()
main()