Extending RadioViz
RadioViz is designed to be extended with new tools. There are two supported paths:
Built-in tools contributed via merge requests to the main repository.
External plugins distributed as separate Python packages and discovered through entry points.
Built-in vs external tools
Built-in tools live in the tools package and are loaded automatically at startup. If you want your tool to ship with RadioViz, submit it as a merge request with the new tool module and any supporting UI or model changes.
External tools can be shipped as independent packages. RadioViz discovers them via the radioviz.tools entry-point group. Your package should declare an entry point that returns a Tool subclass or a factory that returns a Tool instance.
Example pyproject.toml for a plugin package:
[project.entry-points."radioviz.tools"]
my_tool = "my_radioviz_plugin.my_tool:MyTool"
The entry-point name should match your tool’s tool_id.
Tool contract
Tools follow a small, consistent contract. The core classes are:
Toolfor metadata and registration.ToolControllerfor runtime behavior.ToolDockWidgetfor dock UI.BaseToolSessionfor per-image sessions.ToolContextfor access to app state.
The typical structure is:
Toolowns tool metadata and registers overlays and windows.create_controller()constructs aToolController.ToolController.create_session()creates aBaseToolSessionfor the active window.ToolController.create_dock()creates aToolDockWidgetif the tool needs a dock UI.
Tool API and interaction
ToolContext provides access to application state and the active image controller. Tools use it to react to active window changes and to request messages in the UI.
BaseToolSession defines the lifecycle for operations tied to a single window. Sessions start when a tool is activated and end when the tool is deactivated or the active window changes. Sessions allow to have an isolated sandbox where you can perform all your calculations without interfering with the underlying data model. When the session is finished, a ToolSessionResult is returned containing the results of the session so that it can be included in the data model.
Menu entries are provided by the controller via ToolController.menu_specs returning ToolMenuSpec and ToolActionSpec objects. The main window renders these entries into the Tools (context) menu. Tools can also contribute nested menu categories by returning nested ToolMenuSpec objects; menus with the same title are merged.
Ordering can be controlled by setting order on ToolMenuSpec and ToolActionSpec. Lower values appear first. When any entry uses order at a given menu level, items without an explicit order are placed after the ordered items and ties fall back to alphabetical order.
Creating new windows
Some tools need to open a new sub-window. These windows are created through the window factory. A tool can register new window types using windows_to_be_registered with WindowSpec objects.
When a tool needs to open a new window, it emits a WindowRequest from its controller. The main controller handles the request and delegates creation to the window factory WindowFactoryRegistry.
Example from existing tools
The Profile tool is a good reference implementation:
Tool class:
ProfileToolController:
ProfileToolControllerDock widget:
ProfileToolDockSessions:
ProfileToolSession
Look at:
menu_specs()for how menu actions are exposed.windows_to_be_registeredandoverlays_to_be_registeredfor window and overlay registration.create_session()for session creation.create_dock()for dock creation and wiring.
Implementation checklist
Define a
Toolsubclass with a uniqueTool.tool_id.Provide a
ToolControllerwith sessions and optional dock UI.Register overlays and window specs as needed.
Add menu actions through
menu_specs().If external, expose the tool via the
radioviz.toolsentry-point group.If internal (proposed as merge request), extend the
_BUILTIN_TOOL_PATHSlist with your tool.
Quickstart: a minimal tool
Below is a minimal tool that adds a menu entry and a dock. It omits overlays and sessions for brevity.
Tool module:
from PySide6.QtWidgets import QLabel
from radioviz.models.menu_spec import ToolActionSpec, ToolMenuSpec
from radioviz.tools.base_controller import ToolController
from radioviz.tools.base_dock import ToolDockWidget
from radioviz.tools.base_tool import Tool
from radioviz.tools.tool_api import BaseToolSession
class HelloSession(BaseToolSession):
def start(self):
pass
def cancel(self, reason: str):
pass
class HelloDock(ToolDockWidget):
dock_position = ToolDockWidget.DockWidgetArea.RightDockWidgetArea
initial_visibility = True
def __init__(self, parent, controller):
super().__init__('Hello', parent, controller)
self.setWidget(QLabel('Hello from a plugin!'))
class HelloController(ToolController):
def create_session(self, window_controller):
return HelloSession(window_controller, self)
def create_dock(self, parent_window):
return HelloDock(parent_window, self)
def menu_specs(self):
return [
ToolMenuSpec(
title='Analysis',
order=10,
entries=[
ToolMenuSpec(
title='Hello',
order=10,
entries=[
ToolActionSpec(
text='Say Hello',
triggered=self._say_hello,
),
],
)
],
)
]
def _say_hello(self):
self.context.request_message('Hello from RadioViz!', level='info')
class HelloTool(Tool):
tool_id = 'hello'
name = 'Hello Tool'
description = 'Minimal example tool'
def create_controller(self, ctx):
self._controller = HelloController(ctx, self)
return self._controller
External plugin entry point:
[project.entry-points."radioviz.tools"]
hello = "my_radioviz_plugin.hello:HelloTool"