Context-Menu Plugins¶
Context-menu plugins can provide additional tools and capabilities in specific
scenarios within WISER. For example, operations can be provided on datasets or
or other objects picked by a user (a point in a dataset, a spectrum, a region of
interest, etc.). To do this, the plugin must subclass the
wiser.plugins.ContextMenuPlugin type, filling in the various operations
that WISER will call.
Implementing a context-menu plugin requires some familiarity with Qt 5, since
the plugin must, at a minimum, add QMenu actions for specific operations
that are exposed. If a plugin intends to expose its own GUI for configuration
or other user interactions, please see GUI Plugins in WISER for more
details on how this may be done. You will also need to understand everything
in this section so that you can interface effectively with WISER’s internals.
The ContextMenuPlugin Class¶
Context-menu plugins must derive from the ContextMenuPlugin class. The
documentation for this class spells out the essential details for interfacing
with WISER.
- class wiser.plugins.ContextMenuPlugin[source]¶
This is the base type for plugins that integrate into WISER pop-up context menus.
- add_context_menu_items(context_type: ContextMenuType, context_menu: QMenu, context: Dict[str, Any]) → None[source]¶
This method is called by WISER when it is constructing a context menu, so that the plugin can add any menu-actions relevant to the context.
The type of context is indicated by the
context_typeargument/enum. Plugins should examine this value and only add menu entries relevant to the context type, to avoid cluttering up the WISER context menus. This is particularly important, as this method may be called multiple times (with differentcontext_typevalues), in the population of a single context-menu before it is displayed. For example, it is possible for a plugin to see calls to this method withRASTER_VIEW, thenDATASET_PICK, thenROI_PICK, in the construction of a single context menu.Based on the type of context, the
contextdictionary will contain specific key/value pairs relevant to the context, that the plugin may need for its operation. The details are specified below. Besides these values, thecontextdictionary will also always contain awiserkey that references awiser.gui.app_state.ApplicationStateobject for accessing and manipulating WISER’s internal state in specific ways.RASTER_VIEWIndicates a general operation on a dataset within a raster display window - that is, an operation not related to the cursor location. The
contextdictionary includes these keys:dataset- a reference to thewiser.raster.RasterDataSetobject currently being displayed.display_bands- a tuple of integers specifying the bands currently being displayed in the raster-view. This will either hold 1 element if the display is grayscale, or 3 elements if the display is red/green/blue.
SPECTRUM_PLOTIndicates a general operation within a spectrum-plot window - that is, not related to a specific spectrum or the cursor location. The
contextdictionary will not have any additional keys.DATASET_PICKIndicates a location-specific operation on a dataset within a raster display window - that is, an operation that requires the cursor location. The
contextdictionary includes these additional keys:dataset- a reference to thewiser.raster.RasterDataSetobject currently being displayed.display_bands- a tuple of integers specifying the bands currently being displayed in the raster-view. This will either hold 1 element if the display is grayscale, or 3 elements if the display is red/green/blue.ds_coord- an(int, int)tuple of the pixel in the dataset that was picked by the user.
SPECTRUM_PICKIndicates a spectrum-specific operation within a spectrum-plot window. The
contextdictionary will have this additional key:spectrum- a reference to thewiser.raster.Spectrumobject that was picked by the user.
ROI_PICKIndicates a region-of-interest-specific operation within a raster display window. The
contextdictionary includes these additional keys:dataset- a reference to thewiser.raster.RasterDataSetobject currently being displayed.display_bands- a tuple of integers specifying the bands currently being displayed in the raster-view. This will either hold 1 element if the display is grayscale, or 3 elements if the display is red/green/blue.roi- a reference to thewiser.raster.RegionOfInterestobject that was picked by the user.ds_coord- an(int, int)tuple of the pixel in the dataset that was picked by the user.
Plugins should be careful not to hold onto any context references for too long, as it will generate resource leaks within WISER. A recommended pattern for adding menu actions is as follows:
# Construct a lambda that is called when the QAction is clicked; # it traps the context dictionary and passes it to the relevant # handler. The context is reclaimed when the QAction goes away. act = context_menu.addAction(context_menu.tr('Some task...')) act.triggered.connect(lambda checked=False: self.on_some_task(context=context))
The ContextMenuType enumeration is as follows:
- enum wiser.plugins.ContextMenuType(value)[source]¶
This enumeration specifies the kind of context-menu event that occurred, so that plugins know what items to add to the menu.
Valid values are as follows:
- RASTER_VIEW = <ContextMenuType.RASTER_VIEW: 1>¶
- SPECTRUM_PLOT = <ContextMenuType.SPECTRUM_PLOT: 2>¶
- DATASET_PICK = <ContextMenuType.DATASET_PICK: 10>¶
- SPECTRUM_PICK = <ContextMenuType.SPECTRUM_PICK: 11>¶
- ROI_PICK = <ContextMenuType.ROI_PICK: 12>¶
Example Context-Menu Plugin¶
Here is an example of a simple context menu plugin.
import pprint
import textwrap
from typing import Any, Callable, Dict, List, Optional, Tuple
from wiser.plugins import ContextMenuPlugin, ContextMenuType
from PySide2.QtWidgets import QMenu, QMessageBox
class HelloContextPlugin(ContextMenuPlugin):
"""
A simple "Hello world!" example of a context-menu plugin.
"""
def __init__(self):
super().__init__()
def add_context_menu_items(
self,
context_type: ContextMenuType,
context_menu: QMenu,
context: Dict[str, Any],
) -> None:
"""
Use QMenu.addAction() to add individual actions, or QMenu.addMenu() to
add sub-menus to the Tools menu.
"""
if context_type == ContextMenuType.RASTER_VIEW:
"""
Context-menu display in a raster-view, which probably is showing a
dataset. The current dataset is passed to the plugin.
Example code to get all necessary pieces of data for this context_type:
```python
# A RasterDataSet object
dataset = context["dataset"]
# A 3 or 1 tuple of integers
display_bands = context["display_bands"]
# Every context_type has the app_state in the "wiser" key
app_state = context["wiser"]
```
"""
pass
elif context_type == ContextMenuType.SPECTRUM_PLOT:
"""
Context-menu display in the spectrum-plot window.
While this context_type makes the context dict have
no additional keys, we stil have the app_state key:
Example code to get all necessary pieces of data for this context_type:
```python
# Every context_type has the app_state in the "wiser" key
app_state = context["wiser"]
```
"""
pass
elif context_type == ContextMenuType.DATASET_PICK:
"""
A specific dataset was picked. This may not be in the context of
a raster-view window, e.g. if the user right-clicks on a dataset
in the info viewer.
Example code to get all necessary pieces of data for this context_type:
```python
# A RasterDataSet object
dataset = context["dataset"]
# A 3 or 1 tuple of integers
display_bands = context["display_bands"]
# An (int, int) tuple of the clicked pixel in the dataset above
ds_coord = context["ds_coord"]
# Every context_type has the app_state in the "wiser" key
app_state = context["wiser"]
```
"""
pass
elif context_type == ContextMenuType.SPECTRUM_PICK:
"""
A specific spectrum was picked. The spectrum is passed to the
plugin.
Example code to get all necessary pieces of data for this context_type:
```python
# A Spectrum object
spectrum = context["spectrum"]
# Every context_type has the app_state in the "wiser" key
app_state = context["wiser"]
```
"""
pass
elif context_type == ContextMenuType.ROI_PICK:
"""
A specific ROI was picked. The ROI is passed, along with the
current dataset (if available).
Example code to get all necessary pieces of data for this context_type:
```python
# A RasterDataSet object
dataset = context["dataset"]
# A 3 or 1 tuple of integers
display_bands = context["display_bands"]
# A RegionOfInterest object that was picked by the user
roi = context["roi"]
# An (int, int) tuple of the clicked pixel in the dataset above
ds_coord = context["ds_coord"]
# Every context_type has the app_state in the "wiser" key
app_state = context["wiser"]
```
"""
pass
else:
raise ValueError(f"Unrecognized context_type value {context_type}")
act = context_menu.addAction(f"Say hello {context_type}...")
act.triggered.connect(lambda checked=False: self.say_hello(context_type, context))
def say_hello(self, context_type: ContextMenuType, context: Dict[str, Any]):
context_str = pprint.pformat(context)
print("HelloContextPlugin.say_hello() was called!")
print(f" * context_type = {context_type}")
print(f' * context =\n{textwrap.indent(context_str, " " * 8)}')
QMessageBox.information(
None,
"Hello-Context Plugin",
f"Hello from a {context_type} context menu!\n\n{context_str}",
)