From 181cec80d74d77c4ec3dd4687919ca2b9c33a572 Mon Sep 17 00:00:00 2001 From: beniroquai Date: Fri, 8 Nov 2024 23:20:13 +0100 Subject: [PATCH] Add objective Controller, fixing app exit in noqt mode --- .arkitekt_next/.dockerignore | 5 + .arkitekt_next/.gitignore | 5 + imswitch/__main__.py | 7 +- imswitch/imcommon/applaunch.py | 23 ++- imswitch/imcommon/framework/noqt.py | 5 +- .../controller/CommunicationChannel.py | 1 + .../controller/ImConMainController.py | 45 +++-- .../controllers/ObjectiveController.py | 187 ++++++++++++++++++ .../ObjectiveRevolverController.py | 120 ----------- .../controllers/RecordingController.py | 3 +- .../controller/controllers/__init__.py | 2 +- .../controller/server/ImSwitchServer.py | 5 +- imswitch/imcontrol/model/__init__.py | 2 +- .../positioners/VirtualStageManager.py | 17 +- imswitch/imcontrol/view/ImConMainView.py | 70 +------ ...veRevolverWidget.py => ObjectiveWidget.py} | 4 +- imswitch/imcontrol/view/widgets/__init__.py | 2 +- setup.py | 1 + 18 files changed, 278 insertions(+), 226 deletions(-) create mode 100644 .arkitekt_next/.dockerignore create mode 100644 .arkitekt_next/.gitignore create mode 100644 imswitch/imcontrol/controller/controllers/ObjectiveController.py delete mode 100644 imswitch/imcontrol/controller/controllers/ObjectiveRevolverController.py rename imswitch/imcontrol/view/widgets/{ObjectiveRevolverWidget.py => ObjectiveWidget.py} (95%) diff --git a/.arkitekt_next/.dockerignore b/.arkitekt_next/.dockerignore new file mode 100644 index 000000000..650734d37 --- /dev/null +++ b/.arkitekt_next/.dockerignore @@ -0,0 +1,5 @@ +# Hiding ArkitektNext Credential files from git +*.json +*.temp +cache/ +servers/ \ No newline at end of file diff --git a/.arkitekt_next/.gitignore b/.arkitekt_next/.gitignore new file mode 100644 index 000000000..650734d37 --- /dev/null +++ b/.arkitekt_next/.gitignore @@ -0,0 +1,5 @@ +# Hiding ArkitektNext Credential files from git +*.json +*.temp +cache/ +servers/ \ No newline at end of file diff --git a/imswitch/__main__.py b/imswitch/__main__.py index 0320e5be6..16be58241 100644 --- a/imswitch/__main__.py +++ b/imswitch/__main__.py @@ -90,6 +90,7 @@ def main(is_headless:bool=None, default_config:str=None, http_port:int=None, ssl if imswitch.IS_HEADLESS: os.environ["DISPLAY"] = ":0" os.environ["QT_QPA_PLATFORM"] = "offscreen" + app = None else: app = prepareApp() enabledModuleIds = modulesconfigtools.getEnabledModuleIds() @@ -125,7 +126,6 @@ def main(is_headless:bool=None, default_config:str=None, http_port:int=None, ssl else: multiModuleWindow = None multiModuleWindowController = None - # Register modules for modulePkg in modulePkgs: @@ -167,12 +167,11 @@ def main(is_headless:bool=None, default_config:str=None, http_port:int=None, ssl multiModuleWindow.updateLoadingProgress(i / len(modulePkgs)) app.processEvents() # Draw window before continuing logger.info(f'init done') - if not imswitch.IS_HEADLESS: - launchApp(app, multiModuleWindow, moduleMainControllers.values()) + launchApp(app, multiModuleWindow, moduleMainControllers.values()) except Exception as e: logging.error(traceback.format_exc()) - +Bujah!!! You have successfully created a main function that can be called from the command line. You can now run the main function by calling the main function in the __main__.py file. if __name__ == '__main__': main() diff --git a/imswitch/imcommon/applaunch.py b/imswitch/imcommon/applaunch.py index e0a0cd63a..88d846f1c 100644 --- a/imswitch/imcommon/applaunch.py +++ b/imswitch/imcommon/applaunch.py @@ -51,17 +51,22 @@ def prepareApp(): def launchApp(app, mainView, moduleMainControllers): """ Launches the app. The program will exit when the app is exited. """ + logger = initLogger('launchApp') if IS_HEADLESS: """We won't have any GUI, so we don't need to prepare the app.""" - return None - - logger = initLogger('launchApp') - - # Show app - if mainView is not None: - mainView.showMaximized() - mainView.show() - exitCode = app.exec_() + # Keep python running + while True: # TODO: have webserver signal somehow? + try: + import time + time.sleep(1) + except KeyboardInterrupt: + break + else: + # Show app + if mainView is not None: + mainView.showMaximized() + mainView.show() + exitCode = app.exec_() # Clean up for controller in moduleMainControllers: diff --git a/imswitch/imcommon/framework/noqt.py b/imswitch/imcommon/framework/noqt.py index 037f739e4..fea387485 100644 --- a/imswitch/imcommon/framework/noqt.py +++ b/imswitch/imcommon/framework/noqt.py @@ -278,7 +278,10 @@ def run_uvicorn(): ssl_certfile=os.path.join(_baseDataFilesDir, "ssl", "cert.pem") if __ssl__ else None, timeout_keep_alive=2, ) - uvicorn.Server(config).run() + try: + uvicorn.Server(config).run() + except Exception as e: + print(f"Couldn't start server: {e}") except Exception as e: print(f"Couldn't start server: {e}") diff --git a/imswitch/imcontrol/controller/CommunicationChannel.py b/imswitch/imcontrol/controller/CommunicationChannel.py index c53241dd7..2f332f3b5 100644 --- a/imswitch/imcontrol/controller/CommunicationChannel.py +++ b/imswitch/imcontrol/controller/CommunicationChannel.py @@ -88,6 +88,7 @@ class CommunicationChannel(SignalInterface): sigSendScanFreq = Signal(float) # (scanPeriod) + sigPixelSizeChange = Signal(float) # (pixelSize) #sigRequestScannersInScan = Signal() #sigSendScannersInScan = Signal(object) # (scannerList) diff --git a/imswitch/imcontrol/controller/ImConMainController.py b/imswitch/imcontrol/controller/ImConMainController.py index f4fe38bc3..60c0ef61f 100644 --- a/imswitch/imcontrol/controller/ImConMainController.py +++ b/imswitch/imcontrol/controller/ImConMainController.py @@ -52,24 +52,28 @@ def __init__(self, options, setupInfo, mainView, moduleCommChannel): self.controllers = {} for widgetKey, widget in self.__mainView.widgets.items(): - try: + self.__logger.debug(f'Creating controller for widget {widgetKey}') + + # Check if the controller is available + controller_name = f'{widgetKey}Controller' + if widgetKey == 'Scan': + controller_name = f'{widgetKey}Controller{self.__setupInfo.scan.scanWidgetType}' + + if hasattr(controllers, controller_name): + # Controller ist vorhanden self.controllers[widgetKey] = self.__factory.createController( - (getattr(controllers, f'{widgetKey}Controller') - if widgetKey != 'Scan' else - getattr(controllers, f'{widgetKey}Controller{self.__setupInfo.scan.scanWidgetType}')), widget - ) - except Exception as e: - #try to get it from the plugins - foundPluginController = False - for entry_point in pkg_resources.iter_entry_points(f'imswitch.implugins'): - if entry_point.name == f'{widgetKey}_controller': - packageController = entry_point.load() - self.controllers[widgetKey] = self.__factory.createController(packageController, widget) - foundPluginController = True - break - if not foundPluginController: + getattr(controllers, controller_name), widget) + else: + try: + mPlugin = self.loadPlugin(widgetKey) + if mPlugin is None: + raise ValueError(f'No controller found for widget {widgetKey}') + self.controllers[widgetKey] = self.__factory.createController( + mPlugin, widget) + except Exception as e: self.__logger.debug(e) raise ValueError(f'No controller found for widget {widgetKey}') + # Generate API self.__api = None apiObjs = list(self.controllers.values()) + [self.__commChannel] @@ -91,6 +95,17 @@ def __init__(self, options, setupInfo, mainView, moduleCommChannel): self._thread = threading.Thread(target=self._serverWorker.run) self._thread.start() + + def loadPlugin(self, widgetKey): + # try to get it from the plugins + foundPluginController = False + for entry_point in pkg_resources.iter_entry_points(f'imswitch.implugins'): + if entry_point.name == f'{widgetKey}_controller': + packageController = entry_point.load() + return packageController + self.__logger.error(f'No controller found for widget {widgetKey}') + return None + @property def api(self): return self.__api diff --git a/imswitch/imcontrol/controller/controllers/ObjectiveController.py b/imswitch/imcontrol/controller/controllers/ObjectiveController.py new file mode 100644 index 000000000..f968b39b8 --- /dev/null +++ b/imswitch/imcontrol/controller/controllers/ObjectiveController.py @@ -0,0 +1,187 @@ + +from imswitch.imcommon.model import dirtools, modulesconfigtools, ostools, APIExport +from imswitch.imcommon.framework import Signal, Worker, Mutex, Timer +from imswitch.imcontrol.view import guitools +from imswitch.imcommon.model import initLogger +from imswitch.imcontrol.controller.basecontrollers import LiveUpdatedController +from imswitch import IS_HEADLESS + + + +class ObjectiveController(LiveUpdatedController): + """ Linked to ObjectiveWidget.""" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._logger = initLogger(self, tryInheritParent=False) + + # connect camera and stage - + self.positionerName = self._master.positionersManager.getAllDeviceNames()[0] + self.positioner = self._master.positionersManager[self.positionerName] + self.detectorName = self._master.detectorsManager.getAllDeviceNames()[0] + self.detector = self._master.detectorsManager[self.detectorName] + + # Define the parameters for the objective revolver + self.currentObjective = 0 + + #TODO this should be loaded from manager + self.revolverAxis = "A" + self.posObjective1 = 0 + self.posObjective2 = 20000 + self.nameObjective1 = 10 + self.nameObjective2 = 20 + self.magnObjective1 = "10x" + self.magnObjective2 = "20x" + self.pEffObjective1 = .2 + self.pEffObjective2 = .1 + self.calibrateOnStart = True + + # initialize Objective Store + self.objectiveStorer = ObjecitveStorer() + self.objectiveStorer.setObjectiveByID(0, position=self.posObjective1, name=self.nameObjective1, magnification=self.magnObjective1, pixelsize_eff=self.pEffObjective1) + self.objectiveStorer.setObjectiveByID(1, position=self.posObjective2, name=self.nameObjective2, magnification=self.magnObjective2, pixelsize_eff=self.pEffObjective2) + + # initialize objective lens mover + self.objectiveLensMover = ObjectiveLensMover(positioner = self.positioner, positionerName = self.positionerName, + revolverAxis = self.revolverAxis, objectiveStorer = self.objectiveStorer) + + if self.calibrateOnStart: + self.calibrateObjective() + + if IS_HEADLESS: + return + + # Connect signals to slots + self._widget.btnObj1.clicked.connect(self.onObj1Clicked) + self._widget.btnObj2.clicked.connect(self.onObj2Clicked) + self._widget.btnCalibrate.clicked.connect(self.onCalibrateClicked) + self._widget.btnMovePlus.clicked.connect(self.onMovePlusClicked) + self._widget.btnMoveMinus.clicked.connect(self.onMoveMinusClicked) + self._widget.btnSetPosObj1.clicked.connect(self.onSetPosObj1Clicked) + self._widget.btnSetPosObj2.clicked.connect(self.onSetPosObj2Clicked) + + + @APIExport(runOnUIThread=True) + def calibrateObjective(self): + '''This homes the objective revolver.''' + self.objectiveLensMover.calibrateObjective() + self.moveToObjectiveID(self.currentObjective) + if not IS_HEADLESS: self._widget.setCurrentObjectiveInfo(self.currentObjective) + + @APIExport(runOnUIThread=True) + def moveToObjectiveID(self, objectiveID: int): + '''This moves the objective revolver to the objectiveID.''' + self.currentObjective = objectiveID + self.objectiveLensMover.moveToObjectiveID(objectiveID) + # update the pixelsize_eff + self.detector.setPixelSizeUm(self.objectiveStorer.getObjectiveByID(objectiveID).pixelsize_eff) + self._master._MasterController__commChannel.sigPixelSizeChange.emit() + if not IS_HEADLESS: self._widget.setCurrentObjectiveInfo(self.currentObjective) + + @APIExport(runOnUIThread=True) + def getCurrentObjective(self): + return self.currentObjective, self.objectiveStorer.getObjectiveByID(self.currentObjective).name + + def onObj1Clicked(self): + # move to objective 1 + self.moveToObjectiveID(1) + + def onObj2Clicked(self): + # move to objective 2 + self.moveToObjectiveID(2) + + def onCalibrateClicked(self): + # move to objective 1 after homing + self.calibrateObjective() + + def onMovePlusClicked(self): + # Define what happens when Move + is clicked + self.positioner.move(value=self.incrementPosition, speed=self.speed, axis=self.revolverAxis, is_absolute=False, is_blocking=False) + + def onMoveMinusClicked(self): + # Define what happens when Move - is clicked + self.positioner.move(value=-self.incrementPosition, speed=self.speed, axis=self.revolverAxis, is_absolute=False, is_blocking=False) + + def onSetPosObj1Clicked(self): + # Define what happens when Set Position Objective 1 is clicked + self.positioner.setPositionOnDevice(axis=self.revolverAxis, value=0) + self.currentObjective = 1 + + def onSetPosObj2Clicked(self): + # Define what happens when Set Position Objective 2 is clicked + self.positioner.setPositionOnDevice(axis=self.revolverAxis, value=self.posObjective2) + self.currentObjective = 2 + +class Objective(object): + '''This class stores the information of an objective lens.''' + position = 0 + name = "" + magnification = 0 + pixelsize_eff = 0 + + def __init__(self, position, name, magnification, pixelsize_eff): + self.position = position + self.name = name + self.magnification = magnification + self.pixelsize_eff = pixelsize_eff + +class ObjecitveStorer: + '''This class stores the information of all objective lenses. + We can store hold up to 2 objective lenses.''' + def __init__(self): + self.objectives = [] + self.objectives.append(Objective(0, "Objective 1", 0, 0)) + self.objectives.append(Objective(0, "Objective 2", 0, 0)) + + def setObjectiveByID(self, id, position, name, magnification, pixelsize_eff): + self.objectives[id] = Objective(position, name, magnification, pixelsize_eff) + + def getObjectiveByID(self, id): + return self.objectives[id] + +class ObjectiveLensMover(object): + + '''This class organizes the motion of the objective lens revolver.''' + maxDistance = 30000 + offsetFromZero = 1000 + speed = 25000 + incrementPosition = 50 + currentObjective = -1 + + def __init__(self, positioner, positionerName, revolverAxis, objectiveStorer): + self.positioner = positioner + self.positionerName = positionerName + self.revolverAxis = revolverAxis + self.objectiveStorer = objectiveStorer + + def calibrateObjective(self): + '''This function calibrates the objective revolver by moving to the first objective lens.''' + # Move the revolver to the most negative position and then to offsetFromZero in opposite direction e.g. homing + # TODO: This has to be reworked!! + self.positioner.move(value=-1.5*(self.offsetFromZero+self.objectiveStorer.getObjectiveByID(0).position), speed=self.speed, axis=self.revolverAxis, is_absolute=False, is_blocking=False) + self.positioner.move(value=self.offsetFromZero, speed=self.speed, axis=self.revolverAxis, is_absolute=False, is_blocking=False) + self.positioner.setPositionOnDevice(axis=self.revolverAxis, value=0) + self.currentObjective = 1 + #if self._widget.setCurrentObjectiveInfo(self.currentObjective) + + def moveToObjectiveID(self, objectiveID): + # Move the revolver to the objectiveID + mPos = self.objectiveStorer.getObjectiveByID(objectiveID).position + self.positioner.move(value=mPos, speed=self.speed, axis=self.revolverAxis, is_absolute=True, is_blocking=False) + self.currentObjective = objectiveID + +# Copyright (C) 2020-2023 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/imswitch/imcontrol/controller/controllers/ObjectiveRevolverController.py b/imswitch/imcontrol/controller/controllers/ObjectiveRevolverController.py deleted file mode 100644 index 206b547ef..000000000 --- a/imswitch/imcontrol/controller/controllers/ObjectiveRevolverController.py +++ /dev/null @@ -1,120 +0,0 @@ - -from imswitch.imcommon.model import dirtools, modulesconfigtools, ostools, APIExport -from imswitch.imcommon.framework import Signal, Worker, Mutex, Timer -from imswitch.imcontrol.view import guitools -from imswitch.imcommon.model import initLogger -from imswitch.imcontrol.controller.basecontrollers import LiveUpdatedController -from imswitch import IS_HEADLESS - -import time - -class ObjectiveRevolverController(LiveUpdatedController): - """ Linked to ObjectiveRevolverWidget.""" - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self._logger = initLogger(self, tryInheritParent=False) - - # connect camera and stage - self.positionerName = self._master.positionersManager.getAllDeviceNames()[0] - self.positioner = self._master.positionersManager[self.positionerName] - self.revolverAxis = "A" - self.posObjective1 = 0 - self.posObjective2 = 20000 - self.maxDistance = 30000 - self.offsetFromZero = 1000 - self.speed = 25000 - self.incrementPosition = 50 - - self.currentObjective = -1 - - # initialize revolver - self.calibrateObjective() - - if IS_HEADLESS: - return - - # Connect signals to slots - self._widget.btnObj1.clicked.connect(self.onObj1Clicked) - self._widget.btnObj2.clicked.connect(self.onObj2Clicked) - self._widget.btnCalibrate.clicked.connect(self.onCalibrateClicked) - self._widget.btnMovePlus.clicked.connect(self.onMovePlusClicked) - self._widget.btnMoveMinus.clicked.connect(self.onMoveMinusClicked) - self._widget.btnSetPosObj1.clicked.connect(self.onSetPosObj1Clicked) - self._widget.txtPosObj1.setText(str(self.posObjective1)) - self._widget.txtPosObj2.setText(str(self.posObjective2)) - - @APIExport(runOnUIThread=True) - def calibrateObjective(self): - # Move the revolver to the most negative position and then to offsetFromZero in opposite direction - self.positioner.move(value=-1.5*(self.offsetFromZero+self.posObjective2), speed=self.speed, axis=self.revolverAxis, is_absolute=False, is_blocking=False) - self.positioner.move(value=self.offsetFromZero, speed=self.speed, axis=self.revolverAxis, is_absolute=False, is_blocking=False) - self.positioner.setPositionOnDevice(axis=self.revolverAxis, value=0) - self.currentObjective = 1 - self._widget.setCurrentObjectiveInfo(self.currentObjective) - - @APIExport(runOnUIThread=True) - def moveToObjectiveID(self, objectiveID, posObjective1=None, posObjective2=None): - if posObjective1 is not None and not IS_HEADLESS: - try:self.posObjective1 = int(self._widget.txtPosObj1.text()) - except:pass - if posObjective2 is not None and not IS_HEADLESS: - try:self.posObjective2 = int(self._widget.txtPosObj2.text()) - except:pass - # Move the revolver to the objectiveID - if objectiveID == 1: - self.positioner.move(value=self.posObjective1, speed=self.speed, axis=self.revolverAxis, is_absolute=True, is_blocking=False) - self.currentObjective = 1 - elif objectiveID == 2: - self.positioner.move(value=self.posObjective2, speed=self.speed, axis=self.revolverAxis, is_absolute=True, is_blocking=False) - self.currentObjective = 2 - else: - self._logger.error("Objective ID not valid") - if not IS_HEADLESS: self._widget.setCurrentObjectiveInfo(self.currentObjective) - - @APIExport(runOnUIThread=True) - def getCurrentObjective(self): - return self.currentObjective, self.positioner.getPosition()[self.revolverAxis] - - def onObj1Clicked(self): - # move to objective 1 - self.moveToObjectiveID(1) - - def onObj2Clicked(self): - # move to objective 2 - self.moveToObjectiveID(2) - - def onCalibrateClicked(self): - # move to objective 1 after homing - self.calibrateObjective() - - def onMovePlusClicked(self): - # Define what happens when Move + is clicked - self.positioner.move(value=self.incrementPosition, speed=self.speed, axis=self.revolverAxis, is_absolute=False, is_blocking=False) - - def onMoveMinusClicked(self): - # Define what happens when Move - is clicked - self.positioner.move(value=-self.incrementPosition, speed=self.speed, axis=self.revolverAxis, is_absolute=False, is_blocking=False) - - def onSetPosObj1Clicked(self): - # Define what happens when Set Position Objective 1 is clicked - self.positioner.setPositionOnDevice(axis=self.revolverAxis, value=0) - self.currentObjective = 1 - - - -# Copyright (C) 2020-2023 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/imswitch/imcontrol/controller/controllers/RecordingController.py b/imswitch/imcontrol/controller/controllers/RecordingController.py index 66ca06e45..5f29ef9a0 100644 --- a/imswitch/imcontrol/controller/controllers/RecordingController.py +++ b/imswitch/imcontrol/controller/controllers/RecordingController.py @@ -479,8 +479,7 @@ def video_feeder(self, startStream: bool = True) -> StreamingResponse: self._commChannel.sigStartLiveAcquistion.emit(True) headers = { "Cache-Control": "no-cache", - "Connection": "keep-alive", - "Keep-Alive": "timeout=1, max=100" # Set timeout to 1 seconds + "Connection": "keep-alive" } return StreamingResponse(self.streamer(), media_type="multipart/x-mixed-replace;boundary=frame", headers=headers) else: diff --git a/imswitch/imcontrol/controller/controllers/__init__.py b/imswitch/imcontrol/controller/controllers/__init__.py index 77eb48c60..8c234fff6 100644 --- a/imswitch/imcontrol/controller/controllers/__init__.py +++ b/imswitch/imcontrol/controller/controllers/__init__.py @@ -12,7 +12,7 @@ from .STORMReconController import STORMReconController from .HoliSheetController import HoliSheetController from .FlowStopController import FlowStopController -from .ObjectiveRevolverController import ObjectiveRevolverController +from .ObjectiveController import ObjectiveController from .TemperatureController import TemperatureController from .SquidStageScanController import SquidStageScanController from .FocusLockController import FocusLockController diff --git a/imswitch/imcontrol/controller/server/ImSwitchServer.py b/imswitch/imcontrol/controller/server/ImSwitchServer.py index cf57b7479..ff973e468 100644 --- a/imswitch/imcontrol/controller/server/ImSwitchServer.py +++ b/imswitch/imcontrol/controller/server/ImSwitchServer.py @@ -1,8 +1,6 @@ import threading -import Pyro5 import Pyro5.server from Pyro5.api import expose -import multiprocessing from imswitch.imcommon.framework import Worker from imswitch.imcommon.model import initLogger from ._serialize import register_serializers @@ -86,8 +84,7 @@ def run(self): host="0.0.0.0", port=PORT, ssl_keyfile=os.path.join(_baseDataFilesDir, "ssl", "key.pem") if IS_SSL else None, - ssl_certfile=os.path.join(_baseDataFilesDir, "ssl", "cert.pem") if IS_SSL else None, - timeout_keep_alive=2, + ssl_certfile=os.path.join(_baseDataFilesDir, "ssl", "cert.pem") if IS_SSL else None ) self.server = uvicorn.Server(config) self.server.run() diff --git a/imswitch/imcontrol/model/__init__.py b/imswitch/imcontrol/model/__init__.py index f0c513f75..dc4fd4903 100644 --- a/imswitch/imcontrol/model/__init__.py +++ b/imswitch/imcontrol/model/__init__.py @@ -5,5 +5,5 @@ from .signaldesigners import SignalDesignerFactory import sys -sys.modules['visa'] = 'pyvisa' +#sys.modules['visa'] = 'pyvisa' diff --git a/imswitch/imcontrol/model/managers/positioners/VirtualStageManager.py b/imswitch/imcontrol/model/managers/positioners/VirtualStageManager.py index 27a348af8..87c137c9c 100644 --- a/imswitch/imcontrol/model/managers/positioners/VirtualStageManager.py +++ b/imswitch/imcontrol/model/managers/positioners/VirtualStageManager.py @@ -37,7 +37,22 @@ def move(self, value=0, axis="X", is_absolute=False, is_blocking=True, accelerat self._position[axes] = self._positioner.position[axes] self._commChannel.sigUpdateMotorPosition.emit() # TODO: This is a hacky workaround to force Imswitch to update the motor positions in the gui.. - + def setPositionOnDevice(self, axis, value): + if axis == "X": + self._positioner.move(x=value, is_absolute=True) + if axis == "Y": + self._positioner.move(y=value, is_absolute=True) + if axis == "Z": + self._positioner.move(z=value, is_absolute=True) + if axis == "A": + self._positioner.move(a=value, is_absolute=True) + if axis == "XYZ": + self._positioner.move(x=value[0], y=value[1], z=value[2], is_absolute=True) + if axis == "XY": + self._positioner.move(x=value[0], y=value[1], is_absolute=True) + for axes in ["A","X","Y","Z"]: + self._position[axes] = self._positioner.position[axes] + self._commChannel.sigUpdateMotorPosition.emit() def moveForever(self, speed=(0, 0, 0, 0), is_stop=False): pass diff --git a/imswitch/imcontrol/view/ImConMainView.py b/imswitch/imcontrol/view/ImConMainView.py index a5305502b..054130252 100644 --- a/imswitch/imcontrol/view/ImConMainView.py +++ b/imswitch/imcontrol/view/ImConMainView.py @@ -113,7 +113,7 @@ def __init__(self, options, viewSetupInfo, *args, **kwargs): 'STORMRecon': _DockInfo(name='STORM Recon Tool', yPosition=2), 'HoliSheet': _DockInfo(name='HoliSheet Tool', yPosition=3), 'FlowStop': _DockInfo(name='FlowStop Tool', yPosition=3), - 'ObjectiveRevolver': _DockInfo(name='Objective Revolver', yPosition=3), + 'Objective': _DockInfo(name='Objective Revolver', yPosition=3), 'Temperature': _DockInfo(name='Temperature Controller', yPosition=3), 'SquidStageScan': _DockInfo(name='SquidStageScan Tool', yPosition=3), 'WellPlate': _DockInfo(name='Wellplate Tool', yPosition=1), @@ -266,71 +266,11 @@ def __init__(self, options, viewSetupInfo, *args, **kwargs): self.viewSetupInfo = viewSetupInfo # Dock area - allDockKeys = { - 'Autofocus': _DockInfo(name='Autofocus', yPosition=1), - 'FocusLock': _DockInfo(name='Focus Lock', yPosition=0), - 'FOVLock': _DockInfo(name='FOV Lock', yPosition=0), - 'SLM': _DockInfo(name='SLM', yPosition=0), - 'UC2Config': _DockInfo(name='UC2Config', yPosition=0), - 'SIM': _DockInfo(name='SIM', yPosition=0), - 'DPC': _DockInfo(name='DPC', yPosition=0), - 'MCT': _DockInfo(name='MCT', yPosition=0), - 'ROIScan': _DockInfo(name='ROIScan', yPosition=0), - 'Lightsheet': _DockInfo(name='Lightsheet', yPosition=0), - 'WebRTC': _DockInfo(name='WebRTC', yPosition=0), - 'Hypha': _DockInfo(name='Hypha', yPosition=0), - 'MockXX': _DockInfo(name='MockXX', yPosition=0), - 'JetsonNano': _DockInfo(name='JetsonNano', yPosition=0), - 'HistoScan': _DockInfo(name='HistoScan', yPosition=1), - 'Flatfield': _DockInfo(name='Flatfield', yPosition=1), - 'PixelCalibration': _DockInfo(name='PixelCalibration', yPosition=1), - 'ISM': _DockInfo(name='ISM', yPosition=0), - 'Laser': _DockInfo(name='Laser Control', yPosition=0), - 'LED': _DockInfo(name='LED Control', yPosition=0), - 'EtSTED': _DockInfo(name='EtSTED', yPosition=0), - 'Positioner': _DockInfo(name='Positioner', yPosition=1), - 'Rotator': _DockInfo(name='Rotator', yPosition=1), - 'MotCorr': _DockInfo(name='Motorized Correction Collar', yPosition=1), - 'StandaPositioner': _DockInfo(name='StandaPositioner', yPosition=1), - 'StandaStage': _DockInfo(name='StandaStage', yPosition=1), - 'SLM': _DockInfo(name='SLM', yPosition=2), - 'Scan': _DockInfo(name='Scan', yPosition=2), - 'RotationScan': _DockInfo(name='RotationScan', yPosition=2), - 'BeadRec': _DockInfo(name='Bead Rec', yPosition=3), - 'AlignmentLine': _DockInfo(name='Alignment Tool', yPosition=3), - 'AlignAverage': _DockInfo(name='Axial Alignment Tool', yPosition=3), - 'AlignXY': _DockInfo(name='Rotational Alignment Tool', yPosition=3), - 'ULenses': _DockInfo(name='uLenses Tool', yPosition=3), - 'FFT': _DockInfo(name='FFT Tool', yPosition=3), - 'Holo': _DockInfo(name='Holo Tool', yPosition=3), - 'Joystick': _DockInfo(name='Joystick Tool', yPosition=3), - 'Histogramm': _DockInfo(name='Histogramm Tool', yPosition=3), - 'STORMRecon': _DockInfo(name='STORM Recon Tool', yPosition=2), - 'HoliSheet': _DockInfo(name='HoliSheet Tool', yPosition=3), - 'FlowStop': _DockInfo(name='FlowStop Tool', yPosition=3), - 'FLIMLabs': _DockInfo(name='FLIMLabs Tool', yPosition=3), - 'ObjectiveRevolver': _DockInfo(name='Objective Revolver', yPosition=3), - 'Temperature': _DockInfo(name='Temperature Controller', yPosition=3), - 'SquidStageScan': _DockInfo(name='SquidStageScan Tool', yPosition=3), - 'WellPlate': _DockInfo(name='Wellplate Tool', yPosition=1), - 'Deck': _DockInfo(name="Deck Tool", yPosition=1), - 'DeckScan': _DockInfo(name="Deck Scanner", yPosition=1), - 'LEDMatrix': _DockInfo(name='LEDMatrix Tool', yPosition=0), - 'Watcher': _DockInfo(name='File Watcher', yPosition=3), - 'Tiling': _DockInfo(name='Tiling', yPosition=3), - 'Settings': _DockInfo(name='Detector Settings', yPosition=0), - 'View': _DockInfo(name='Image Controls', yPosition=1), - 'Recording': _DockInfo(name='Recording', yPosition=2), - 'Console': _DockInfo(name='Console', yPosition=3) - } + # add widgets that are enabled but not in allDockKeys enabledDockKeys = self.viewSetupInfo.availableWidgets - if enabledDockKeys is False: - enabledDockKeys = [] - elif enabledDockKeys is True: - enabledDockKeys = allDockKeys - self._addWidget( - {k: v for k, v in allDockKeys.items() if k in enabledDockKeys} - ) + disabledKeys = ["Image"] + widget_keys = {key: _DockInfo(name=key, yPosition=1) for key in enabledDockKeys if key not in disabledKeys} + self._addWidget(widget_keys) def closeEvent(self, event): diff --git a/imswitch/imcontrol/view/widgets/ObjectiveRevolverWidget.py b/imswitch/imcontrol/view/widgets/ObjectiveWidget.py similarity index 95% rename from imswitch/imcontrol/view/widgets/ObjectiveRevolverWidget.py rename to imswitch/imcontrol/view/widgets/ObjectiveWidget.py index 83f78e42b..49effa3b0 100644 --- a/imswitch/imcontrol/view/widgets/ObjectiveRevolverWidget.py +++ b/imswitch/imcontrol/view/widgets/ObjectiveWidget.py @@ -9,8 +9,8 @@ -class ObjectiveRevolverWidget(NapariHybridWidget): - """ Displays the ObjectiveRevolver transform of the image. """ +class ObjectiveWidget(NapariHybridWidget): + """ Displays the Objective transform of the image. """ def __post_init__(self): # Create widgets diff --git a/imswitch/imcontrol/view/widgets/__init__.py b/imswitch/imcontrol/view/widgets/__init__.py index 888be5561..f2e3ab016 100644 --- a/imswitch/imcontrol/view/widgets/__init__.py +++ b/imswitch/imcontrol/view/widgets/__init__.py @@ -18,7 +18,7 @@ from .HoliSheetWidget import HoliSheetWidget from .FlowStopWidget import FlowStopWidget - from .ObjectiveRevolverWidget import ObjectiveRevolverWidget + from .ObjectiveWidget import ObjectiveWidget from .TemperatureWidget import TemperatureWidget from .LEDMatrixWidget import LEDMatrixWidget from .WellPlateWidget import WellPlateWidget diff --git a/setup.py b/setup.py index e0fe3cc3b..66cc202de 100644 --- a/setup.py +++ b/setup.py @@ -76,6 +76,7 @@ def get_version(): "pymba==0.3.7", "ashlarUC2", "imjoy-rpc==0.5.59", + "imswitchclient=>0.1.2", "psygnal @ git+https://github.com/pyapp-kit/psygnal.git@v0.11.1#egg=psygnal" # ensure we have --no-binary ],