diff --git a/setup.cfg b/setup.cfg index c8c8b5f..8cb7dd6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -31,6 +31,9 @@ install_requires = magicgui qtpy scikit-image + arkitekt + mikro + reaktion python_requires = >=3.8 include_package_data = True @@ -55,8 +58,6 @@ testing = pytest # https://docs.pytest.org/en/latest/contents.html pytest-cov # https://pytest-cov.readthedocs.io/en/latest/ pytest-qt # https://pytest-qt.readthedocs.io/en/latest/ - imswitch - pyqt5 [options.package_data] diff --git a/src/imswitch_arkitekt/imswitch_arkitekt_controller.py b/src/imswitch_arkitekt/imswitch_arkitekt_controller.py index 34c35d1..2c7f3c2 100644 --- a/src/imswitch_arkitekt/imswitch_arkitekt_controller.py +++ b/src/imswitch_arkitekt/imswitch_arkitekt_controller.py @@ -1,13 +1,182 @@ from imswitch.imcontrol.model.managers.detectors.DetectorManager import DetectorManager, DetectorAction, DetectorNumberParameter from imswitch.imcontrol.controller.basecontrollers import ImConWidgetController from imswitch.imcontrol.view.ImConMainView import _DockInfo +import imswitch +from imswitch.imcommon.controller import MainController +from mikro.api.schema import ( + from_xarray, +) +from arkitekt import App +from rekuest.qt.builders import qtinloopactifier, qtwithfutureactifier +import xarray as xr +from imswitch.imcommon.model.logging import initLogger +from imswitch.imcontrol.controller.controllers.LaserController import LaserController +from rekuest.widgets import SliderWidget +from mikro.api.schema import ( + RepresentationFragment, + OmeroRepresentationInput, + PhysicalSizeInput, + ChannelInput, +) import numpy as np -from typing import Any, Dict, List, Optional, Tuple - +from koil.qt import QtFuture class imswitch_arkitekt_controller(ImConWidgetController): """Linked to CameraPluginWidget.""" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) + self.__logger = initLogger(self) + self.__logger.debug("Initializing imswitch arkitekt controller") + + # An actifier that will be used to register the functions + # actifiers are used to defined the asynchronous wrapping of the function + # i.e should the function be run in the main thread or in a separate thread + # if not provided arkitekt will offload the function to a separate thread + 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) + + # here we are using the qtinloopactifier which will run the function in the main thread of + # the qt application and return the result of that functioncall, this is often not a good idea + self.qt_actifier = qtinloopactifier + # A future actifier can be used to pass the function a future object that can be resolved + # at a later time, this is useful for functions that need to wait for a signal to be emitted + # before they can continue + self.qt_future_activier = qtwithfutureactifier + + # Will be run in the main thread + self.__app.rekuest.register(builder=self.qt_actifier)(self.call_run_scan) + self.__app.rekuest.register( + builder=self.qt_actifier, widgets={"value": SliderWidget(min=0, max=1024)} + )(self.set_laser_value) + + # Will be run in a separate thread + self.__app.rekuest.register()(self.snap_image) + + # WIll run in the main thread, but will be passed a future object that can be + # resolved at a later time + self.__app.rekuest.register(builder=self.qt_future_activier)(self.on_do_stuff_async) + + #@property + #def api(self): + # return self.__api + + #@property + #def shortcuts(self): + # return self.__shortcuts + + def on_do_stuff_async(self, qtfuture: QtFuture, hello: str) -> str: + """Do Stuff Async + + This function will be run in the main thread, but will be passed a future object + that can be resolved at a later time. This is useful for functions that need to + wait for a signal to be emitted before they can continue. + + Args: + qtfuture (QFuture): The future object that can be resolved at a later time. + hello (str): A string that will be printed to the console. + + Returns: + str: A string that will be returned to the caller. + """ + print(hello) + + # YOu can store the future object and resolve it at a later time + + self._to_be_resolved_future = qtfuture + + + #here we are resolving the future object with the value 42 immediately + # This is not very useful, but you can imagine that you would resolve the future + # object when a signal is emitted or when some other condition is met + qtfuture.resolve("42") + + return None + + def call_run_scan(self): + """Run Scan + + Runs the currently active scan that is open on imswitch. + """ + controlMainController = self._MikroMainController__moduleMainControllers[ + "imcontrol" + ] + scanController = controlMainController.controllers["Scan"] + scanController.runScan() + + def snap_image( + self, xdims: int = 1000, ydims: int = 1000 + ) -> RepresentationFragment: + """Snap Image + + Snaps a single image from the camera. + + Args: + xdims (int): The dimensions of the image in the x direction. + ydims (int): The dimensions of the image in the y direction. + + Returns: + RepresentationFragment: The generated image. + """ + 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): + """Set Laser Value + + Sets the value of the laser. + + Args: + lasername (str): The lasername value + value (int): the value to set the laser to + """ + 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") + + +# Copyright (C) 2020-2021 ImSwitch developers +# This file is part of ImSwitch. +# +# ImSwitch is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# ImSwitch is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . diff --git a/src/imswitch_arkitekt/imswitch_arkitekt_manager.py b/src/imswitch_arkitekt/imswitch_arkitekt_manager.py index b839ec4..30087bb 100644 --- a/src/imswitch_arkitekt/imswitch_arkitekt_manager.py +++ b/src/imswitch_arkitekt/imswitch_arkitekt_manager.py @@ -11,22 +11,11 @@ def __init__(self, pluginInfo, *args, **kwargs): super().__init__(*args, **kwargs) self.__logger = initLogger(self) + self.global_app = None + if pluginInfo is None: # import imswitch_sim_info.py return - self.__pluginInfo = pluginInfo - self.__wavelength = self.__pluginInfo.wavelength - self.__pixelsize = self.__pluginInfo.pixelSize - self.__angleMount = self.__pluginInfo.angleMount - self.__simSize = (self.__pluginInfo.width, self.__pluginInfo.height) - self.__patternsDir = self.__pluginInfo.patternsDir - self.isSimulation = self.__pluginInfo.isSimulation - self.nRotations = self.__pluginInfo.nRotations - self.nPhases = self.__pluginInfo.nPhases - self.simMagnefication = self.__pluginInfo.nPhases - self.isFastAPISIM = self.__pluginInfo.isFastAPISIM - self.simPixelsize = self.__pluginInfo.simPixelsize - self.simNA = self.__pluginInfo.simNA - self.simN = self.__pluginInfo.simN # refr - self.simETA = self.__pluginInfo.simETA + def set_global_app(self, app): + self.global_app = app \ No newline at end of file diff --git a/src/imswitch_arkitekt/imswitch_arkitekt_widget.py b/src/imswitch_arkitekt/imswitch_arkitekt_widget.py index a184068..74e9ff4 100644 --- a/src/imswitch_arkitekt/imswitch_arkitekt_widget.py +++ b/src/imswitch_arkitekt/imswitch_arkitekt_widget.py @@ -1,14 +1,69 @@ 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 import numpy as np from typing import Any, Dict, List, Optional, Tuple +from dataclasses import dataclass +from qtpy import QtCore, QtWidgets -class dotdict(dict): - """dot.notation access to dictionary attributes""" - __getattr__ = dict.get - __setattr__ = dict.__setitem__ - __delattr__ = dict.__delitem__ - +global_app = None class imswitch_arkitekt_widget(Widget): - """Linked to CameraPluginWidget.""" + """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-napari" + 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.loadParamsAction = QtWidgets.QAction( + "Load parameters from saved HDF5 fileā€¦", self + ) + + # Buttons + 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) + + # Adding widgets to layout + layout.addWidget(self.magic_bar) + layout.addWidget(self.loadParamsButton) + layout.addWidget(self.pickSetupButton) + layout.addWidget(self.fileSelectButton) + layout.addStretch() + + self.setLayout(layout) + + + +@dataclass +class _DockInfo: + name: str + yPosition: int