"""The first module used at the mia runtime.
Basically, this module is dedicated to the initialisation of the basic
parameters and the various checks necessary for a successful launch of the
mia's GUI.
:Contains:
:Function:
- add_to_sys_path
- check_package
- main
- qt_message_handler
"""
###############################################################################
# Populse_mia - Copyright (C) IRMaGe/CEA, 2018
# Distributed under the terms of the CeCILL license, as published by
# the CEA-CNRS-INRIA. Refer to the LICENSE file or to
# http://www.cecill.info/licences/Licence_CeCILL_V2.1-en.html
# for details.
###############################################################################
import importlib
import logging
import os
import sys
import tempfile
from pathlib import Path
try:
# Running inside the package (installed or `python -m populse_mia.main`)
from .cli_args import parse_args
except ImportError:
# Running as a top-level script (python main.py)
from cli_args import parse_args
# PyQt5 imports
from PyQt5.QtCore import QCoreApplication, Qt, qInstallMessageHandler
from PyQt5.QtWidgets import QApplication, QMessageBox
logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
logger = logging.getLogger(__name__)
[docs]
def add_to_sys_path(path, name, index=0):
"""
Adds the specified path to the system path if it's a valid directory.
:param path (pathlib.Path): The directory path to be added to the
system path.
:param name (str): The name of the package being added.
:param index (int, optional): The index at which to insert the path
into sys.path. Defaults to 0.
:return (bool): True if the path is a valid directory and was added
to sys.path, False otherwise.
"""
if path.is_dir():
sys.path.insert(index, str(path))
logger.info(f" . Using {name} package from {path}")
return True
else:
logger.info(f" {name} package was not found from {path}!")
return False
[docs]
def check_package(name):
"""
Attempts to import a package by its name, logs the location of the
package if successful, and logs an error if the package is missing.
:param name (str): The name of the package to be imported.
:return (bool): True if the package is imported successfully;
False if the package is missing.
"""
try:
mod = importlib.import_module(name)
mod_dir = Path(mod.__file__).resolve().parents[1]
logger.info(f" . Using {name} package from {mod_dir}")
return True
except ImportError:
logger.error(f"Failed to import {name} package!")
return None
except AttributeError:
logger.warning(f"{name} package has no __file__ attribute!")
return True
[docs]
def main(args):
"""Make basic configuration check, then actual launch of Mia.
Checks if Mia is called from the site/dist packages (`user mode`) or from a
cloned git repository (`developer mode`).
~/.populse_mia/configuration_path.yml is mandatory, if it doesn't exist
or is corrupted, try to create one with a valid properties path.
- If launched from a cloned git repository (`developer mode`):
- the properties_path is the "properties_dev_path" parameter in
~/.populse_mia/configuration_path.yml
- If launched from the site/dist packages (`user mode`):
- the properties_path is the "properties_user_path" parameter in
~/.populse_mia/configuration_path.yml
Launches the verify_processes() function, then the launch_mia() function
(Mia's real launch).
"""
pypath = []
package_not_found = []
# Disables any etelemetry check.
os.environ.setdefault("NO_ET", "1")
os.environ.setdefault("NIPYPE_NO_ET", "1")
# Trying to fix High DPI Display issue
os.environ["QT_AUTO_SCREEN_SCALE_FACTOR"] = "1"
# General QApplication class instantiation
QCoreApplication.setAttribute(Qt.AA_ShareOpenGLContexts)
app = QApplication(sys.argv) # noqa: F841
QApplication.setOverrideCursor(Qt.WaitCursor)
# Adding the populse projects path to sys.path, if in developer mode
if str(Path(__file__).resolve().parents[1]) not in sys.path:
# "developer" mode
DEV_MODE = True
os.environ["MIA_DEV_MODE"] = "1"
# Determine the root development directory
root_dev_dir = Path(__file__).resolve().parents[2]
branch = ""
populse_bdir = ""
capsul_bdir = ""
soma_bdir = ""
if not (root_dev_dir / "populse_mia").is_dir():
# Different sources layout - try casa_distro mode
# TODO: At the moment, my casa_distro isn't working, and I don't
# understand this part. I'll check later when I've got
# casa_distro back.
# TODO start
root_dev_dir = os.path.dirname(
os.path.dirname(
os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
)
)
if os.path.basename(root_dev_dir) == "populse":
root_dev_dir = os.path.dirname(root_dev_dir)
populse_bdir = "populse"
soma_bdir = "soma"
logger.info(f"root_dev_dir: {root_dev_dir}")
branch = os.path.basename(
os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
)
logger.info(f"branch: {branch}")
# TODO stop
# populse packages
packages = [
{
"populse_mia": (
root_dev_dir / populse_bdir / "populse_mia" / branch
)
},
{
"populse_db": (
root_dev_dir
/ populse_bdir
/ "populse_db"
/ branch
/ "python"
)
},
{
"soma": (
root_dev_dir / soma_bdir / "soma-base" / branch / "python"
)
},
{"capsul": (root_dev_dir / capsul_bdir / "capsul" / branch)},
{
"soma_workflow": (
root_dev_dir
/ soma_bdir
/ "soma-workflow"
/ branch
/ "python"
)
},
{
"mia_processes": (
root_dev_dir / populse_bdir / "mia_processes" / branch
)
},
]
# Adding populse packages in sys.path
logger.info("- Mia in 'developer' mode")
for i, package in enumerate(packages):
for name, dev_path in package.items():
if add_to_sys_path(dev_path, name, i):
pypath.append(dev_path)
i += 1
elif not check_package(name):
package_not_found.append(name)
try:
importlib.import_module(name)
if sys.modules[name].__version__ is None:
raise AttributeError
logger.info(
f" {name} version: "
f"{sys.modules[name].__version__}"
)
except (ModuleNotFoundError, AttributeError):
# Version is not found.
# Try to find version in a version submodule
try:
importlib.import_module(f"{name}.version")
logger.info(
f" {name} version: "
f"{sys.modules[name + '.version'].fullVersion}"
)
except (ModuleNotFoundError, AttributeError):
# Version is not found.
# Try to find version in pyproject.toml
try:
path_value = next(iter(package.values()))
pyproject_toml_path = (
path_value.parent / "pyproject.toml"
)
with open(pyproject_toml_path, "rb") as f:
import tomli
pyproject = tomli.load(f)
if pyproject["project"]["name"].replace(
"-", "_"
) == name.replace("-", "_"):
version = pyproject["project"]["version"]
logger.info(
f" {name} version: {version}"
)
else:
raise AttributeError
except Exception as e:
# Version is not found.
logger.warning(
f"Version was not found for {name} package!: "
f"{e}",
exc_info=True,
)
if package_not_found:
error_msg = "\n".join(f"- {pkg}" for pkg in package_not_found)
QMessageBox.warning(
None,
"populse_mia - ImportError",
f"Missing packages:\n{error_msg}\nPlease reinstall them.",
)
sys.exit(1)
# TODO: Same as the first todo above, I don't understand the lines below,
# so I'll comment on them for now.
# elif "CASA_DISTRO" in os.environ:
# # If the casa distro development environment is detected,
# # developer mode is activated.
# os.environ["MIA_DEV_MODE"] = "1"
# DEV_MODE = True
else: # "user" mode
os.environ["MIA_DEV_MODE"] = "0"
DEV_MODE = False
logger.info("- Mia in 'user' mode")
modules = (
"populse_mia",
"capsul",
"soma",
"soma_workflow",
"populse_db",
"mia_processes",
)
for module in modules:
if module in sys.modules:
mod = sys.modules[module]
else:
mod = importlib.import_module(module)
logger.info(
f" . Using {mod.__name__} package "
f"from {mod.__path__[0]} ..."
)
try:
logger.info(
f" {mod.__name__} version: "
f"{sys.modules[module].__version__}"
)
except (ModuleNotFoundError, AttributeError):
# Version is not found.
# Try to find version in a version submodule
try:
importlib.import_module(f"{module}.version")
logger.info(
f" {module} version: "
f"{sys.modules[module + '.version'].fullVersion}"
)
except Exception as e:
# Version is not found.
logger.warning(
f"Version was not found for {name} package!: " f"{e}",
exc_info=True,
)
# Check if nipype is available on the station.
# If not available ask the user to install it.
try:
importlib.import_module("nipype")
except (ImportError, AttributeError) as e:
logger.error(f"MIA warning {e.__class__}: {e}")
msg = QMessageBox()
msg.setIcon(QMessageBox.Warning)
msg.setWindowTitle("populse_mia - warning: ImportError!")
msg.setText(
"An issue has been detected with "
"the nipype package. Please (re)install this "
"package and/or fix the issues displayed in the standard "
"output. Then, start again Mia ..."
)
msg.setStandardButtons(QMessageBox.Ok)
msg.buttonClicked.connect(msg.close)
msg.exec()
sys.exit(1)
# Now that populse projects paths have been set in sys.path, if necessary,
# we can import from these projects:
# Populse_mia imports
from populse_mia.utils import ( # noqa E402
check_python_version,
launch_mia,
verify_processes,
verify_setup,
)
verify_setup(dev_mode=DEV_MODE, pypath=list(map(str, pypath)))
verify_processes(
sys.modules["nipype"].__version__,
sys.modules["mia_processes"].__version__,
sys.modules["capsul"].__version__,
)
check_python_version()
cwd = os.getcwd()
with tempfile.TemporaryDirectory() as temp_work_dir:
os.chdir(temp_work_dir)
launch_mia(args)
os.chdir(cwd)
[docs]
def qt_message_handler(msg_type, context, message):
"""
Custom Qt message handler to filter out specific unwanted messages
and print the remaining ones to stderr.
:param msg_type (QtMsgType): The type of Qt message (debug, warning,
critical, etc.).
:param context (QMessageLogContext): Context information about where the
message originated.
:param message (str): The log message string.
"""
for unwanted_message in unwanted_messages:
if message.strip() == unwanted_message:
return
if unwanted_message in message:
# Remove the unwanted message but keep the rest of the line
message = message.replace(unwanted_message, "").strip()
# Print if anything remains
if message:
sys.stderr.write(message + "\n")
if __name__ == "__main__":
args = parse_args()
# Print the multi_instance argument value
logger.info(f"--multi_instance is set to: {args.multi_instance}")
# This will only be executed when this module is run directly
# list of unwanted messages to filter out in stdout
unwanted_messages = [
"QPixmap::scaleHeight: Pixmap is a null pixmap",
]
# Install the custom Qt message handler
qInstallMessageHandler(qt_message_handler)
main(args)