# Create an ImSwitch Extension

**URL:** <https://openuc2.discourse.group/t/create-an-imswitch-extension/39>\
**Category:** openUC2 ImSwitch\
**Created:** [June 19, 2024, 4:01pm UTC](https://openuc2.discourse.group/t/create-an-imswitch-extension/39 "2024-06-19T16:01:17Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![benedictdied](https://avatars.discourse-cdn.com/v4/letter/b/96bed5/32.png) [@benedictdied](https://openuc2.discourse.group/u/benedictdied)\
**Post date:** [June 19, 2024, 4:01pm UTC](https://openuc2.discourse.group/t/create-an-imswitch-extension/39/1 "2024-06-19T16:01:17Z")

</div>

# Create an Extension for ImSwitch: A Tutorial

In the openUC2 ImSwitch fork we created a new feature. =\> You don’t need to understand the whole code base anymore, only the 3 main elements to interact with imswitch. 😇  
In this tutorial, we will create an adapter for the Arkitekt Experiment Manager to be used within ImSwitch. We will expose API-generated functions to a service and implement the Controller, Manager, and Widget for it.

To simplify the process, we will use a cookiecutter template that provides a skeleton for the plugin. Follow these steps to get started.

## 1. Setting Up Your Environment

1. Open a terminal and navigate to the directory where you want to develop the plugin, e.g., `~/Downloads`:

2. Clone the cookiecutter template from the GitHub repository:

3. Activate your Python environment or use your system Python:

4. Use the cookiecutter to generate the plugin skeleton and follow the prompts:

This process generates a project structure with files such as `imswitch_arkitekt_manager.py`, `imswitch_arkitekt_widget.py`, and `imswitch_arkitekt_controller.py`.

## 2. Setting Up Dependencies

Add any dependencies your plugin requires in the `setup.cfg` file. For our example, we need the `arkitekt` package:

```ini
[options]
packages = find:
install_requires =
    arkitekt

```

## 3. Registering the Plugin

In `setup.cfg`, register the entry points for the plugin:

```ini
[options.entry_points]
imswitch.manifest =
    imswitch-arkitekt = imswitch_arkitekt:imswitch.yaml
imswitch.implugins =
    imswitch_arkitekt_controller = imswitch_arkitekt:imswitch_arkitekt_controller
    imswitch_arkitekt_widget = imswitch_arkitekt:imswitch_arkitekt_widget

```

These entry points will be loaded inside ImSwitch.

## 4. Implementing the Widget

The widget needs to be derived from one of the base widgets. Here’s an example implementation in `imswitch_arkitekt_widget.py`:

```python
from imswitch.imcontrol.view.widgets.basewidgets import Widget
from imswitch.imcommon.model import initLogger
from arkitekt.qt.magic_bar import MagicBar
from arkitekt.builders import publicscheduleqt
from qtpy import QtCore, QtWidgets

class imswitch_arkitekt_widget(Widget):
    """Linked to the Arkitekt Controller."""

    sigLoadParamsFromHDF5 = QtCore.Signal()
    sigPickSetup = QtCore.Signal()
    sigClosing = QtCore.Signal()

    def __init__ (self, *args, **kwargs):
        super(). __init__ (*args, **kwargs)

        # Initialize the logger
        self.__logger = initLogger(self)
        self.__logger.debug("Initializing")

        # Create the main widget layout
        self.cwidget = QtWidgets.QWidget()
        layout = QtWidgets.QVBoxLayout()
        self.cwidget.setLayout(layout)

        # Initialize the Magic Bar (Arkitekt) to have the menu available
        identifier = "github.io.jhnnsrs.mikro-imswitch"
        version = "latest"
        logo = "https://avatars.githubusercontent.com/u/127734217?s=200&v=4"
        settings = QtCore.QSettings("imswitch", f"{identifier}:{version}")
        self.global_app = publicscheduleqt(
            identifier, version, parent=self.cwidget, logo=logo, settings=settings
        )
        self.magic_bar = MagicBar(self.global_app, dark_mode=True)

        self.loadParamsButton = QtWidgets.QPushButton("Load Parameters")
        self.loadParamsButton.clicked.connect(self.sigLoadParamsFromHDF5.emit)

        self.pickSetupButton = QtWidgets.QPushButton("Pick Setup")
        self.pickSetupButton.clicked.connect(self.sigPickSetup.emit)

        self.fileSelectButton = QtWidgets.QPushButton("Select File")
        self.fileSelectButton.clicked.connect(self.sigPickSetup.emit)

        layout.addWidget(self.magic_bar)
        layout.addWidget(self.loadParamsButton)
        layout.addWidget(self.pickSetupButton)
        layout.addWidget(self.fileSelectButton)
        layout.addStretch()

        self.setLayout(layout)

```

This may look as the following:

 ![image](https://global.discourse-cdn.com/free1/uploads/openuc2/original/1X/ccaa78574f1c61811fd38a8559f78f841f717bd7.png)

## 5. Implementing the Controller

The controller connects the widget with the model and handles interactions. Here’s an example implementation in `imswitch_arkitekt_controller.py`:

```python
from imswitch.imcontrol.model.managers.detectors.DetectorManager import DetectorManager
from imswitch.imcontrol.controller.basecontrollers import ImConWidgetController
from mikro.api.schema import from_xarray
from arkitekt import App
from rekuest.qt.builders import qtinloopactifier, qtwithfutureactifier
from mikro.api.schema import RepresentationFragment, OmeroRepresentationInput, PhysicalSizeInput, ChannelInput
from koil.qt import QtFuture
from qtpy import QtCore

class imswitch_arkitekt_controller(ImConWidgetController):
    """Linked to Arkitekt Controller."""

    def __init__ (self, *args, **kwargs):
        super(). __init__ (*args, **kwargs)
        self.__logger = initLogger(self)
        self.__logger.debug("Initializing imswitch arkitekt controller")

        self.__app = self._widget.global_app
        if not imswitch.IS_HEADLESS:
            self._widget.magic_bar.app_up.connect(self.on_app_up)
            self._widget.magic_bar.app_down.connect(self.on_app_down)

        self.qt_actifier = qtinloopactifier
        self.qt_future_activier = qtwithfutureactifier

        self.__app.rekuest.register(builder=self.qt_actifier)(self.call_run_scan)
        self.__app.rekuest.register(builder=self.qt_actifier)(self.set_laser_value)
        self.__app.rekuest.register()(self.snap_image)
        self.__app.rekuest.register(builder=self.qt_future_activier)(self.on_do_stuff_async)

    def on_do_stuff_async(self, qtfuture: QtFuture, hello: str) -> str:
        print(hello)
        qtfuture.resolve("42")
        return None

    def call_run_scan(self):
        controlMainController = self._MikroMainController__moduleMainControllers["imcontrol"]
        scanController = controlMainController.controllers["Scan"]
        scanController.runScan()

    def snap_image(self, xdims: int = 1000, ydims: int = 1000) -> RepresentationFragment:
        detectorName = self._master.detectorsManager.getAllDeviceNames()[0]
        detectorManager = self._controller._master.detectorsManager[detectorName]
        latestimg = detectorManager.getLatestFrame().copy()
        active_channels = [detectorName]
        if len(latestimg.shape) == 2:
            latestimg = latestimg[:, :, np.newaxis]
            active_channels = ["channel1"]

        metadata = OmeroRepresentationInput(
            physicalSize=PhysicalSizeInput(x=2, y=2),
            channels=[ChannelInput(name=channel) for channel in active_channels]
        )

        return from_xarray(
            xr.DataArray(latestimg, dims=["x", "y", "c"]),
            name="Randomly generated image",
            omero=metadata
        )

    def set_laser_value(self, lasername: str, value: int):
        allIlluNames = self._master.lasersManager.getAllDeviceNames()
        laserSource = self._master.lasersManager[allIlluNames[0]]
        laserSource.setEnabled(True)
        laserSource.setLaserValue(lasername, value)

    def on_app_up(self):
        print("App up")

    def on_app_down(self):
        print("App down")

    def closeEvent(self):
        self.__logger.debug("Shutting down")

```

## 6. Debugging Tips

- Start ImSwitch each time you make changes to test the extension.
- Keep the number of `availableWidgets` low to reduce startup times.

## 7. Installation

To install the plugin, navigate to the plugin directory and run:

```bash
pip install -e .

```

You should see:

```bash
Installing collected packages: imswitch-arkitekt
Successfully installed imswitch-arkitekt-0.0.1

```

## 8. Setting Up and Testing the Plugin

Add the plugin to the `availableModules` tag in your ImSwitch configuration:

```json
"availableWidgets": [
  "Settings",
  "View",
  "Recording",
  "Image",
  "Laser",
  "Positioner",
  "Autofocus",
  "MCT",
  "ROIScan",
  "HistoScan",
  "Hypha",
 

 "imswitch_arkitekt"
]

```

## 9. Publishing

Feel free to update the `README.md` and then add your changes and publish it on github. Then - e.g. in case of this example plugin - you can install it using:

```auto
pip install https://github.com/openUC2/imswitch-arkitekt/archive/refs/heads/main.zip

```

in the same environment as imswitch. Then you can use the plugin.

## 10. Troubleshooting

- Add any missing packages to `setup.cfg`.
- Ensure the correct names are used in the configuration to avoid errors like:

```python
--- Logging error ---
Traceback (most recent call last):
  ...
AttributeError: module 'imswitch.imcontrol.view.widgets' has no attribute 'arkitekt_controllerWidget'

```

With this guide, you should be able to create and set up an ImSwitch plugin successfully. Happy coding!
