diff --git a/docs/source/conf.py b/docs/source/conf.py index 5bd17c83..1f1fbf7d 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -16,7 +16,7 @@ from time import gmtime, strftime from re import match -__version__ = '2.0.0-rc.0' +__version__ = '2.0.0' # -- Project information ----------------------------------------------------- diff --git a/setup.py b/setup.py index b90b28eb..84c5f0b6 100755 --- a/setup.py +++ b/setup.py @@ -18,6 +18,8 @@ extensions = [] # Now finding the extensions to install +install_camera_link = False +install_py_fgen = False if platform.system() == "Linux": # Find the latest runtime version of SiliconSoftware install try: @@ -38,16 +40,10 @@ "-l", "clsersis", "-l", "fglib5"], include_dirs=[f'/usr/local/lib/python{py_ver}/dist-packages/numpy/' f'core/include']) + p = popen("lsmod | grep menable") - if p.read(): - if input("would you like to install CameraLink module? ([y]/n)") != "n": - print("menable kernel module found, installing CameraLink module.") - extensions.append(cl_module) - else: - print("menable kernel module found, but not installing") - else: - print("Cannot find menable kernel module, CameraLink module won't be " - "available.") + if p.read() and install_camera_link: + extensions.append(cl_module) if platform.system() == "Windows": @@ -61,7 +57,7 @@ library_dirs=["C:\\Program Files\\IVI Foundation\\IVI\\Lib_x64\\msc"], extra_compile_args=["/EHsc", "/WX"]) - if input("would you like to install pyFgen module? ([y]/n)") != "n": + if install_py_fgen: extensions.append(py_fgen_module) cl_path = "C:\\Program Files\\SiliconSoftware\\Runtime5.2.1\\" @@ -76,11 +72,7 @@ extra_compile_args=["/EHsc", "/WX"]) p = popen('driverquery /NH | findstr "me4"') - if p.read(): - if input("would you like to install CameraLink module? ([y]/n)") != "n": - extensions.append(cl_module) - else: - print("Can't find microEnable4 Device driver, clModule will not be " - "compiled") + if p.read() and install_camera_link: + extensions.append(cl_module) setup(ext_package='crappy', ext_modules=extensions) diff --git a/src/crappy/__version__.py b/src/crappy/__version__.py index de9e01a6..23678b0e 100644 --- a/src/crappy/__version__.py +++ b/src/crappy/__version__.py @@ -1,3 +1,3 @@ # coding: utf-8 -__version__ = '2.0.0-rc.0' +__version__ = '2.0.0' diff --git a/src/crappy/blocks/_deprecated.py b/src/crappy/blocks/_deprecated.py index 5e9aaadb..6e56fb41 100644 --- a/src/crappy/blocks/_deprecated.py +++ b/src/crappy/blocks/_deprecated.py @@ -3,6 +3,67 @@ from .meta_block import Block +def recv_all(_): + """Empty function for signaling a deprecated method of the Block object.""" + + raise NotImplementedError("The method recv_all was deprecated in version " + "2.0.0, please use recv_all_data instead !") + + +def poll(_): + """Empty function for signaling a deprecated method of the Block object.""" + + raise NotImplementedError("The method poll was deprecated in version " + "2.0.0, please use data_available instead !") + + +def recv_all_last(_): + """Empty function for signaling a deprecated method of the Block object.""" + + raise NotImplementedError("The method recv_all_last was deprecated in " + "version 2.0.0, please use recv_last_data " + "instead !") + + +def get_last(_, *__, **___): + """Empty function for signaling a deprecated method of the Block object.""" + + raise NotImplementedError("The method get_last was deprecated in version " + "2.0.0, please use recv_last_data instead !") + + +def get_all_last(_, *__, **___): + """Empty function for signaling a deprecated method of the Block object.""" + + raise NotImplementedError("The method get_all_last was deprecated in " + "version 2.0.0, please use recv_all_data " + "instead !") + + +def recv_all_delay(_, *__, **___): + """Empty function for signaling a deprecated method of the Block object.""" + + raise NotImplementedError("The method recv_all_delay was deprecated in " + "version 2.0.0, please use recv_all_data_raw " + "instead !") + + +def drop(_, *__, **___): + """Empty function for signaling a deprecated method of the Block object.""" + + raise NotImplementedError("The method drop was deprecated in version " + "2.0.0 !") + + +setattr(Block, recv_all.__name__, recv_all) +setattr(Block, poll.__name__, poll) +setattr(Block, recv_all_last.__name__, recv_all_last) +setattr(Block, get_last.__name__, get_last) +setattr(Block, get_all_last.__name__, get_all_last) +setattr(Block, recv_all_delay.__name__, recv_all_delay) +setattr(Block, drop.__name__, drop) + + class AutoDrive(Block): """Empty class for signaling an object of version 1.5 whose name changed in version 2.0 and is now deprecated. diff --git a/src/crappy/blocks/client_server.py b/src/crappy/blocks/client_server.py index d48f433c..c945efc4 100644 --- a/src/crappy/blocks/client_server.py +++ b/src/crappy/blocks/client_server.py @@ -17,7 +17,7 @@ try: import paho.mqtt.client as mqtt except (ModuleNotFoundError, ImportError): - mqtt = OptionalModule("paho.mqtt.client") + mqtt = OptionalModule("paho-mqtt") TopicsType = Iterable[Union[str, Iterable[str]]] @@ -229,15 +229,8 @@ def __init__(self, self._port = port self._spam = spam self._init_output = init_output if init_output is not None else dict() - self._reader = Thread(target=self._output_reader) self._stop_mosquitto = False - - # Instantiating the client - self._client = mqtt.Client(str(time())) - self._client.on_connect = self._on_connect - self._client.on_message = self._on_message - self._client.reconnect_delay_set(max_delay=10) # These attributes may be set later self._topics: Optional[List[Tuple[str, ...]]] = None @@ -259,9 +252,6 @@ def __init__(self, # The last out vals are given for each label, not each topic self._last_out_val = {label: None for label in chain(*self._topics)} - # The buffer for received data is a dictionary of queues - self._buffer_output = {topic: Queue() for topic in self._topics} - # Preparing for publishing data if cmd_labels is not None: # Replacing strings with tuples @@ -292,16 +282,31 @@ def prepare(self) -> None: if self._cmd_labels is not None and not self.inputs: raise ValueError("cmd_labels are specified but there's no input link !") + # Setting the buffer here because Queue objects cannot be set during + # __init__ in spawn multiprocessing mode + if self._topics is not None: + # The buffer for received data is a dictionary of queues + self._buffer_output = {topic: Queue() for topic in self._topics} + # Starting the broker if self._broker: self.log(logging.INFO, f"Starting the Mosquitto broker on port " f"{self._port}") self._launch_mosquitto() + # Creating and starting a Thread reading the stdout of the broker + self._reader = Thread(target=self._output_reader) self._reader.start() sleep(2) self.log(logging.INFO, "Waiting for Mosquitto to start") sleep(2) + # Instantiating the client here as it cannot be set during __init__ in + # spawn multiprocessing mode + self._client = mqtt.Client(str(time())) + self._client.on_connect = self._on_connect + self._client.on_message = self._on_message + self._client.reconnect_delay_set(max_delay=10) + # Connecting to the broker try_count = 15 while True: diff --git a/src/crappy/blocks/pid.py b/src/crappy/blocks/pid.py index 5942d3d0..933a0c03 100644 --- a/src/crappy/blocks/pid.py +++ b/src/crappy/blocks/pid.py @@ -187,7 +187,7 @@ def loop(self) -> None: # Calculating the three PID terms p_term = self._kp * error self._i_term += self._ki * error * delta_t - d_term = - self._kd * d_input / delta_t + d_term = - self._kd * d_input / delta_t if delta_t > 0 else 0 self._prev_t = t self._last_input = input_ diff --git a/src/crappy/blocks/stop_block.py b/src/crappy/blocks/stop_block.py index 20cb5e9c..262187e8 100644 --- a/src/crappy/blocks/stop_block.py +++ b/src/crappy/blocks/stop_block.py @@ -64,8 +64,15 @@ def __init__(self, criteria = (criteria,) criteria = tuple(criteria) - # Ultimately, all the criteria are converted to Callables - self._criteria = tuple(map(self._parse_criterion, criteria)) + self._raw_crit = criteria + self._criteria = None + + def prepare(self) -> None: + """Converts all the given criteria to :ref:`collections.abc.Callable`.""" + + # This operation cannot be performed during __init__ due to limitations of + # the spawn start method of multiprocessing + self._criteria = tuple(map(self._parse_criterion, self._raw_crit)) def loop(self) -> None: """Receives data from upstream Blocks, checks if this data meets the diff --git a/src/crappy/camera/gstreamer_camera_v4l2.py b/src/crappy/camera/gstreamer_camera_v4l2.py index 61762b41..a363646e 100644 --- a/src/crappy/camera/gstreamer_camera_v4l2.py +++ b/src/crappy/camera/gstreamer_camera_v4l2.py @@ -251,7 +251,7 @@ def open(self, else: self.log(logging.ERROR, f'The type {param.type} is not yet' f' implemented. Only int, bool and menu ' - f'type are implemented. ') + f'type are implemented.') raise NotImplementedError # No need to add the channels setting if there's only one channel diff --git a/src/crappy/modifier/__init__.py b/src/crappy/modifier/__init__.py index 83c5448b..ec46e051 100644 --- a/src/crappy/modifier/__init__.py +++ b/src/crappy/modifier/__init__.py @@ -15,4 +15,6 @@ from .trig_on_change import TrigOnChange from .trig_on_value import TrigOnValue +from ._deprecated import Moving_avg, Moving_med, Trig_on_change, Trig_on_value + modifier_dict: Dict[str, Type[Modifier]] = MetaModifier.classes diff --git a/src/crappy/modifier/_deprecated.py b/src/crappy/modifier/_deprecated.py new file mode 100644 index 00000000..b3e41594 --- /dev/null +++ b/src/crappy/modifier/_deprecated.py @@ -0,0 +1,71 @@ +# coding: utf-8 + +from .meta_modifier import Modifier + + +class Moving_avg(Modifier): + """Empty class for signaling an object of version 1.5 whose name changed in + version 2.0 and is now deprecated. + + The new name of the correct object to use is given. + """ + + def __init__(self, *_, **__) -> None: + """Simply raises the exception when instantiating the object.""" + + super().__init__() + + raise NotImplementedError(f"The {type(self).__name__} Modifier was " + f"renamed to MovingAvg in version 2.0.0 ! " + f"Check the documentation for more information.") + + +class Moving_med(Modifier): + """Empty class for signaling an object of version 1.5 whose name changed in + version 2.0 and is now deprecated. + + The new name of the correct object to use is given. + """ + + def __init__(self, *_, **__) -> None: + """Simply raises the exception when instantiating the object.""" + + super().__init__() + + raise NotImplementedError(f"The {type(self).__name__} Modifier was " + f"renamed to MovingMed in version 2.0.0 ! " + f"Check the documentation for more information.") + + +class Trig_on_change(Modifier): + """Empty class for signaling an object of version 1.5 whose name changed in + version 2.0 and is now deprecated. + + The new name of the correct object to use is given. + """ + + def __init__(self, *_, **__) -> None: + """Simply raises the exception when instantiating the object.""" + + super().__init__() + + raise NotImplementedError(f"The {type(self).__name__} Modifier was " + f"renamed to TrigOnChange in version 2.0.0 ! " + f"Check the documentation for more information.") + + +class Trig_on_value(Modifier): + """Empty class for signaling an object of version 1.5 whose name changed in + version 2.0 and is now deprecated. + + The new name of the correct object to use is given. + """ + + def __init__(self, *_, **__) -> None: + """Simply raises the exception when instantiating the object.""" + + super().__init__() + + raise NotImplementedError(f"The {type(self).__name__} Modifier was " + f"renamed to TrigOnValue in version 2.0.0 ! " + f"Check the documentation for more information.") diff --git a/src/crappy/tool/camera_config/camera_config.py b/src/crappy/tool/camera_config/camera_config.py index b8505f6c..be5774f6 100644 --- a/src/crappy/tool/camera_config/camera_config.py +++ b/src/crappy/tool/camera_config/camera_config.py @@ -10,8 +10,8 @@ from pkg_resources import resource_string from io import BytesIO import logging -from multiprocessing import current_process, Event, Pipe -from multiprocessing.queues import Queue +from multiprocessing import current_process, Event, Queue +from multiprocessing.queues import Queue as MPQueue from .config_tools import Zoom, HistogramProcess from ...camera.meta_camera.camera_setting import CameraBoolSetting, \ @@ -58,7 +58,7 @@ class CameraConfig(tk.Tk): def __init__(self, camera: Camera, - log_queue: Queue, + log_queue: MPQueue, log_level: Optional[int], max_freq: Optional[float]) -> None: """Initializes the interface and displays it. @@ -86,11 +86,11 @@ def __init__(self, # Instantiating objects for the process managing the histogram calculation self._stop_event = Event() self._processing_event = Event() - self._img_in, img_in_proc = Pipe() - img_out_proc, self._img_out = Pipe() + self._img_in = Queue(maxsize=0) + self._img_out = Queue(maxsize=0) self._histogram_process = HistogramProcess( stop_event=self._stop_event, processing_event=self._processing_event, - img_in=img_in_proc, img_out=img_out_proc, log_level=log_level, + img_in=self._img_in, img_out=self._img_out, log_level=log_level, log_queue=log_queue) # Attributes containing the several images and histograms @@ -142,8 +142,8 @@ def main(self) -> None: while self._run: # Remaining below the max allowed frequency - if self._max_freq is None or \ - self._n_loops / (time() - start_time) < self._max_freq: + if self._max_freq is None or (self._n_loops < + self._max_freq * (time() - start_time)): # Update the image, the histogram and the information self._update_img() @@ -967,12 +967,12 @@ def _calc_hist(self) -> None: # Sending the image to the histogram process self.log(logging.DEBUG, "Sending image for histogram calculation") - self._img_in.send((hist_img, self._auto_range.get(), - self._low_thresh, self._high_thresh)) + self._img_in.put_nowait((hist_img, self._auto_range.get(), + self._low_thresh, self._high_thresh)) # Checking if a histogram is available for display - while self._img_out.poll(): - self._hist = self._img_out.recv() + while not self._img_out.empty(): + self._hist = self._img_out.get_nowait() self.log(logging.DEBUG, "Received histogram from histogram process") def _resize_hist(self) -> None: diff --git a/src/crappy/tool/camera_config/config_tools/histogram_process.py b/src/crappy/tool/camera_config/config_tools/histogram_process.py index 3cac0123..6b838e99 100644 --- a/src/crappy/tool/camera_config/config_tools/histogram_process.py +++ b/src/crappy/tool/camera_config/config_tools/histogram_process.py @@ -3,7 +3,6 @@ import numpy as np from multiprocessing import Process, current_process, get_start_method from multiprocessing.synchronize import Event -from multiprocessing.connection import Connection from multiprocessing.queues import Queue import logging import logging.handlers @@ -28,8 +27,8 @@ class HistogramProcess(Process): def __init__(self, stop_event: Event, processing_event: Event, - img_in: Connection, - img_out: Connection, + img_in: Queue, + img_out: Queue, log_level: Optional[int], log_queue: Queue) -> None: """Sets the arguments and initializes the parent class. @@ -40,9 +39,9 @@ def __init__(self, processing_event: An :obj:`multiprocessing.Event` set by the :obj:`multiprocessing.Process` to indicate that it's currently processing an image. Avoids having images to process piling up. - img_in: The :obj:`~multiprocessing.connection.Connection` through which + img_in: The :obj:`~multiprocessing.queues.Queue` through which the images to process are received. - img_out: The :obj:`~multiprocessing.connection.Connection` through which + img_out: The :obj:`~multiprocessing.queues.Queue` through which the calculated histograms are sent back. log_level: The minimum logging level of the entire Crappy script, as an :obj:`int`. @@ -58,8 +57,8 @@ def __init__(self, self._stop_event: Event = stop_event self._processing_event: Event = processing_event - self._img_in: Connection = img_in - self._img_out: Connection = img_out + self._img_in: Queue = img_in + self._img_out: Queue = img_out def run(self) -> None: """The main method being run by the HistogramProcess. @@ -73,15 +72,19 @@ def run(self) -> None: try: self._processing_event.clear() + # Initializing the variables + img, auto_range, low_thresh, high_thresh = None, None, None, None + # Looping until told to stop or an exception is raised while not self._stop_event.is_set(): # Setting the processing event when busy processing an image - if self._img_in.poll(): + if not self._img_in.empty(): self._processing_event.set() # Receiving the image to process as well as additional parameters - while self._img_in.poll(): - img, auto_range, low_thresh, high_thresh = self._img_in.recv() + while not self._img_in.empty(): + (img, auto_range, + low_thresh, high_thresh) = self._img_in.get_nowait() self.log(logging.DEBUG, "Received image from CameraConfig") @@ -102,7 +105,7 @@ def run(self) -> None: out_img[:, round(2 * high_thresh)] = 127 # Sending back the histogram - self._img_out.send(out_img) + self._img_out.put_nowait(out_img) self._processing_event.clear() self.log(logging.DEBUG, "Sent the histogram back to the " "CameraConfig") @@ -115,6 +118,11 @@ def run(self) -> None: except KeyboardInterrupt: self.log(logging.INFO, "Caught KeyboardInterrupt, stopping") + except (Exception,) as exc: + self._logger.exception("Caught Exception while running, stopping !", + exc_info=exc) + finally: + self.log(logging.INFO, "HistogramProcess finished") @staticmethod def _hist_func(x: np.ndarray,