The phy user directory is
~ is your home directory. On Linux, it is typically
The phy user config file is
~/.phy/phy_config.py. It is a Python file. A default file is automatically created if needed.
Writing plugins for phy
You can extend the GUI by writing plugins. This involves the following steps:
- Make sure the plugin code is stored in a file that is loaded by phy.
- Write a plugin by creating a class deriving from
- Add the plugin name to the list of plugins loaded by the GUI.
1. Where to write the code
You can write the code either:
- In your phy user config file
- Or in a separate Python file saved in
Warning: make sure that the same plugin (using the same name) is not defined in two different places, otherwise it might not be loaded properly.
2. Writing the plugin
Here is an example of a "hello world" plugin:
from phy import IPlugin class MyPlugin(IPlugin): def attach_to_controller(self, controller): print("Hello world!")
Put this code either in
~/.phy/phy_config.py or in
~/.phy/plugins/myplugin.py. If the latter, remove the possibly existing
MyPlugin class definition in
When a plugin is activated, the
attach_to_controller(controller) function is automatically called with the
TemplateController instance as argument. This object gives you programmatic access to all objects, data, and views in the GUI.
3. Activating the plugin
Put this line in your
c.TemplateGUI.plugins = ['MyPlugin']
Then, launching the GUI should print
Using the event system
A lightweight event system is implemented in phy. You will likely have to use it when writing plugins, so here is a brief overview.
Objects in the program may emit events. An event has a name and optional parameters. Other objects may subscribe to events by registering callbacks. A callback is a function that is called when an event is raised.
Events are raised globally. Every object in the Python process may subscribe a callback function to any event.
Registering a callback
In this example, we register a callback function to the event
from phy import connect @connect def on_myevent(sender, arg): """The function name pattern is on_eventname().""" print("myevent called with argument %s" % arg)
Note: the first argument,
sender, is mandatory. You can use arbitrary further positional and keyword arguments.
Emitting an event
In this example, the object
sender (any Python object) emits a
myevent event with argument
from phy import emit emit('myevent', sender, 123)
This will automatically call all callbacks registered to
myevent, in the order they were registered. So here, it will display:
myevent called with argument 123
Optional keyword arguments to
connect() function has a few optional parameters.
connect(f, event=event)to specify the event name explicitely, without having to use a special
on_eventname()name for the function.
connect(f, sender=sender)to restrict the callback to a specific sender.
Note: events are sent globally in the Python process.
You can also filter senders directly in the callback function. For example, here is how to write a callback function that will only react to events sent by instances of the
@connect def on_myevent(sender, arg): if not isinstance(sender, AcceptableSender): return # Now, process the event as we're sure the sender derives from the AcceptableSender class.
Main objects in phy
The main objects used in phy are the following.
This is the main object, that holds together the model (access to the data), the views, and the manual clustering logic.
This object (
controller.model) holds the data: spike times, template waveforms, raw data, initial template assignments, etc.
A few remarks:
- There is a distinction between templates and clusters: spike templates assignments are done by the spike sorting algorithm (e.g. KiloSort), whereas spike clusters assignments are manual refinements of the original spike templates assignments. Therefore:
spike_templates.npyis created by the spike sorting algorithm, and never modified by phy
spike_clusters.npyis initially a copy of
spike_templates.npy, but it is modified by phy during the manual clustering session
- The TemplateModel supports both dense and sparse arrays for templates, features...
- In several methods, the TemplateModel requires the channel ids to be passed explicitly. There are methods to find the "best" channels associated to a given template or cluster.
This object (
controller.supervisor) is responsible for creating the cluster and similarity views, defining all clustering actions, specifying the logic of the wizard, etc. It holds together more specialized classes that you should not have to know about:
- Clustering: manages the cluster assignments and the related undo stack.
- ClusterMeta: manages the cluster groups and labels, and the related undo stack.
- History: a generic undo stack used by the two classes above.
- Table: a table used by the cluster and similarity views.
- Context: manages the memory and disk cache, using joblib.
- ClusterColorSelector: manages the cluster color mapping.
tablejs that uses the
Disk cache and memory cache are stored in the
.phy subdirectory within the data directory. Functions retrieving cluster-dependent data such as waveforms, templates, and so on, are all cached for performance reasons. It is important to ensure that this directory is stored on an SSD.
This object provides a lightweight generic GUI with the following features:
- Dock widgets for the views (HTML, OpenGL, matplotlib)
- Status bar
- Menu bar and submenus
- Keyboard shortcuts
- GUI state
Default menus are accessible with
gui.select_actions, and so on. These are instances of the
Actions class. The main method of
Actions.add(). This method has many arguments, see the Plugins section for a few examples, and the API documentation for more details.
The GUI state contains information about the GUI, like the position, size, dock widget layout, and view options that can be changed via the menu or keyboard shortcuts, like the number of bins in the correlogram view and so on. The GUI state is saved in two JSON files:
- Global state: in
- Local state: in
Values saved in the local state (like data-dependent scaling in the views) override those saved in the global state.
These files are automatically recreated if they're missing.
Note: in case of visualization problem in the GUI, feel free to delete these files.
Events used in phy
In your plugins, you can register custom functions to existing events that are used in phy. This is how you can extend the GUI.
Important events used in phy include the following (ordered by the different objects that can send the events).
Remember the generic form of event emission:
emit(event_name, sender, argument).
emit('gui_ready', controller, gui): when the GUI has been fully loaded.
emit('table_filter', cluster_view, cluster_ids): when cluster filtering changes in the cluster view.
emit('table_sort', cluster_view, cluster_ids): when the cluster order changes in the cluster view.
emit('attach_gui', supervisor): when the Supervisor has been attached to the GUI.
emit('select', supervisor, cluster_ids): when the cluster selection changes.
emit('cluster', supervisor, up): when cluster assignments, cluster groups, or cluster labels change.
UpdateInfoobject that contains all the information about the cluster changes.
emit('color_mapping_changed', supervisor): when a different color mapping is selected.
This class derives from
phy.Bunch, which itself derives from the Python built-in
dict type. It is a dictionary with an extra
a.b syntax that is more convenient than
UpdateInfo attributes include:
description: information about the update:
assign(for split), or
metadata_namefor group/label changes
added: list of new clusters
deleted: list of old clusters (remember that cluster ids do not change, there can be only be added clusters or deleted clusters)
spike_ids: all spike ids affected by the update
descendants: list of pairs
(old_cluster_id, new_cluster_id), used to track the history of clusters
metadata_changed: clusters that had changed metadata (group or label)
metadata_value: new value of the metadata (group or label)
The following events are raised with Control+click in specific views:
emit('cluster_click', view, cluster_id, button=None)when a cluster is selected in the raster or template view.
emit('spike_click', trace_view, channel_id=None, spike_id=None, cluster_id=None): when a spike is selected in the trace view.
emit('channel_click', waveform_view, channel_id=None, key=None, button=None): when a channel is selected in the waveform view.
emit('add_view', gui, view): when a view is added to the GUI.
emit('close_view', gui, view): when a view is closed in the GUI.
emit('show', gui): when the GUI is shown.
emit('close', gui): when the GUI is closed.
Examples of using the API
You can use the phy API to write custom analysis and visualization scripts, without using the GUI. We give a few examples here.
Note: the high-level methods provided by
TemplateModel are convenient but not particularly efficient. If you need better performance, you should use the
TemplateController class which leverages the cache in the
Here is a Python script that will extract the waveforms of a given cluster directly from the raw data, and display them.
import sys import matplotlib.pyplot as plt from phylib.io.model import load_model from phylib.utils.color import selected_cluster_color # First, we load the TemplateModel. model = load_model(sys.argv) # first argument: path to params.py # We obtain the cluster id from the command-line arguments. cluster_id = int(sys.argv) # second argument: cluster index # We get the waveforms of the cluster. waveforms = model.get_cluster_spike_waveforms(cluster_id) n_spikes, n_samples, n_channels_loc = waveforms.shape # We get the channel ids where the waveforms are located. channel_ids = model.get_cluster_channels(cluster_id) # We plot the waveforms on the first four channels. f, axes = plt.subplots(1, min(4, n_channels_loc), sharey=True) for ch in range(min(4, n_channels_loc)): axes[ch].plot(waveforms[::100, :, ch].T, c=selected_cluster_color(0, .05)) axes[ch].set_title("channel %d" % channel_ids[ch]) plt.show()
Launching the Template GUI
Simply use the following script to launch the GUI from a Python script.
from phy.apps.template import template_gui # Launch the GUI using the params.py file found in the current directory. template_gui('params.py')