Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
259b567
starting to add plugins
sezelt Jan 7, 2025
1ef18ec
remove EMPAD2 reader functionality, which is now a separate plugin
sezelt Jan 7, 2025
2d9b444
no longer use argv in plugins since it's accessible from sys anyway
sezelt Jan 7, 2025
65e6bde
no longer pass argv to plugin loader
sezelt Jan 7, 2025
8af8633
create datacube setter function
sezelt Jan 7, 2025
d722dca
actually set datacube in datacube setter
sezelt Jan 8, 2025
6d72f08
migrate tcBF to an internal plugin
sezelt Jan 8, 2025
5d3b72d
hold on to menu objects to prevent them from being lost
sezelt Jan 8, 2025
e768d63
add ability for plugin to ask for a single action button, reimplement…
sezelt Jan 8, 2025
32925dc
edits to PLUGINS document
sezelt Jan 11, 2025
a0256a7
minor plugin related changes
sezelt Jan 12, 2025
1a19e47
new interface for getting the detector/selection information
sezelt Jan 15, 2025
e18817c
changes to PLUGINS readme
sezelt Jan 21, 2025
ece4e43
add note on using the GUI settings object from plugins
sezelt Feb 9, 2025
a6cf124
better handling of plugin import failure
sezelt Feb 23, 2025
91907d9
format with black 25
sezelt Feb 23, 2025
d08c597
set right side widget stretch correctly
sezelt Feb 23, 2025
59ba133
use loaded shape from py4dstem for arina data and warn on 3D
sezelt Apr 22, 2025
eb33d15
Merge pull request #32 from sezelt/issue31
sezelt May 19, 2025
4b9043d
update plugins to use new detector info interface
sezelt Jun 27, 2025
b5a96ea
format with black
sezelt Jun 27, 2025
be9cc48
Merge pull request #30 from sezelt/dev
sezelt Jun 27, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions PLUGINS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# py4DGUI Plugins

Over time, we have substantially pared down the functionality available in the browser, removing things such as pre-processing, file conversion, and data analysis. This has allowed the browser code to become much cleaner, and focused primarily on its core functionality of visualizing 4D-STEM data. In doing so, we have made the browser more robust and maintainable.
With the introduction of plugins in version 1.3.0, we hope to enable easy expansibility of the capabilities of the browser without complicating the core implementation.

## Known Plugins
We hope to maintain a list of existing plugins here. If you produce a browser plugin, feel free to message `sezelt` or create a PR to be added to this list.

### Pre-packaged plugins
Parts of what used to be "core" functionality are now implemented using the plugin interface to separate them from the core browser code. These are packaged with py4DGUI and always available:
* `Calibration`: Allows for the calibration of the scale bars using known physical distances. **Note:** This plugin is currently considered "badly behaved" because of the way it accesses the detector ROI objects directly. An abstract interface for this behavior will be created in the future, but for now this plugin should not be considered an "example" to follow.
* `tcBF`: Allows for the computation of tilt-corrected brightfield images. This also accesses detector ROIs directly and should be considered "badly behaved".

### External plugins
* [EMPAD2 Raw File Reader](https://github.com/sezelt/empad2): This also previously was present in the core browser code and would add an additional menu if the external package was installed. This adds the ability to import the "concatenated" raw binary data from the TFS EMPAD-G2 detector. This plugin is considered conforming to the guidelines.

# Creating a Plugin

The py4D_browser plugin mechanics are inspired by [Nion Swift](https://nionswift.readthedocs.io/en/stable/api/plugins.html), particularly how plugins are installed, discovered, and loaded.

Plugins should create a module in the `py4d_browser_plugin` namespace and should define a class with the `plugin_id` attribute

```python
class ExamplePlugin:

# required for py4DGUI to recognize this as a plugin.
plugin_id = "my.plugin.identifier"

######## optional flags ########
display_name = "Example Plugin"

# Plugins may add a top-level menu on their own, or can opt to have
# a submenu located under Plugins>[display_name], which is created before
# initialization and its QMenu object passed as `plugin_menu`
uses_plugin_menu = False

# If the plugin only needs a single action button, the browser can opt
# to have that menu item created automatically under Plugins>[Display Name]
# and its QAction object passed as `plugin_action`
uses_single_action = False

def __init__(self, parent, **kwargs):
self.parent = parent

def close(self):
pass # perform any shutdown activities

```

On loading the class is initialized using
```python
ExamplePlugin(parent=self, [...])
```
where `self` is the `DataViewer` instance (the main window object). All arguments will always be passed as keywords, including any additional arguments that are provided as a result of setting various optional flags. Plugins are loaded as the last step after constructing the `DataViewer`, before its `show()` method is called.

The current implementation of the plugin interface is thus extremely simple: the plugin object gets a reference to the main window, and can in theory do whatever artitrarily stupid things it wants with it, and there are no guarantees on compatibility between different versions of the browser and plugins. Swift solves this using the API Broker, which interposes all actions taken by the plugin. While we may adopt such an interface in version 2.0, for now we simply have the following design guidelines that should ensure compatibility:

* If the plugin adds menu items, it should only add items to its own menu (not to ones already existing in the GUI). The plugin is permitted to add a menu to the top bar on its own, or (preferably) can set the `uses_plugin_menu` attribute which will initialize a menu under Plugins>MyPluginDisplayName which gets passed to the initializer as `plugin_menu`.
* If the plugin adds a single menu item, it can have the browser create and insert that action item automatically by setting `uses_single_action`. The `QAction` object will be passed in as `plugin_action`.
* The plugin should *never* render an image to the views directly. To display images, plugins should always call `set_virtual_image` or `set_diffraction_image` using raw, unscaled data. If the plugin needs to produce a customized display, it cannot do that in the existing views and must create its own window.
* The plugin should not retain references to any objects in the `DataViewer`, as that may prevent objects from being freed at the right times. For example, do not do something like `self.current_datacube = self.parent.datacube`, as until this reference is cleared the browser could not free memory after closing a dataset and opening a new one.
* The plugin is allowed to read/write from the QSettings of the GUI, but should only do so in a top-level section with the same name as `plugin_id`, i.e. `value = self.parent.settings(self.plugin_id + "/my_setting", default_value)`.

## Accessing the detectors

With version 1.3.0, there is a new API for accessing the ROI selections made using the detectors on the two views. Plugins should only interact with the detectors via this API, as the implementation details of the ROI objects themselves are considered internal and subject to change. Calling `get_diffraction_detector` or `get_virtual_image_detector` yields a `DetectorInfo` object containing the properties of the current detector and the information (either a slice or a mask array) needed to produce the selection it represents.

## Namespace packages

Namespace packages are a way to split a package across multiple sources, which can be provided by different distributions. This allows the py4DGUI to import this special namespace and have all plugins, regardless of their source, appear under that import. Details can be found in [PEP 420](https://peps.python.org/pep-0420/).

In order to create a plugin, create a directory called `py4d_browser_plugin` under your `src` directory, and then create a directory for your plugin within that folder. _Do not place an `__init__.py` file in the `py4d_browser_plugin` folder, or the import mechanism will be broken for all plugins._
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,17 @@ Run `py4DGUI` in your terminal to open the GUI. Then just drag and drop a 4D-STE
* The information in the bottom bar contains the details of the virtual detector used to generate the images, and can be entered into py4DSTEM to generate the same image.
* The FFT pane can be switched between displaying the FFT of the virtual image and displaying the [exit wave power cepstrum](https://doi.org/10.1016/j.ultramic.2020.112994).
* Virtual images can be exported either as the scaled and clipped displays shown in the GUI or as raw data. The exact datatype stored in the raw TIFF image depends on both the datatype of the dataset and the type of virtual image being displayed (in particular, integer datatypes are converted internally to floating point to prevent overflows when generating any synthesized virtual images).
* If the [EMPAD-G2 Raw Reader](https://github.com/sezelt/empad2) is installed in the same environment, an extra menu will appear that allows the concatenated binary format data to be background subtracted and calibrated in the GUI. You can also save the calibrated data as an HDF5 file for later analysis.

![Demonstration](/images/demo.gif)

The keyboard map in the Help menu was made using [this tool](https://archie-adams.github.io/keyboard-shortcut-map-maker/) and the map file is in the top level of this repo.

## Plugins

As of version 1.3.0, we now support a simple means for loading plugins that extend the functionality of the browser. Details on creating a plugin can be found in [this document](PLUGINS.md).

The [EMPAD-G2 Raw Reader](https://github.com/sezelt/empad2), which was previously implemented in the browser code itself, is now implemented as a plugin, which can serve as an example.

## About

![py4DSTEM logo](/images/py4DSTEM_logo.png)
Expand Down
5 changes: 1 addition & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "py4D_browser"
version = "1.2.1"
version = "1.3.0"
authors = [
{ name="Steven Zeltmann", email="steven.zeltmann@lbl.gov" },
]
Expand Down Expand Up @@ -34,9 +34,6 @@ py4DGUI = "py4D_browser.runGUI:launch"
"Homepage" = "https://github.com/py4dstem/py4D-browser"
"Bug Tracker" = "https://github.com/py4dstem/py4D-browser/issues"

[tool.pyright]
venv = "py4dstem"

[tool.setuptools]
include-package-data = true

Expand Down
Loading