# coding:utf-8
from typing import Optional, NoReturn, Any
from importlib import import_module
import webbrowser
from pkg_resources import resource_string, resource_filename
from numpy import frombuffer, uint8
# Quick access to documentation
[docs]
def docs():
"""Opens the online documentation of Crappy.
It opens the latest version, and of course requires an internet access.
.. versionadded:: 1.5.5
.. versionchanged:: 2.0.0 renamed from *doc* to *docs*
"""
webbrowser.open('https://crappy.readthedocs.io/en/latest/')
[docs]
class OptionalModule:
"""Placeholder for optional dependencies that are not installed.
Will display a message and raise an error when trying to use them.
.. versionadded:: 1.4.0
"""
[docs]
def __init__(self,
module_name: str,
message: Optional[str] = None,
lazy_import: bool = False) -> None:
"""Sets the arguments.
Args:
module_name: The name of the module s a :obj:`str`, preferably the one
invoked with `pip install`.
message: Optionally, the message to display in case the module is
missing (as a :obj:`str`). If not provided, a generic message will be
displayed.
lazy_import: If :obj:`True`, the module won't be imported directly even
if it is installed. Instead, it will be imported only when necessary.
Allows reducing the import time, especially on Window.
.. versionadded:: 2.0.0
"""
self._name = module_name
# Setting a default message if none was provided
if message is not None:
self._msg = message
else:
self._msg = f"The module {self._name} is necessary to use this " \
f"functionality. Please install it and try again !"
self._lazy = lazy_import
self._module = None
[docs]
def __getattr__(self, attr: str) -> Any:
"""Method normally raising an exception indicating that the module is
missing.
In case the ``lazy_import`` argument was set to :obj:`True`, still tries to
get the desired attribute and raises the exception only if the module is
missing.
"""
# The module has to be imported only when called because it's too heavy
if self._lazy:
raise_ = False
# Trying to import the module
if self._module is None:
try:
self._module = import_module(self._name)
except (ImportError, ModuleNotFoundError):
raise_ = True
# The module could be imported, returning the desired attribute
if not raise_:
return getattr(self._module, attr)
# The module could not be imported
raise RuntimeError(f"Missing module: {self._name}\n{self._msg}")
[docs]
def __call__(self, *_, **__) -> NoReturn:
"""Method raising an exception indicating that the module is missing."""
raise RuntimeError(f"Missing module: {self._name}\n{self._msg}")
# Data aliases
class resources:
"""This class defines aliases for quick access to the resources in the
`tool/data/` folder.
These aliases are then used in the examples provided on the GitHub
repository, but could also be used in custom user scripts.
.. versionadded:: 1.5.3
"""
try:
# Defining aliases to the images themselves
from cv2 import imdecode
speckle = imdecode(frombuffer(resource_string('crappy',
'tool/data/speckle.png'),
uint8), flags=0)
ve_markers = imdecode(frombuffer(
resource_string('crappy', 'tool/data/ve_markers.tif'), uint8), flags=0)
pad = imdecode(frombuffer(resource_string('crappy', 'tool/data/pad.png'),
uint8), flags=0)
# In case the module opencv-python is missing
except (ModuleNotFoundError, ImportError):
speckle = OptionalModule('opencv-python')
ve_markers = OptionalModule('opencv-python')
pad = OptionalModule('opencv-python')
# Also getting the paths to the images
paths = {'pad': resource_filename('crappy', 'tool/data/pad.png'),
'speckle': resource_filename('crappy', 'tool/data/speckle.png'),
've_markers': resource_filename('crappy',
'tool/data/ve_markers.tif')}
[docs]
class LinkDataError(ValueError):
"""Exception raised when trying to send a wrong data type through a
:class:`~crappy.links.Link`."""
[docs]
class StartTimeout(TimeoutError):
"""Exception raised when the start event takes too long to be set."""
[docs]
class PrepareError(IOError):
"""Error raised in a :class:`~crappy.blocks.Block` when waiting for all
Blocks to be ready but another Block fails to prepare."""
class CameraConfigError(RuntimeError):
"""Error raised by a :class:`~crappy.tool.camera_config.CameraConfig` window
when encountering an exception."""
[docs]
class CameraPrepareError(RuntimeError):
"""Error raised by a :class:`~crappy.blocks.Camera` when one of its children
processes crashes while preparing."""
[docs]
class CameraRuntimeError(RuntimeError):
"""Error raised by a :class:`~crappy.blocks.Camera` when one of its children
processes crashes while running."""
[docs]
class T0NotSetError(ValueError):
"""Exception raised when requesting the t0 value when it is not set."""
[docs]
class DefinitionError(NameError):
"""Exception raised when trying to define an object with the same name as
an already-defined one."""
def __init__(self, msg: str) -> None:
"""Sets the msg attribute.
Args:
msg: The message to display along with the exception.
"""
super().__init__()
self._msg = msg
def __str__(self) -> str:
return self._msg
[docs]
class GeneratorStop(Exception):
"""Exception raised when a :class:`~crappy.blocks.Generator` Block reaches
the end of its :class:`~crappy.blocks.generator_path.Path`."""
[docs]
class ReaderStop(Exception):
"""Exception raised when a :class:`~crappy.camera.FileReader` Camera has
exhausted all the images to read."""
[docs]
class CrappyFail(Exception):
"""Exception raised when an unexpected Exception is caught in Crappy. Also
raised when all the Blocks do not terminate gracefully."""
def __init__(self) -> None:
"""Only defined here to set the message to display when raised."""
super().__init__("This Exception means that an error occurred while "
"running Crappy. Check the Traceback in the terminal for "
"more information")