Skip to content

Commit

Permalink
removed pyqt, better docstrings for some stuff in mesmerize_core.utils
Browse files Browse the repository at this point in the history
  • Loading branch information
kushalkolar committed Jul 21, 2022
1 parent 6ba1229 commit eb4640f
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 264 deletions.
11 changes: 0 additions & 11 deletions mesmerize_core/batch_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,11 @@
"cnmfe": cnmfe,
}

try:
import PyQt5

HAS_PYQT = True
except ImportError:
HAS_PYQT = False

COMPUTE_BACKEND_QPROCESS = "qprocess" #: QProcess backend for use in napari
COMPUTE_BACKEND_SUBPROCESS = "subprocess" #: subprocess backend, for use output a Qt application such as a notebook
COMPUTE_BACKEND_SLURM = "slurm" #: SLURM backend, not yet implemented

COMPUTE_BACKENDS = [COMPUTE_BACKEND_SUBPROCESS, COMPUTE_BACKEND_SLURM]

if HAS_PYQT:
COMPUTE_BACKENDS += [COMPUTE_BACKEND_QPROCESS]

DATAFRAME_COLUMNS = ["algo", "name", "input_movie_path", "params", "outputs", "uuid"]


Expand Down
40 changes: 1 addition & 39 deletions mesmerize_core/caiman_extensions/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,6 @@
HAS_PYQT,
)
from ..utils import validate_path, IS_WINDOWS, make_runfile, warning_experimental

if HAS_PYQT:
from PyQt5 import QtCore
from caiman import load_memmap


Expand Down Expand Up @@ -137,42 +134,7 @@ class CaimanSeriesExtensions:

def __init__(self, s: pd.Series):
self._series = s
self.process: [Union, QtCore.QProcess, Popen] = None

def _run_qprocess(
self,
runfile_path: str,
callbacks_finished: List[callable],
callback_std_out: Optional[callable] = None,
) -> QtCore.QProcess:

# Create a QProcess
self.process = QtCore.QProcess()
self.process.setProcessChannelMode(QtCore.QProcess.MergedChannels)

# Set the callback function to read the stdout
if callback_std_out is not None:
self.process.readyReadStandardOutput.connect(
partial(callback_std_out, self.process)
)

# connect the callback functions for when the process finishes
if callbacks_finished is not None:
for f in callbacks_finished:
self.process.finished.connect(f)

parent_path = self._series.paths.resolve(self._series.input_movie_path).parent

# Set working dir for the external process
self.process.setWorkingDirectory(str(parent_path))

# Start the external process
if IS_WINDOWS:
self.process.start("powershell.exe", [runfile_path])
else:
self.process.start(runfile_path)

return self.process
self.process: Popen = None

def _run_subprocess(
self,
Expand Down
257 changes: 46 additions & 211 deletions mesmerize_core/utils.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
from qtpy.QtWidgets import QWidget, QFileDialog, QMessageBox
from qtpy import QtGui
"""
Useful functions adapted from old mesmerize
GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007
"""


import numpy as np
from matplotlib import cm as matplotlib_color_map
from functools import wraps
import os
from stat import S_IEXEC
import traceback
from typing import *
import re as regex
from pathlib import Path
from warnings import warn


# Useful functions adapted from mesmerize


# to use powershell to run the CNMF process using QProcess
# napari's built in @thread_worker locks up the entire application
if os.name == "nt":
IS_WINDOWS = True
HOME = "USERPROFILE"
Expand Down Expand Up @@ -75,200 +74,43 @@ def validate_path(path: Union[str, Path]):
return path


def use_open_file_dialog(
title: str = "Choose file",
start_dir: Union[str, None] = None,
exts: List[str] = None,
):
"""
Use to pass a file path, for opening, into the decorated function using QFileDialog.getOpenFileName
:param title: Title of the dialog box
:param start_dir: Directory that is first shown in the dialog box.
:param exts: List of file extensions to set the filter in the dialog box
"""

def wrapper(func):
@wraps(func)
def fn(self, *args, **kwargs):
if "qdialog" in kwargs.keys():
if not kwargs["qdialog"]:
func(self, *args, **kwargs)
return fn

if exts is None:
e = []
else:
e = exts

if isinstance(self, QWidget):
parent = self
else:
parent = None

path = QFileDialog.getOpenFileName(
parent, title, os.environ["HOME"], f'({" ".join(e)})'
)
if not path[0]:
return
path = path[0]
func(self, path, *args, **kwargs)

return fn

return wrapper


def use_save_file_dialog(
title: str = "Save file", start_dir: Union[str, None] = None, ext: str = None
):
"""
Use to pass a file path, for saving, into the decorated function using QFileDialog.getSaveFileName
:param title: Title of the dialog box
:param start_dir: Directory that is first shown in the dialog box.
:param exts: List of file extensions to set the filter in the dialog box
"""

def wrapper(func):
@wraps(func)
def fn(self, *args, **kwargs):
if ext is None:
raise ValueError("Must specify extension")
if ext.startswith("*"):
ex = ext[1:]
else:
ex = ext

if isinstance(self, QWidget):
parent = self
else:
parent = None

path = QFileDialog.getSaveFileName(parent, title, start_dir, f"(*{ex})")
if not path[0]:
return
path = path[0]
if not path.endswith(ex):
path = f"{path}{ex}"

path = validate_path(path)

func(self, path, *args, **kwargs)

return fn

return wrapper


def use_open_dir_dialog(
title: str = "Open directory", start_dir: Union[str, None] = None
):
"""
Use to pass a dir path, to open, into the decorated function using QFileDialog.getExistingDirectory
:param title: Title of the dialog box
:param start_dir: Directory that is first shown in the dialog box.
Example:
.. code-block:: python
@use_open_dir_dialog('Select Project Directory', '')
def load_data(self, path, *args, **kwargs):
my_func_to_do_stuff_and_load_data(path)
"""

def wrapper(func):
@wraps(func)
def fn(self, *args, **kwargs):
if isinstance(self, QWidget):
parent = self
else:
parent = None

path = QFileDialog.getExistingDirectory(parent, title)
if not path:
return
func(self, path, *args, **kwargs)

return fn

return wrapper


def present_exceptions(
title: str = "error", msg: str = "The following error occurred."
):
"""
Use to catch exceptions and present them to the user in a QMessageBox warning dialog.
The traceback from the exception is also shown.
This decorator can be stacked on top of other decorators.
Example:
.. code-block: python
@present_exceptions('Error loading file')
@use_open_file_dialog('Choose file')
def select_file(self, path: str, *args):
pass
:param title: Title of the dialog box
:param msg: Message to display above the traceback in the dialog box
:param help_func: A helper function which is called if the user clicked the "Help" button
"""

def catcher(func):
@wraps(func)
def fn(self, *args, **kwargs):
try:
return func(self, *args, **kwargs)
except Exception as e:
tb = traceback.format_exc()

mb = QMessageBox()
mb.setIcon(QMessageBox.Warning)
mb.setWindowTitle(title)
mb.setText(msg)
mb.setInformativeText(f"{e.__class__.__name__}: {e}")
mb.setDetailedText(tb)
mb.setStandardButtons(QMessageBox.Ok | QMessageBox.Help)

# getLogger().info(
# f"{e.__class__.__name__}: {e}\n"
# f"{traceback.format_exc()}"
# )

return fn

return catcher


def auto_colormap(
n_colors: int,
cmap: str = "hsv",
output: str = "mpl",
output: Union[str, type] = "float",
spacing: str = "uniform",
alpha: float = 1.0,
) -> List[Union[QtGui.QColor, np.ndarray, str]]:
) -> List[Union[np.ndarray, str]]:
"""
If non-qualitative map: returns list of colors evenly spread through the chosen colormap.
If qualitative map: returns subsequent colors from the chosen colormap
:param n_colors: Numbers of colors to return
:param cmap: name of colormap
Parameters
----------
n_colors: int
Numbers of colors to return
cmap: str
name of colormap
:param output: option: 'mpl' returns RGBA values between 0-1 which matplotlib likes,
option: 'bokeh' returns hex strings that correspond to the RGBA values which bokeh likes
output: Union[str, type]
option: "float" or ``float`` returns RGBA values between 0-1: [R, G, B, A],
option: "hex" returns hex strings that correspond to the RGBA values
:param spacing: option: 'uniform' returns evenly spaced colors across the entire cmap range
option: 'subsequent' returns subsequent colors from the cmap
spacing: str
option: "uniform"'" returns evenly spaced colors across the entire cmap range
option: "subsequent" returns subsequent colors from the cmap
:param alpha: alpha level, 0.0 - 1.0
alpha: float
alpha level, 0.0 - 1.0
:return: List of colors as either ``QColor``, ``numpy.ndarray``, or hex ``str`` with length ``n_colors``
Returns
-------
List[Union[np.ndarray, str]]
List of colors as either ``numpy.ndarray``, or hex ``str`` with length ``n_colors``
"""

valid = ["mpl", "pyqt", "bokeh"]
valid = ["float", float, "hex"]
if output not in valid:
raise ValueError(f"output must be one {valid}")

Expand All @@ -282,10 +124,7 @@ def auto_colormap(
cm = matplotlib_color_map.get_cmap(cmap)
cm._init()

if output == "pyqt":
lut = (cm._lut * 255).view(np.ndarray)
else:
lut = (cm._lut).view(np.ndarray)
lut = (cm._lut).view(np.ndarray)

lut[:, 3] *= alpha

Expand All @@ -306,12 +145,12 @@ def auto_colormap(
for ix in range(n_colors):
c = lut[cm_ixs[ix]]

if output == "bokeh":
if output == "hex":
c = tuple(c[:3] * 255)
hc = "#%02x%02x%02x" % tuple(map(int, c))
colors.append(hc)

else: # mpl
else: # floats
colors.append(c)

return colors
Expand All @@ -321,28 +160,24 @@ def make_runfile(
module_path: str, args_str: Optional[str] = None, filename: Optional[str] = None
) -> str:
"""
Make an executable bash script. Used for running python scripts in external processes.
:param module_path: absolute module path
:type module_path: str
:param args_str: str of args that is directly passed with the python command in the bash script
:type args_str: str
:param savedir: working directory
:type savedir: Optional[str]
Make an executable bash script.
Used for running python scripts in external processes within the same python environment as the main/parent process.
:param filename: optional, specific filename for the script
:type filename: Optional[str]
Parameters
----------
module_path: str
absolute path to the python module/script that should be run externally
:param pre_run: optional, str to run before module is ran
:type pre_run: Optional[str]
args_str: Optional[str]
optinal str of args that is directly passed to the script specified by ``module_path``
:param post_run: optional, str to run after module has run
:type post_run: Optional[str]
filename: Optional[str]
optional, filename of the executable bash script
:return: path to the shell script that can be run
:rtype: str
Returns
-------
str
path to the shell script that can be executed
"""

if filename is None:
Expand Down
Loading

0 comments on commit eb4640f

Please sign in to comment.