'''
mesoSPIM_control.py
========================================
The core module of the mesoSPIM software
'''
__authors__ = "mesoSPIM team"
__license__ = "GPL v3"
__version__ = "1.11.1"
import time
import logging
import argparse
import glob
import os
import sys
import importlib.util
from PyQt5 import QtWidgets, QtCore
import qdarkstyle
package_directory = os.path.dirname(os.path.abspath(__file__))
sys.path.append(os.path.dirname(package_directory)) # this is critical for 'from mesoSPIM.src.mesoSPIM_MainWindow import mesoSPIM_MainWindow' to work in both script and package form.
from mesoSPIM.src.mesoSPIM_MainWindow import mesoSPIM_MainWindow
[docs]
def load_config_UI(current_path):
'''
Bring up a GUI that allows the user to select a microscope configuration to import
'''
cfg_app = QtWidgets.QApplication(sys.argv)
current_path = os.path.abspath('./config')
global_config_path = ''
global_config_path , _ = QtWidgets.QFileDialog.getOpenFileName(None,
'Open microscope configuration file',current_path)
if global_config_path != '':
config = load_config_from_file(global_config_path)
return config, global_config_path
else:
''' Application shutdown '''
warning = QtWidgets.QMessageBox.warning(None, 'Shutdown warning',
'No configuration file selected - shutting down!',
QtWidgets.QMessageBox.Ok)
sys.exit()
[docs]
def load_config_from_file(path_to_config):
'''
Load a microscope configuration from a file using importlib
'''
spec = importlib.util.spec_from_file_location('module.name', path_to_config)
config = importlib.util.module_from_spec(spec)
spec.loader.exec_module(config)
print(f'Configuration file loaded: {path_to_config}')
return config
[docs]
def stage_referencing_check(cfg):
'''
Due to problems with some PI stages loosing reference information
after restarting the mesoSPIM software, some stage configurations require
a reference movement to be carried out before starting the rest of the software.
As reference movements can damage the instrument, this function warns users
about this problem by message boxes and asks them to reach a safe state.
'''
if cfg.stage_parameters['stage_type'] == 'PI_rotzf_and_Galil_xy' or cfg.stage_parameters['stage_type'] == 'PI_rotz_and_Galil_xyf':
warning = QtWidgets.QMessageBox.warning(None,'Sample z reference movement necessary!',
'Please move the XYZ stage to position where a reference z movement (to the midpoint of the movement range) is safe!',
QtWidgets.QMessageBox.Cancel | QtWidgets.QMessageBox.Ok)
if warning == QtWidgets.QMessageBox.Cancel:
shutdown_message = QtWidgets.QMessageBox.warning(None,'Shutdown warning',
'No reference movement - shutting down!',
QtWidgets.QMessageBox.Ok)
sys.exit()
else:
pass
else:
pass
[docs]
def get_parser():
"""
Parse command-line input arguments
:return: The argparse parser object
"""
parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument('-C', '--console', action='store_true', # store_true makes it False by default
help='Start a ipython console')
parser.add_argument('-D', '--demo', action='store_true',
help='Start in demo mode')
return parser
[docs]
def dark_mode_check(cfg, app):
if (hasattr(cfg, 'dark_mode') and cfg.dark_mode) or (hasattr(cfg, 'ui_options') and cfg.ui_options['dark_mode']):
app.setStyleSheet(qdarkstyle.load_stylesheet(qt_api='pyqt5'))
[docs]
def get_logger(cfg, package_directory):
if hasattr(cfg, 'logging_level') and cfg.logging_level in ('DEBUG', 'INFO'):
LOGGING_LEVEL = cfg.logging_level
else:
LOGGING_LEVEL = 'INFO'
print(f"Config file has missing parameter 'logging_level' ('INFO', 'DEBUG'). Setting to 'INFO' value.")
timestr = time.strftime("%Y%m%d-%H%M%S")
logging_filename = os.path.join(package_directory, 'log', timestr + '.log')
logging.basicConfig(filename=logging_filename, level=LOGGING_LEVEL,
format='%(asctime)-8s:%(levelname)s:%(threadName)s:%(thread)d:%(module)s:%(funcName)s:%(message)s')
logger = logging.getLogger(__name__)
return logger
[docs]
def main(embed_console=False, demo_mode=False):
"""
Load a configuration file according to the following rules:
1. If the user asked for demo mode, load the `demo_config.py` file
2. Else, ff the user did not ask for demo mode:
- if there is only one non-demo config file, load that.
- if there are multiple config files, bring up the UI loader.
"""
print('Starting control software')
QtCore.QThread.currentThread().setObjectName('MainThread')
demo_fname = os.path.join(package_directory, 'config', 'demo_config.py')
if not os.path.exists(demo_fname):
raise ValueError(f"Demo file not found: {demo_fname}")
if demo_mode:
config_fname = demo_fname
cfg = load_config_from_file(config_fname)
else:
all_configs = glob.glob(os.path.join(package_directory, 'config', '*.py')) # All possible config files
all_configs_no_demo = list(filter(lambda f: str.find(f, 'demo_') < 0, all_configs))
if len(all_configs_no_demo) == 0:
config_fname = demo_fname
cfg = load_config_from_file(config_fname)
elif len(all_configs_no_demo) == 1:
config_fname = os.path.join(package_directory, all_configs_no_demo[0])
cfg = load_config_from_file(config_fname)
else:
cfg, config_fname = load_config_UI(os.path.join(package_directory, 'config'))
logger = get_logger(cfg, package_directory)
logger.info(f'Config file loaded: {config_fname}')
logger.info(f'mesoSPIM-control version: {__version__}')
app = QtWidgets.QApplication(sys.argv)
dark_mode_check(cfg, app)
stage_referencing_check(cfg)
ex = mesoSPIM_MainWindow(package_directory, cfg, "mesoSPIM Main Window, v. " + __version__)
ex.show()
# hook up the log display widget: discontinued
#logging.getLogger().addHandler(ex.log_display_handler)
if embed_console:
from traitlets.config import Config
cfg = Config()
cfg.InteractiveShellApp.gui = 'qt5'
import IPython
IPython.start_ipython(config=cfg, argv=[], user_ns=dict(mSpim=ex, app=app))
else:
sys.exit(app.exec_())
[docs]
def run():
args = get_parser().parse_args()
main(embed_console=args.console, demo_mode=args.demo)
if __name__ == '__main__':
run()