From f0713e6dc2e7f19bbb4ece505b75236e3f794e7d Mon Sep 17 00:00:00 2001 From: PIERROOOTT Date: Mon, 6 Nov 2023 11:17:07 +0100 Subject: [PATCH 01/57] ref: split the CameraOpencv class in two. One class for Linux using v4l2-ctl and the other for other situation --- src/crappy/camera/__init__.py | 5 +++-- src/crappy/camera/opencv_camera_basic.py | 2 +- .../camera/{opencv_camera.py => opencv_camera_v4l2.py} | 0 3 files changed, 4 insertions(+), 3 deletions(-) rename src/crappy/camera/{opencv_camera.py => opencv_camera_v4l2.py} (100%) diff --git a/src/crappy/camera/__init__.py b/src/crappy/camera/__init__.py index 6435f5eb..5440acaa 100644 --- a/src/crappy/camera/__init__.py +++ b/src/crappy/camera/__init__.py @@ -4,8 +4,6 @@ from .fake_camera import FakeCamera from .file_reader import FileReader -from .opencv_camera import CameraOpencv -from .opencv_camera_basic import Webcam from .raspberry_pi_camera import RaspberryPiCamera from .seek_thermal_pro import SeekThermalPro from .ximea_xiapi import XiAPI @@ -22,10 +20,13 @@ run(['v4l2-ctl'], capture_output=True) except FileNotFoundError: from .gstreamer_camera_basic import CameraGstreamer + from .opencv_camera_basic import CameraOpencv else: from .gstreamer_camera_v4l2 import CameraGstreamer + from .opencv_camera_v4l2 import CameraOpencv else: from .gstreamer_camera_basic import CameraGstreamer + from .opencv_camera_basic import CameraOpencv from ._deprecated import deprecated_cameras camera_dict: Dict[str, Type[Camera]] = MetaCamera.classes diff --git a/src/crappy/camera/opencv_camera_basic.py b/src/crappy/camera/opencv_camera_basic.py index ce18e179..2f6ac807 100644 --- a/src/crappy/camera/opencv_camera_basic.py +++ b/src/crappy/camera/opencv_camera_basic.py @@ -13,7 +13,7 @@ cv2 = OptionalModule("opencv-python") -class Webcam(Camera): +class CameraOpencv(Camera): """A basic class for reading images from a USB camera (including webcams). It relies on the OpenCv library. Note that it was purposely kept extremely diff --git a/src/crappy/camera/opencv_camera.py b/src/crappy/camera/opencv_camera_v4l2.py similarity index 100% rename from src/crappy/camera/opencv_camera.py rename to src/crappy/camera/opencv_camera_v4l2.py From 32e5907c4539cda994823b25588c394abb80d8f7 Mon Sep 17 00:00:00 2001 From: PIERROOOTT Date: Mon, 13 Nov 2023 10:50:28 +0100 Subject: [PATCH 02/57] ref: get and set parameters by using vl2-ctl. Same method as for CameraGstreamerV4l2 --- src/crappy/camera/opencv_camera_v4l2.py | 317 ++++++++++++++++++++---- 1 file changed, 266 insertions(+), 51 deletions(-) diff --git a/src/crappy/camera/opencv_camera_v4l2.py b/src/crappy/camera/opencv_camera_v4l2.py index f920e5da..622df071 100644 --- a/src/crappy/camera/opencv_camera_v4l2.py +++ b/src/crappy/camera/opencv_camera_v4l2.py @@ -1,12 +1,13 @@ # coding: utf-8 +from __future__ import annotations from time import time, sleep -from typing import Tuple, List, Optional +from typing import Tuple, List, Optional, Callable from numpy import ndarray -from platform import system from subprocess import run -from re import findall, split, search +from re import findall, split, search, finditer, Match import logging +from dataclasses import dataclass from .meta_camera import Camera from .._global import OptionalModule @@ -17,6 +18,62 @@ cv2 = OptionalModule("opencv-python") +@dataclass +class Parameter: + """A class for the different parameters the user can adjust.""" + + name: str + type: str + min: Optional[str] = None + max: Optional[str] = None + step: Optional[str] = None + default: Optional[str] = None + value: Optional[str] = None + flags: Optional[str] = None + options: Optional[Tuple[str, ...]] = None + + @classmethod + def parse_info(cls, match: Match) -> Parameter: + """Instantiates the class Parameter, according to the information + collected with v4l2-ctl. + + Args: + match: Match object returned by successful matches of the regex with + a string. + + Returns: + The instantiated class. + """ + + return cls(name=match.group(1), + type=match.group(2), + min=match.group(4) if match.group(4) else None, + max=match.group(6) if match.group(6) else None, + step=match.group(8) if match.group(8) else None, + default=match.group(10) if match.group(10) else None, + value=match.group(11), + flags=match.group(13) if match.group(13) else None) + + def add_options(self, match: Match) -> None: + """Adds the different possible options for a menu parameter. + + Args: + match: Match object returned by successful matches of the regex with + a string. + """ + + menu_info = match.group(1) + menu_values = match.group(2) + menu_name = search(r'(\w+) \w+ \(menu\)', menu_info).group(1) + if self.name == menu_name: + options = findall(r'\d+: .+?(?=\n|$)', menu_values) + num_options = findall(r'(\d+): .+?(?=\n|$)', menu_values) + self.options = tuple(options) + for i in range(len(num_options)): + if self.default == num_options[i]: + self.default = options[i] + + class CameraOpencv(Camera): """A class for reading images from any camera able to interface with OpenCv. @@ -27,9 +84,7 @@ class CameraOpencv(Camera): :class:`~crappy.camera.CameraGstreamer` one that relies on GStreamer, but the installation of OpenCv is way easier than the one of GStreamer. - Note: - For a better performance of this class in Linux, it is recommended to have - `v4l-utils` installed. + To use this class, `v4l-utils` must be installed. """ def __init__(self) -> None: @@ -40,6 +95,7 @@ def __init__(self) -> None: self._cap = None self._device_num: Optional[int] = None self._formats: List[str] = list() + self.parameters = [] self.add_choice_setting(name="channels", choices=('1', '3'), @@ -61,31 +117,26 @@ def open(self, device_num: int = 0, **kwargs) -> None: self.log(logging.INFO, "Opening the image stream from the camera") self._cap = cv2.VideoCapture(device_num) self._device_num = device_num - fourcc = self._get_fourcc() - if system() == 'Linux': - self._formats = [] + self._formats = [] - # Trying to run v4l2-ctl to get the available formats - command = ['v4l2-ctl', '-d', str(device_num), '--list-formats-ext'] - try: - self.log(logging.INFO, f"Getting the available image formats with " - f"command {command}") - check = run(command, capture_output=True, text=True) - except FileNotFoundError: - self.log(logging.WARNING, "The performance of the CameraOpencv class " - "could be improved if v4l-utils was " - "installed !") - check = None - check = check.stdout if check is not None else '' - - # Splitting the returned string to isolate each encoding - if findall(r'\[\d+]', check): - check = split(r'\[\d+]', check)[1:] - elif findall(r'Pixel\sFormat', check): - check = split(r'Pixel\sFormat', check)[1:] - else: - check = [] + # Trying to run v4l2-ctl to get the available formats + command = ['v4l2-ctl', '-d', str(device_num), '--list-formats-ext'] + try: + self.log(logging.INFO, f"Getting the available image formats with " + f"command {command}") + check = run(command, capture_output=True, text=True) + except FileNotFoundError: + check = None + check = check.stdout if check is not None else '' + + # Splitting the returned string to isolate each encoding + if findall(r'\[\d+]', check): + check = split(r'\[\d+]', check)[1:] + elif findall(r'Pixel\sFormat', check): + check = split(r'Pixel\sFormat', check)[1:] + else: + check = [] if check: for img_format in check: @@ -100,28 +151,6 @@ def open(self, device_num: int = 0, **kwargs) -> None: for fps in fps_list: self._formats.append(f'{name} {size} ({fps} fps)') - else: - # If v4l-utils is not installed, proposing two encodings without - # further detail - self._formats = [fourcc, 'MJPG'] - - # Still letting the user choose the size - self.add_scale_setting(name='width', lowest=1, highest=1920, - getter=self._get_width, setter=self._set_width) - self.add_scale_setting(name='height', lowest=1, highest=1080, - getter=self._get_height, - setter=self._set_height) - - else: - # On Windows the fourcc management is even messier than on Linux - self._formats = [] - - # Still letting the user choose the size - self.add_scale_setting(name='width', lowest=1, highest=1920, - getter=self._get_width, setter=self._set_width) - self.add_scale_setting(name='height', lowest=1, highest=1080, - getter=self._get_height, setter=self._set_height) - if self._formats: # The format integrates the size selection if ' ' in self._formats[0]: @@ -136,6 +165,69 @@ def open(self, device_num: int = 0, **kwargs) -> None: getter=self._get_fourcc, setter=self._set_format) + # Trying to run v4l2-ctl to get the available settings + command = ['v4l2-ctl', '-L'] if device_num is None \ + else ['v4l2-ctl', '-d', str(device_num), '-L'] + self.log(logging.INFO, f"Getting the available image settings with " + f"command {command}") + try: + check = run(command, capture_output=True, text=True) + except FileNotFoundError: + check = None + check = check.stdout if check is not None else '' + + # Regex to extract the different parameters and their information + param_pattern = (r'(\w+)\s+0x\w+\s+\((\w+)\)\s+:\s*' + r'(min=(-?\d+)\s+)?' + r'(max=(-?\d+)\s+)?' + r'(step=(\d+)\s+)?' + r'(default=(-?\d+)\s+)?' + r'value=(-?\d+)\s*' + r'(flags=([^\\n]+))?') + + # Extract the different parameters and their information + matches = finditer(param_pattern, check) + for match in matches: + self.parameters.append(Parameter.parse_info(match)) + + # Regex to extract the different options in a menu + menu_options = finditer( + r'(\w+ \w+ \(menu\))([\s\S]+?)(?=\n\s*\w+ \w+ \(.+?\)|$)', check) + + # Extract the different options + for menu_option in menu_options: + for param in self.parameters: + param.add_options(menu_option) + + # Create the different settings + for param in self.parameters: + if not param.flags: + if param.type == 'int': + self.add_scale_setting(name=param.name, + lowest=int(param.min), + highest=int(param.max), + getter=self._add_scale_getter(param.name), + setter=self._add_setter(param.name), + default=param.default, + step=int(param.step)) + elif param.type == 'bool': + self.add_bool_setting(name=param.name, + getter=self._add_bool_getter(param.name), + setter=self._add_setter(param.name), + default=bool(int(param.default))) + elif param.type == 'menu': + if param.options: + self.add_choice_setting(name=param.name, + choices=param.options, + getter=self._add_menu_getter(param.name), + setter=self._add_setter(param.name), + default=param.default) + else: + self.log(logging.ERROR, f'The type {param.type} is not yet' + f' implemented. Only int, bool and menu ' + f'type are implemented. ') + raise NotImplementedError + # Adding the software ROI selection settings if 'width' in self.settings and 'height' in self.settings: width, height = self._get_width(), self._get_height() @@ -259,3 +351,126 @@ def _get_format_size(self) -> str: fps, *_ = search(r"Frames per second\s*:\s*(\d+.\d+)", check).groups() return f'{format_} {width}x{height} ({fps} fps)' + + def _add_setter(self, name: str) -> Callable: + """Creates a setter function for a setting named 'name'. + Args: + name: Name of the setting. + + Returns: + The setter function. + """ + + def setter(value) -> None: + """The method to set the value of a setting running v4l2-ctl. + """ + + if isinstance(value, str): + # The value to set the menu parameter is just the int + # at the beginning the string + value = search(r'(\d+): ', value).group(1) + if self._device_num is not None: + command = ['v4l2-ctl', '-d', str(self._device_num), '--set-ctrl', + f'{name}={value}'] + else: + command = ['v4l2-ctl', '--set-ctrl', f'{name}={int(value[0])}'] + self.log(logging.DEBUG, f"Setting {name} with command {command}") + run(command, capture_output=True, text=True) + else: + if self._device_num is not None: + command = ['v4l2-ctl', '-d', str(self._device_num), '--set-ctrl', + name+f'={int(value)}'] + else: + command = ['v4l2-ctl', '--set-ctrl', f'{name}={int(value)}'] + self.log(logging.DEBUG, f"Setting {name} with command {command}") + run(command, capture_output=True, text=True) + return setter + + def _add_scale_getter(self, name: str) -> Callable: + """Creates a getter function for a setting named 'name'. + Args: + name: Name of the setting. + + Returns: + The getter function. + """ + + def getter() -> int: + """The method to get the current value of a scale setting + running v4l2-ctl. + """ + + # Trying to run v4l2-ctl to get the value + if self._device_num is not None: + command = ['v4l2-ctl', '-d', str(self._device_num), '--get-ctrl', name] + else: + command = ['v4l2-ctl', '--get-ctrl', name] + try: + self.log(logging.DEBUG, f"Getting {name} with command {command}") + value = run(command, capture_output=True, text=True).stdout + value = search(r': (-?\d+)', value).group(1) + except FileNotFoundError: + value = None + return int(value) + return getter + + def _add_bool_getter(self, name: str) -> Callable: + """Creates a getter function for a setting named 'name'. + Args: + name: Name of the setting. + + Returns: + The getter function. + """ + + def getter() -> bool: + """The method to get the current value of a bool setting + running v4l2-ctl. + """ + + # Trying to run v4l2-ctl to get the value + if self._device_num is not None: + command = ['v4l2-ctl', '-d', str(self._device_num), '--get-ctrl', name] + else: + command = ['v4l2-ctl', '--get-ctrl', name] + try: + self.log(logging.DEBUG, f"Getting {name} with command {command}") + value = run(command, capture_output=True, text=True).stdout + value = search(r': (\d+)', value).group(1) + except FileNotFoundError: + value = None + return bool(int(value)) + return getter + + def _add_menu_getter(self, name: str) -> Callable: + """Creates a getter function for a setting named 'name'. + Args: + name: Name of the setting. + + Returns: + The getter function. + """ + + def getter() -> str: + """The method to get the current value of a choice setting + running v4l2-ctl. + """ + + # Trying to run v4l2-ctl to get the value + if self._device_num is not None: + command = ['v4l2-ctl', '-d', str(self._device_num), '--get-ctrl', name] + else: + command = ['v4l2-ctl', '--get-ctrl', name] + try: + self.log(logging.DEBUG, f"Getting {name} with command {command}") + value = run(command, capture_output=True, text=True).stdout + value = search(r': (\d+)', value).group(1) + for param in self.parameters: + if param.name == name: + for option in param.options: + if value == search(r'(\d+):', option).group(1): + value = option + except FileNotFoundError: + value = None + return value + return getter From 9be135a55384d0c06df940c77a14c65da916a145 Mon Sep 17 00:00:00 2001 From: PIERROOOTT Date: Mon, 13 Nov 2023 10:51:03 +0100 Subject: [PATCH 03/57] ref: remove formats that can not be set. --- src/crappy/camera/opencv_camera_v4l2.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/crappy/camera/opencv_camera_v4l2.py b/src/crappy/camera/opencv_camera_v4l2.py index 622df071..48e2794d 100644 --- a/src/crappy/camera/opencv_camera_v4l2.py +++ b/src/crappy/camera/opencv_camera_v4l2.py @@ -138,10 +138,11 @@ def open(self, device_num: int = 0, **kwargs) -> None: else: check = [] - if check: - for img_format in check: - # For each encoding, finding its name - name, *_ = search(r"'(\w+)'", img_format).groups() + if check: + for img_format in check: + # For each encoding, finding its name + name, *_ = search(r"'(\w+)'", img_format).groups() + if name == 'MJPG' or name == 'YUYV': sizes = findall(r'\d+x\d+', img_format) fps_sections = split(r'\d+x\d+', img_format)[1:] From 039914c93509b56cc41ec475670fe2afe39b4cab Mon Sep 17 00:00:00 2001 From: PIERROOOTT Date: Wed, 15 Nov 2023 14:40:21 +0100 Subject: [PATCH 04/57] ref: add a class Webcam using opencv without any parameter management. --- src/crappy/camera/__init__.py | 1 + src/crappy/camera/opencv_camera_webcam.py | 76 +++++++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 src/crappy/camera/opencv_camera_webcam.py diff --git a/src/crappy/camera/__init__.py b/src/crappy/camera/__init__.py index 5440acaa..d5bbc28a 100644 --- a/src/crappy/camera/__init__.py +++ b/src/crappy/camera/__init__.py @@ -4,6 +4,7 @@ from .fake_camera import FakeCamera from .file_reader import FileReader +from .opencv_camera_webcam import Webcam from .raspberry_pi_camera import RaspberryPiCamera from .seek_thermal_pro import SeekThermalPro from .ximea_xiapi import XiAPI diff --git a/src/crappy/camera/opencv_camera_webcam.py b/src/crappy/camera/opencv_camera_webcam.py new file mode 100644 index 00000000..ce18e179 --- /dev/null +++ b/src/crappy/camera/opencv_camera_webcam.py @@ -0,0 +1,76 @@ +# coding: utf-8 + +from time import time +from typing import Tuple, Optional +from numpy import ndarray +import logging +from .meta_camera import Camera +from .._global import OptionalModule + +try: + import cv2 +except (ModuleNotFoundError, ImportError): + cv2 = OptionalModule("opencv-python") + + +class Webcam(Camera): + """A basic class for reading images from a USB camera (including webcams). + + It relies on the OpenCv library. Note that it was purposely kept extremely + simple as it is mainly used as a demo. See + :class:`~crappy.camera.CameraOpencv` and + :class:`~crappy.camera.CameraGstreamer` for classes giving a finer control + over the camera. + """ + + def __init__(self) -> None: + """Sets variables and adds the channels setting.""" + + super().__init__() + + self._cap = None + + self.add_choice_setting(name="channels", + choices=('1', '3'), + default='1') + + def open(self, device_num: Optional[int] = 0, **kwargs) -> None: + """Opens the video stream and sets any user-specified settings. + + Args: + device_num: The index of the device to open, as an :obj:`int`. + **kwargs: Any additional setting to set before opening the configuration + window. + """ + + # Opening the videocapture device + self.log(logging.INFO, "Opening the image stream from the camera") + self._cap = cv2.VideoCapture(device_num) + + # Setting the kwargs if any + self.set_all(**kwargs) + + def get_image(self) -> Tuple[float, ndarray]: + """Grabs a frame from the videocapture object and returns it along with a + timestamp.""" + + # Grabbing the frame and the timestamp + t = time() + ret, frame = self._cap.read() + + # Checking the integrity of the frame + if not ret: + raise IOError("Error reading the camera") + + # Returning the image in the right format, and its timestamp + if self.channels == '1': + return t, cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) + else: + return t, frame + + def close(self) -> None: + """Releases the videocapture object.""" + + if self._cap is not None: + self.log(logging.INFO, "Closing the image stream from the camera") + self._cap.release() From ed4471cd16009940024eff49ba71ea421a4dbdcf Mon Sep 17 00:00:00 2001 From: Antoine Weisrock Date: Wed, 15 Nov 2023 15:17:06 +0100 Subject: [PATCH 05/57] docs: use the :kbd: directive to describe keyboard combinations They were previously described in plain text, capital letters --- docs/source/developers.rst | 3 +- docs/source/tutorials/custom_objects.rst | 4 +- docs/source/tutorials/getting_started.rst | 49 ++++++++++++----------- docs/source/tutorials/more_complexity.rst | 18 ++++----- 4 files changed, 38 insertions(+), 36 deletions(-) diff --git a/docs/source/developers.rst b/docs/source/developers.rst index 51aff282..71f5b59a 100644 --- a/docs/source/developers.rst +++ b/docs/source/developers.rst @@ -406,7 +406,8 @@ cases : - If all the Blocks are not done running at the end of this phase. - If an :exc:`Exception` was caught during Crappy's execution. -- If Crappy was stopped using CTRL+C, resulting in a :exc:`KeyboardInterrupt`. +- If Crappy was stopped using :kbd:`Control-c`, resulting in a + :exc:`KeyboardInterrupt`. The goal of this exception is to stop the execution of the ``__main__`` Process, to avoid any more code to be executed in case something went wrong in diff --git a/docs/source/tutorials/custom_objects.rst b/docs/source/tutorials/custom_objects.rst index fa9874f7..2fee1108 100644 --- a/docs/source/tutorials/custom_objects.rst +++ b/docs/source/tutorials/custom_objects.rst @@ -770,8 +770,8 @@ used : short timeout (at most a few seconds), and do not use infinite loops that could never end. That is because in the smooth termination scenarios, the Blocks are only told to terminate once their current method call returns. - Otherwise, you'll have to use CTRL+C to stop your script, which is now - considered an invalid way to stop Crappy. + Otherwise, you'll have to use :kbd:`Control-c` to stop your script, which is + now considered an invalid way to stop Crappy. Now that the possible methods have been described, it is time to put them into application in an example. However, as the Block object is quite complex, such diff --git a/docs/source/tutorials/getting_started.rst b/docs/source/tutorials/getting_started.rst index a90c0d94..28fb9a86 100644 --- a/docs/source/tutorials/getting_started.rst +++ b/docs/source/tutorials/getting_started.rst @@ -175,10 +175,10 @@ the same level as the script that was just started. It contains the data being acquired by the Recorder Block. As mentioned earlier, the execution of the script will stop after 40s as specified to the Generator Block. The script can also stop earlier if an error occurs (e.g. missing dependency), or if the user -hits CTRL+C. Note that this last way of ending a script should only be used in -case something goes wrong, e.g. if the script crashes. You can find more about -the different ways to stop a script in Crappy in :ref:`a later section <3. -Properly stopping a script>`. +hits :kbd:`Control-c`. Note that this last way of ending a script should only +be used in case something goes wrong, e.g. if the script crashes. You can find +more about the different ways to stop a script in Crappy in :ref:`a later +section <3. Properly stopping a script>`. **You now know learned the very basics of writing scripts for Crappy** ! You can :download:`download this first example @@ -278,10 +278,10 @@ The script should run in the exact same way as the one of the previous section, except this time there should be two cycles of stretching and relaxation before the final step of stretching until failure. **That reflects the changes we** **made to the path of the Generator Block**. Just like previously, the script -will stop by itself. You can stop it earlier with CTRL+C, but this is not -considered as a clean way to stop Crappy. :download:`Download this Generator -example ` to run it locally on -your machine ! +will stop by itself. You can stop it earlier with :kbd:`Control-c`, but this is +not considered as a clean way to stop Crappy. :download:`Download this +Generator example ` to run it +locally on your machine ! **You should now be able to build an run a variety of patterns for your** **Generator Blocks** ! It is after all just a matter of reading the API, @@ -336,9 +336,9 @@ lines. In particular, unlike the :ref:`Generator` Block, the Camera does not automatically stop after a condition is met. To allow the script to stop in a proper way, a :ref:`Stop Button` Block should be added. It will display a button, that will stop the execution of the script when clicked upon. It is -always possible to stop Crappy using CTRL+C, but this is not considered a -proper way of ending the script. After inserting the stop button, here's the -final runnable script : +always possible to stop Crappy using :kbd:`Control-c`, but this is not +considered a proper way of ending the script. After inserting the stop button, +here's the final runnable script : .. literalinclude:: /downloads/getting_started/tuto_camera.py :language: python @@ -527,16 +527,17 @@ So, what should you *not* do to stop a script ? Obviously, you should **avoid** extreme solutions for the (very unlikely) situations when Crappy would become totally unresponsive... -Starting from version 2.0.0, hitting CTRL+C to stop Crappy (i.e. raising -:exc:`KeyboardInterrupt`) is also considered as an invalid behavior. -**However**, unlike more aggressive methods, CTRL+C is still handled internally -and **should lead to a proper termination of the Blocks**. As it might lead to -unexpected behavior, and to deter users from using it, we chose to have CTRL+C -raise an Exception once all the Blocks are correctly stopped. This behavior can -be tuned, see the :ref:`7. Advanced control over the runtime` section of the -tutorials. The take-home message about CTRL+C is : **using CTRL+C to stop a** -**script is fine in most cases but it is preferable to do otherwise, so it** -**will by default raise an error even if everything went fine** ! +Starting from version 2.0.0, hitting :kbd:`Control-c` to stop Crappy (i.e. +raising :exc:`KeyboardInterrupt`) is also considered as an invalid behavior. +**However**, unlike more aggressive methods, :kbd:`Control-c` is still handled +internally and **should lead to a proper termination of the Blocks**. As it +might lead to unexpected behavior, and to deter users from using it, we chose +to have :kbd:`Control-c` raise an Exception once all the Blocks are correctly +stopped. This behavior can be tuned, see the :ref:`7. Advanced control over the +runtime` section of the tutorials. The take-home message about :kbd:`Control-c` +is : **using :kbd:`Control-c` to stop a script is fine in most cases but it** +**is preferable to do otherwise, so it will by default raise an error even if** +**everything went fine** ! Now that we know what are the forbidden and not recommended ways of stopping Crappy, let's review the 100% approved ones ! As you should have noticed in the @@ -554,9 +555,9 @@ is also an option. .. Note:: A test in Crappy will also end if an unexpected Exception is raised anywhere in the module. In that case, all Blocks will instantly stop and, just like - with CTRL+C, an error will be raised once all the Blocks are stopped. The - unexpected Exception will still be handled and all Blocks should terminate - properly if the problem is not too serious. + with :kbd:`Control-c`, an error will be raised once all the Blocks are + stopped. The unexpected Exception will still be handled and all Blocks + should terminate properly if the problem is not too serious. When writing a script, first determine which termination way seems more appropriate. **If you want to be able to stop the test anytime, opt for the** diff --git a/docs/source/tutorials/more_complexity.rst b/docs/source/tutorials/more_complexity.rst index 9adf2d19..5e195a92 100644 --- a/docs/source/tutorials/more_complexity.rst +++ b/docs/source/tutorials/more_complexity.rst @@ -515,12 +515,12 @@ Finally, the :py:`'no_raise'` argument is a :obj:`bool` that allows to disable the exceptions raised at the end of a script. The default behavior of Crappy is to raise an exception when it stops, if either an unexpected error was raised during its execution or if a :exc:`KeyboardInterrupt` was caught (script -stopped using CTRL+C). The purpose of this behavior is to prevent the execution -of any line of code that would come after :meth:`crappy.start()`, since it -might not be safe to run it after Crappy has failed or the user interrupted the -test. By setting :py:`'no_raise'` to :obj:`True`, the exceptions are disabled -and Python goes on after Crappy finishes, even if it crashed. **Use this** -**feature with caution, as it can lead to unexpected or even unsafe** -**behavior** ! This argument can be changed by users who would prefer to use -CTRL+C to stop tests but don't want exceptions to be raised, although we -discourage using this strategy. +stopped using :kbd:`Control-c`). The purpose of this behavior is to prevent the +execution of any line of code that would come after :meth:`crappy.start()`, +since it might not be safe to run it after Crappy has failed or the user +interrupted the test. By setting :py:`'no_raise'` to :obj:`True`, the +exceptions are disabled and Python goes on after Crappy finishes, even if it +crashed. **Use this feature with caution, as it can lead to unexpected or** +**even unsafe behavior** ! This argument can be changed by users who would +prefer to use :kbd:`Control-c` to stop tests but don't want exceptions to be +raised, although we discourage using this strategy. From ab9ddc2780ffd5fa557e2131c1f5603588c12fbe Mon Sep 17 00:00:00 2001 From: Antoine Weisrock Date: Wed, 15 Nov 2023 16:02:42 +0100 Subject: [PATCH 06/57] docs: add numpy, matplotlib and psutil to intersphinx mapping --- docs/source/conf.py | 6 +++++- docs/source/tutorials/complex_custom_objects.rst | 4 ++-- docs/source/tutorials/custom_objects.rst | 16 ++++++++-------- docs/source/tutorials/getting_started.rst | 12 ++++++------ docs/source/tutorials/more_complexity.rst | 12 ++++++------ src/crappy/inout/meta_inout/inout.py | 4 ++-- src/crappy/modifier/demux.py | 8 ++++---- 7 files changed, 33 insertions(+), 29 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 9185dba5..6c79034a 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -232,4 +232,8 @@ # -- Options for intersphinx extension --------------------------------------- # Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {'python': ('https://docs.python.org/3', None)} +intersphinx_mapping = { + 'python': ('https://docs.python.org/3', None), + 'numpy': ('https://numpy.org/doc/stable/', None), + 'matplotlib': ('https://matplotlib.org/stable/', None), + 'psutil': ('https://psutil.readthedocs.io/en/latest/', None)} diff --git a/docs/source/tutorials/complex_custom_objects.rst b/docs/source/tutorials/complex_custom_objects.rst index eabecf1c..80c63348 100644 --- a/docs/source/tutorials/complex_custom_objects.rst +++ b/docs/source/tutorials/complex_custom_objects.rst @@ -97,8 +97,8 @@ either fixed or controlled by the value of an input label : :emphasize-lines: 35, 40-41, 49, 52-53 .. Note:: - To run this example, you'll need to have the *matplotlib* and *scipy* Python - modules installed. + To run this example, you'll need to have the :mod:`matplotlib` and *scipy* + Python modules installed. This example contains all the ingredients described above. The parent class is initialized, then the :py:`condition` argument is parsed with diff --git a/docs/source/tutorials/custom_objects.rst b/docs/source/tutorials/custom_objects.rst index 2fee1108..82fbdf00 100644 --- a/docs/source/tutorials/custom_objects.rst +++ b/docs/source/tutorials/custom_objects.rst @@ -51,7 +51,7 @@ modified dictionary in the function ! :lines: 1-3, 5-12, 32-41, 43-45, 47-48 .. Note:: - To run this example, you'll need to have the *matplotlib* Python module + To run this example, you'll need to have the :mod:`matplotlib` Python module installed. In this first example, you can see that instead of replacing the value of the @@ -107,7 +107,7 @@ Integrate Modifier : :lines: 1-6, 13-39, 42-44, 46-48 .. Note:: - To run this example, you'll need to have the *matplotlib* Python module + To run this example, you'll need to have the :mod:`matplotlib` Python module installed. As you can see, compared to the template, several features have been added. @@ -286,7 +286,7 @@ context to make it run : :language: python .. Note:: - To run this example, you'll need to have the *matplotlib* Python module + To run this example, you'll need to have the :mod:`matplotlib` Python module installed. You can :download:`download this custom Actuator example @@ -389,7 +389,7 @@ new stored values. Let's now integrate the InOut into a runnable code : :language: python .. Note:: - To run this example, you'll need to have the *matplotlib* Python module + To run this example, you'll need to have the :mod:`matplotlib` Python module installed. In order to obtain two commands from a single :ref:`Generator`, a @@ -497,7 +497,7 @@ the previous sub-section : :emphasize-lines: 5, 25-35, 52, 54-55, 61-62 .. Note:: - To run this example, you'll need to have the *matplotlib* Python module + To run this example, you'll need to have the :mod:`matplotlib` Python module installed. The first difference is that the module :mod:`numpy` must be used, but that is @@ -649,8 +649,8 @@ management : :lines: 1-12, 21-97 .. Note:: - To run this example, you'll need to have the *opencv-python*, *matplotlib* - and *Pillow* Python modules installed. + To run this example, you'll need to have the *opencv-python*, + :mod:`matplotlib` and *Pillow* Python modules installed. After the changes, notice that the :meth:`~crappy.camera.Camera.get_image` method remains unchanged. The values of the settings, that were previously @@ -792,7 +792,7 @@ tutorial ! Here is the full code : :emphasize-lines: 13-22, 46, 97, 127 .. Note:: - To run this example, you'll need to have the *matplotlib* Python module + To run this example, you'll need to have the :mod:`matplotlib` Python module installed. In this Block, only the :meth:`~crappy.blocks.Block.begin` method is not diff --git a/docs/source/tutorials/getting_started.rst b/docs/source/tutorials/getting_started.rst index 28fb9a86..e0c1c422 100644 --- a/docs/source/tutorials/getting_started.rst +++ b/docs/source/tutorials/getting_started.rst @@ -59,7 +59,7 @@ In this second part of the tutorials, we're going to **write step-by-step an** following tutorials will also follow the same principle. If a script would not work as expected, please signal it to the developers (see the :ref:`Troubleshooting` page). Note that this first example script requires the -`matplotlib `_ Python module to run. +:mod:`matplotlib` Python module to run. The first thing to do when writing a script for Crappy is to open a new *.py* file. In this new file, you should start by **importing the module Crappy** : @@ -211,7 +211,7 @@ let's take a closer look at it : :lines: 1-11 .. Note:: - To run this example, you'll need to have the *matplotlib* Python module + To run this example, you'll need to have the :mod:`matplotlib` Python module installed. As you can see, the first argument of the Generator is its *path*. It describes @@ -309,8 +309,8 @@ looks like : :lines: 1-13 .. Note:: - To run this example, you'll need to have the *opencv-python*, *matplotlib* - and *Pillow* Python modules installed. + To run this example, you'll need to have the *opencv-python*, + :mod:`matplotlib` and *Pillow* Python modules installed. The first given argument is the name of the :class:`~crappy.camera.Camera` to use for acquiring the images. In this demo, the :ref:`Fake Camera` is used so @@ -416,8 +416,8 @@ device. Here's an example of code featuring an IOBlock for data acquisition : :lines: 1-6, 15, 17-23, 25-27 .. Note:: - To run this example, you'll need to have the *psutil* and *matplotlib* - Python modules installed. + To run this example, you'll need to have the :mod:`psutil` and + :mod:`matplotlib` Python modules installed. As you can see, the base syntax is quite simple for acquiring data with an IOBlock. You first have to specify the :class:`~crappy.inout.InOut` that you diff --git a/docs/source/tutorials/more_complexity.rst b/docs/source/tutorials/more_complexity.rst index 5e195a92..1baa5bc8 100644 --- a/docs/source/tutorials/more_complexity.rst +++ b/docs/source/tutorials/more_complexity.rst @@ -55,7 +55,7 @@ Block together consistently : :emphasize-lines: 39-40, 42, 44-45 .. Note:: - To run this example, you'll need to have the *matplotlib* Python module + To run this example, you'll need to have the :mod:`matplotlib` Python module installed. Can you see it ? We have both :py:`crappy.link(mot, pid)` and @@ -103,7 +103,7 @@ Machine Block and pointing towards a new :ref:`Grapher` for the position : :emphasize-lines: 39, 49-51 .. Note:: - To run this example, you'll need to have the *matplotlib* Python module + To run this example, you'll need to have the :mod:`matplotlib` Python module installed. As you can see, the Modifiers are expected to be given to the :py:`'modifier'` @@ -177,7 +177,7 @@ breaks, no matter the elongation speed. The code is as follows : :emphasize-lines: 9, 26 .. Note:: - To run this example, you'll need to have the *matplotlib* Python module + To run this example, you'll need to have the :mod:`matplotlib` Python module installed. You can :download:`download this advanced Generator example @@ -236,8 +236,8 @@ them together : :emphasize-lines: 20-24 .. Note:: - To run this example, you'll need to have the *matplotlib* and *psutil* - Python modules installed. + To run this example, you'll need to have the :mod:`matplotlib` and + :mod:`psutil` Python modules installed. Compared to the regular IOBlock usage, this is when things get a bit more complicated ! As the IOBlock and HDFRecorder are both meant to handle stream @@ -423,7 +423,7 @@ This is not very interesting to watch, let's add some visualization : :emphasize-lines: 4, 12-13 .. Note:: - To run this example, you'll need to have the *matplotlib* and + To run this example, you'll need to have the :mod:`matplotlib` and *opencv-python* Python modules installed. You can :download:`download this FakeCamera example diff --git a/src/crappy/inout/meta_inout/inout.py b/src/crappy/inout/meta_inout/inout.py index 9e7620b5..e56731b1 100644 --- a/src/crappy/inout/meta_inout/inout.py +++ b/src/crappy/inout/meta_inout/inout.py @@ -132,8 +132,8 @@ def start_stream(self) -> None: def get_stream(self) -> Optional[Union[Iterable[np.ndarray], Dict[str, np.ndarray]]]: - """This method should acquire a stream as a numpy array, and return it - along with an array carrying the timestamps. + """This method should acquire a stream as a :obj:`numpy.array`, and return + it along with another array carrying the timestamps. Two arrays should be created: one of dimension `(m,)` carrying the timestamps, and one of dimension `(m, n)` carrying the acquired data. They diff --git a/src/crappy/modifier/demux.py b/src/crappy/modifier/demux.py index f2d5ca6e..6ffa9598 100644 --- a/src/crappy/modifier/demux.py +++ b/src/crappy/modifier/demux.py @@ -16,10 +16,10 @@ class Demux(Modifier): the stream to make it readable by most Blocks, and also splits the stream in several labels if necessary. - It takes a stream as an input, i.e. a :obj:`dict` whose values are numpy - arrays, and outputs another :obj:`dict` whose values are :obj:`float`. If the - numpy arrays contains several columns (corresponding to several acquired - channels), it splits them into several labels. + It takes a stream as an input, i.e. a :obj:`dict` whose values are + :obj:`numpy.array`, and outputs another :obj:`dict` whose values are + :obj:`float`. If the numpy arrays contains several columns (corresponding to + several acquired channels), it splits them into several labels. Important: In the process of converting the stream data to regular labeled data, much From 4e191a1751db6b90becdfae81364e20b1cda4123 Mon Sep 17 00:00:00 2001 From: PIERROOOTT Date: Wed, 15 Nov 2023 17:10:31 +0100 Subject: [PATCH 07/57] refactor: add several scale settings to CameraOpencvBasic --- src/crappy/camera/opencv_camera_basic.py | 92 ++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/src/crappy/camera/opencv_camera_basic.py b/src/crappy/camera/opencv_camera_basic.py index 2f6ac807..cba5d396 100644 --- a/src/crappy/camera/opencv_camera_basic.py +++ b/src/crappy/camera/opencv_camera_basic.py @@ -47,6 +47,58 @@ def open(self, device_num: Optional[int] = 0, **kwargs) -> None: self.log(logging.INFO, "Opening the image stream from the camera") self._cap = cv2.VideoCapture(device_num) + self._cap.set(cv2.CAP_PROP_BRIGHTNESS, 99999) + max_brightness = int(self._cap.get(cv2.CAP_PROP_BRIGHTNESS)) + self._cap.set(cv2.CAP_PROP_BRIGHTNESS, -99999) + min_brightness = int(self._cap.get(cv2.CAP_PROP_BRIGHTNESS)) + if min_brightness == max_brightness: + self._cap.set(cv2.CAP_PROP_BRIGHTNESS, 0) + min_brightness = int(self._cap.get(cv2.CAP_PROP_BRIGHTNESS)) + self.add_scale_setting(name='brightness', + lowest=min_brightness, + highest=max_brightness, + getter=self._get_brightness, + setter=self._set_brightness) + + self._cap.set(cv2.CAP_PROP_CONTRAST, 99999) + max_contrast = int(self._cap.get(cv2.CAP_PROP_CONTRAST)) + self._cap.set(cv2.CAP_PROP_CONTRAST, -99999) + min_contrast = int(self._cap.get(cv2.CAP_PROP_CONTRAST)) + if min_contrast == max_contrast: + self._cap.set(cv2.CAP_PROP_CONTRAST, 0) + min_contrast = int(self._cap.get(cv2.CAP_PROP_CONTRAST)) + self.add_scale_setting(name='contrast', + lowest=min_contrast, + highest=max_contrast, + getter=self._get_contrast, + setter=self._set_contrast) + + self._cap.set(cv2.CAP_PROP_HUE, 99999) + max_hue = int(self._cap.get(cv2.CAP_PROP_HUE)) + self._cap.set(cv2.CAP_PROP_HUE, -99999) + min_hue = int(self._cap.get(cv2.CAP_PROP_HUE)) + if min_hue == max_hue: + self._cap.set(cv2.CAP_PROP_HUE, 0) + min_hue = int(self._cap.get(cv2.CAP_PROP_HUE)) + self.add_scale_setting(name='hue', + lowest=min_hue, + highest=max_hue, + getter=self._get_hue, + setter=self._set_hue) + + self._cap.set(cv2.CAP_PROP_SATURATION, 99999) + max_saturation = int(self._cap.get(cv2.CAP_PROP_SATURATION)) + self._cap.set(cv2.CAP_PROP_SATURATION, -99999) + min_saturation = int(self._cap.get(cv2.CAP_PROP_SATURATION)) + if min_saturation == max_saturation: + self._cap.set(cv2.CAP_PROP_SATURATION, 0) + min_saturation = int(self._cap.get(cv2.CAP_PROP_SATURATION)) + self.add_scale_setting(name='saturation', + lowest=min_saturation, + highest=max_saturation, + getter=self._get_saturation, + setter=self._set_saturation) + # Setting the kwargs if any self.set_all(**kwargs) @@ -74,3 +126,43 @@ def close(self) -> None: if self._cap is not None: self.log(logging.INFO, "Closing the image stream from the camera") self._cap.release() + + def _get_brightness(self) -> int: + """Gets the image brightness.""" + + return int(self._cap.get(cv2.CAP_PROP_BRIGHTNESS)) + + def _get_contrast(self) -> int: + """Gets the image contrast.""" + + return int(self._cap.get(cv2.CAP_PROP_CONTRAST)) + + def _get_hue(self) -> int: + """Gets the image hue.""" + + return int(self._cap.get(cv2.CAP_PROP_HUE)) + + def _get_saturation(self) -> int: + """Gets the image saturation.""" + + return int(self._cap.get(cv2.CAP_PROP_SATURATION)) + + def _set_brightness(self, brightness: int) -> None: + """Sets the image brightness.""" + + self._cap.set(cv2.CAP_PROP_BRIGHTNESS, brightness) + + def _set_contrast(self, contrast: int) -> None: + """Sets the image contrast.""" + + self._cap.set(cv2.CAP_PROP_CONTRAST, contrast) + + def _set_hue(self, hue: int) -> None: + """Sets the image hue.""" + + self._cap.set(cv2.CAP_PROP_HUE, hue) + + def _set_saturation(self, saturation: int) -> None: + """Sets the image saturation.""" + + self._cap.set(cv2.CAP_PROP_SATURATION, saturation) From 2d80167945585def033aba2e55f182e997686fd6 Mon Sep 17 00:00:00 2001 From: PIERROOOTT Date: Thu, 16 Nov 2023 11:55:02 +0100 Subject: [PATCH 08/57] refactor: get min and max value of several parameters more properly --- src/crappy/camera/opencv_camera_basic.py | 56 ++++++++++-------------- 1 file changed, 22 insertions(+), 34 deletions(-) diff --git a/src/crappy/camera/opencv_camera_basic.py b/src/crappy/camera/opencv_camera_basic.py index cba5d396..49b65814 100644 --- a/src/crappy/camera/opencv_camera_basic.py +++ b/src/crappy/camera/opencv_camera_basic.py @@ -47,55 +47,31 @@ def open(self, device_num: Optional[int] = 0, **kwargs) -> None: self.log(logging.INFO, "Opening the image stream from the camera") self._cap = cv2.VideoCapture(device_num) - self._cap.set(cv2.CAP_PROP_BRIGHTNESS, 99999) - max_brightness = int(self._cap.get(cv2.CAP_PROP_BRIGHTNESS)) - self._cap.set(cv2.CAP_PROP_BRIGHTNESS, -99999) - min_brightness = int(self._cap.get(cv2.CAP_PROP_BRIGHTNESS)) - if min_brightness == max_brightness: - self._cap.set(cv2.CAP_PROP_BRIGHTNESS, 0) - min_brightness = int(self._cap.get(cv2.CAP_PROP_BRIGHTNESS)) + min_bright, max_bright = self._get_min_max(cv2.CAP_PROP_BRIGHTNESS) self.add_scale_setting(name='brightness', - lowest=min_brightness, - highest=max_brightness, + lowest=min_bright, + highest=max_bright, getter=self._get_brightness, setter=self._set_brightness) - self._cap.set(cv2.CAP_PROP_CONTRAST, 99999) - max_contrast = int(self._cap.get(cv2.CAP_PROP_CONTRAST)) - self._cap.set(cv2.CAP_PROP_CONTRAST, -99999) - min_contrast = int(self._cap.get(cv2.CAP_PROP_CONTRAST)) - if min_contrast == max_contrast: - self._cap.set(cv2.CAP_PROP_CONTRAST, 0) - min_contrast = int(self._cap.get(cv2.CAP_PROP_CONTRAST)) + min_cont, max_cont = self._get_min_max(cv2.CAP_PROP_CONTRAST) self.add_scale_setting(name='contrast', - lowest=min_contrast, - highest=max_contrast, + lowest=min_cont, + highest=max_cont, getter=self._get_contrast, setter=self._set_contrast) - self._cap.set(cv2.CAP_PROP_HUE, 99999) - max_hue = int(self._cap.get(cv2.CAP_PROP_HUE)) - self._cap.set(cv2.CAP_PROP_HUE, -99999) - min_hue = int(self._cap.get(cv2.CAP_PROP_HUE)) - if min_hue == max_hue: - self._cap.set(cv2.CAP_PROP_HUE, 0) - min_hue = int(self._cap.get(cv2.CAP_PROP_HUE)) + min_hue, max_hue = self._get_min_max(cv2.CAP_PROP_HUE) self.add_scale_setting(name='hue', lowest=min_hue, highest=max_hue, getter=self._get_hue, setter=self._set_hue) - self._cap.set(cv2.CAP_PROP_SATURATION, 99999) - max_saturation = int(self._cap.get(cv2.CAP_PROP_SATURATION)) - self._cap.set(cv2.CAP_PROP_SATURATION, -99999) - min_saturation = int(self._cap.get(cv2.CAP_PROP_SATURATION)) - if min_saturation == max_saturation: - self._cap.set(cv2.CAP_PROP_SATURATION, 0) - min_saturation = int(self._cap.get(cv2.CAP_PROP_SATURATION)) + min_sat, max_sat = self._get_min_max(cv2.CAP_PROP_SATURATION) self.add_scale_setting(name='saturation', - lowest=min_saturation, - highest=max_saturation, + lowest=min_sat, + highest=max_sat, getter=self._get_saturation, setter=self._set_saturation) @@ -127,6 +103,18 @@ def close(self) -> None: self.log(logging.INFO, "Closing the image stream from the camera") self._cap.release() + def _get_min_max(self, prop_id: int) -> Tuple[int, int]: + """Gets the min and max values of a parameter.""" + + self._cap.set(prop_id, 99999) + max_ = int(self._cap.get(prop_id)) + self._cap.set(prop_id, -99999) + min_ = int(self._cap.get(prop_id)) + if min_ == max_: + self._cap.set(prop_id, 0) + min_ = int(self._cap.get(prop_id)) + return min_, max_ + def _get_brightness(self) -> int: """Gets the image brightness.""" From 0d0760b31421375d20a8f6fcf538e96f75435e4b Mon Sep 17 00:00:00 2001 From: Antoine Weisrock Date: Thu, 16 Nov 2023 12:19:35 +0100 Subject: [PATCH 09/57] docs: add collapse menus to hide long code sections --- docs/source/conf.py | 3 +- docs/source/requirements.txt | 3 +- .../tutorials/complex_custom_objects.rst | 18 +++-- docs/source/tutorials/custom_objects.rst | 80 +++++++++++++------ 4 files changed, 73 insertions(+), 31 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 6c79034a..e02f5877 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -52,7 +52,8 @@ 'sphinx.ext.mathjax', 'sphinx.ext.duration', 'sphinx_copybutton', - 'sphinx_tabs.tabs' + 'sphinx_tabs.tabs', + 'sphinx_toolbox.collapse' ] # Tabs settings diff --git a/docs/source/requirements.txt b/docs/source/requirements.txt index eec3eb1b..cc81a761 100644 --- a/docs/source/requirements.txt +++ b/docs/source/requirements.txt @@ -1,4 +1,5 @@ numpy==1.24.3 sphinx_copybutton==0.5.2 sphinx-tabs==3.4.1 -sphinx-rtd-theme==1.3.0 \ No newline at end of file +sphinx-rtd-theme==1.3.0 +sphinx-toolbox==3.5.0 \ No newline at end of file diff --git a/docs/source/tutorials/complex_custom_objects.rst b/docs/source/tutorials/complex_custom_objects.rst index 80c63348..241ebc82 100644 --- a/docs/source/tutorials/complex_custom_objects.rst +++ b/docs/source/tutorials/complex_custom_objects.rst @@ -92,9 +92,13 @@ clearer how to create a custom Generator Path and how to handle the conditions. This example generates a square wave, whose duty cycle can be either fixed or controlled by the value of an input label : -.. literalinclude:: /downloads/complex_custom_objects/custom_path.py - :language: python - :emphasize-lines: 35, 40-41, 49, 52-53 +.. collapse:: (Expand to see the full code) + + .. literalinclude:: /downloads/complex_custom_objects/custom_path.py + :language: python + :emphasize-lines: 35, 40-41, 49, 52-53 + +| .. Note:: To run this example, you'll need to have the :mod:`matplotlib` and *scipy* @@ -672,8 +676,12 @@ that is the final object called by the user in its script. Based on these development, here is a final runnable code performing eye detection and adding the detected eyes on the displayed images : -.. literalinclude:: /downloads/complex_custom_objects/custom_camera_block.py - :language: python +.. collapse:: (Expand to see the full code) + + .. literalinclude:: /downloads/complex_custom_objects/custom_camera_block.py + :language: python + +| .. Note:: To run this example, you'll need to have the *opencv-python* and *Pillow* diff --git a/docs/source/tutorials/custom_objects.rst b/docs/source/tutorials/custom_objects.rst index 82fbdf00..e73fc1f1 100644 --- a/docs/source/tutorials/custom_objects.rst +++ b/docs/source/tutorials/custom_objects.rst @@ -120,8 +120,12 @@ consecutive calls is exactly what was desired when using classes as Modifiers ! The two examples presented in this section can finally be merged into a single big one : -.. literalinclude:: /downloads/custom_objects/custom_modifier.py - :language: python +.. collapse:: (Expand to see the full code) + + .. literalinclude:: /downloads/custom_objects/custom_modifier.py + :language: python + +| You can :download:`download this custom Modifier example ` to run it locally on your @@ -282,8 +286,12 @@ the current one, as well as the :py:`get_position` method since the position is also measurable. Now that the Actuator is defined, it is time to add some context to make it run : -.. literalinclude:: /downloads/custom_objects/custom_actuator.py - :language: python +.. collapse:: (Expand to see the full code) + + .. literalinclude:: /downloads/custom_objects/custom_actuator.py + :language: python + +| .. Note:: To run this example, you'll need to have the :mod:`matplotlib` Python module @@ -385,8 +393,12 @@ called, it simply returns these two values as well as a timestamp. When, :py:`set_cmd` is called, it expects two arguments and sets their values as the new stored values. Let's now integrate the InOut into a runnable code : -.. literalinclude:: /downloads/custom_objects/custom_inout.py - :language: python +.. collapse:: (Expand to see the full code) + + .. literalinclude:: /downloads/custom_objects/custom_inout.py + :language: python + +| .. Note:: To run this example, you'll need to have the :mod:`matplotlib` Python module @@ -492,9 +504,13 @@ you. A short example should help you understand it, it's not as difficult as it first seems ! The following example is the continuation of the one presented in the previous sub-section : -.. literalinclude:: /downloads/custom_objects/custom_inout_streamer.py - :language: python - :emphasize-lines: 5, 25-35, 52, 54-55, 61-62 +.. collapse:: (Expand to see the full code) + + .. literalinclude:: /downloads/custom_objects/custom_inout_streamer.py + :language: python + :emphasize-lines: 5, 25-35, 52, 54-55, 61-62 + +| .. Note:: To run this example, you'll need to have the :mod:`matplotlib` Python module @@ -597,10 +613,14 @@ should look like in an actual script, inspired from an `example available on GitHub `_ : -.. literalinclude:: /downloads/custom_objects/custom_camera.py - :language: python - :emphasize-lines: 8-41 - :lines: 1-17, 52-76, 86-97 +.. collapse:: (Expand to see the full code) + + .. literalinclude:: /downloads/custom_objects/custom_camera.py + :language: python + :emphasize-lines: 8-41 + :lines: 1-17, 52-76, 86-97 + +| In this first example, the Camera object generates a random image with several settings that can be adjusted in the :meth:`~crappy.camera.Camera.__init__` @@ -643,10 +663,14 @@ internally like any other attribute. Every setting can be accessed by calling spaces. Let's now modify the first example to include a better setting management : -.. literalinclude:: /downloads/custom_objects/custom_camera.py - :language: python - :emphasize-lines: 13, 15-42, 69-71, 73-75 - :lines: 1-12, 21-97 +.. collapse:: (Expand to see the full code) + + .. literalinclude:: /downloads/custom_objects/custom_camera.py + :language: python + :emphasize-lines: 13, 15-42, 69-71, 73-75 + :lines: 1-12, 21-97 + +| .. Note:: To run this example, you'll need to have the *opencv-python*, @@ -787,9 +811,13 @@ provides this functionality using MQTT. The demo Block is really not advanced enough to be distributed with Crappy, but it will do just fine for this tutorial ! Here is the full code : -.. literalinclude:: /downloads/custom_objects/custom_block.py - :language: python - :emphasize-lines: 13-22, 46, 97, 127 +.. collapse:: (Expand to see the full code) + + .. literalinclude:: /downloads/custom_objects/custom_block.py + :language: python + :emphasize-lines: 13-22, 46, 97, 127 + +| .. Note:: To run this example, you'll need to have the :mod:`matplotlib` Python module @@ -908,10 +936,14 @@ meaning : In the presented example, you may have recognized a few of the presented attributes. They are highlighted here for convenience : -.. literalinclude:: /downloads/custom_objects/custom_block.py - :language: python - :emphasize-lines: 27-29, 49, 74 - :lines: 1-96 +.. collapse:: (Expand to see the full code) + + .. literalinclude:: /downloads/custom_objects/custom_block.py + :language: python + :emphasize-lines: 27-29, 49, 74 + :lines: 1-96 + +| There is not much more to say about the available attributes of the Block that you can use, you'll see for yourself which ones you need and which ones you From 209c364dc9d3e798e67dd628cdba55a4813b575d Mon Sep 17 00:00:00 2001 From: Antoine Weisrock Date: Thu, 16 Nov 2023 14:41:00 +0100 Subject: [PATCH 10/57] docs: change global author and copyright of documentation --- docs/source/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index e02f5877..3290a406 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -21,7 +21,7 @@ # -- Project information ----------------------------------------------------- project = 'Crappy' -author = 'Antoine Weisrock' +author = 'LaMcube and contributors' copyright = f"{strftime('%Y', gmtime())}, {author}" version = match(r'\d+\.\d+', __version__).group() release = __version__ From 61c3d65456e9fb20de9673865c8d63e3db0d0401 Mon Sep 17 00:00:00 2001 From: Antoine Weisrock Date: Thu, 16 Nov 2023 14:41:29 +0100 Subject: [PATCH 11/57] docs: add a sectionauthor for each section of the documentation --- docs/source/api.rst | 2 ++ docs/source/citing.rst | 2 ++ docs/source/conf.py | 2 +- docs/source/developers.rst | 6 ++++++ docs/source/features.rst | 8 ++++++++ docs/source/index.rst | 2 ++ docs/source/installation.rst | 10 ++++++++++ docs/source/troubleshooting.rst | 2 ++ docs/source/tutorials/complex_custom_objects.rst | 14 ++++++++++++++ docs/source/tutorials/custom_objects.rst | 12 ++++++++++++ docs/source/tutorials/getting_started.rst | 10 ++++++++++ docs/source/tutorials/more_complexity.rst | 16 ++++++++++++++++ docs/source/what_is_crappy.rst | 6 ++++++ 13 files changed, 91 insertions(+), 1 deletion(-) diff --git a/docs/source/api.rst b/docs/source/api.rst index 4c85ea8c..39213990 100644 --- a/docs/source/api.rst +++ b/docs/source/api.rst @@ -2,6 +2,8 @@ API === +.. sectionauthor:: Antoine Weisrock + This section contains the inline documentation of all the objects of the module Crappy. In particular, a description is given for each possible argument of the classes and methods. The only way to get an even finer understanding of an diff --git a/docs/source/citing.rst b/docs/source/citing.rst index ba9a0d11..9f5e094b 100644 --- a/docs/source/citing.rst +++ b/docs/source/citing.rst @@ -2,6 +2,8 @@ Citing Crappy ============== +.. sectionauthor:: Antoine Weisrock + If Crappy has been of help in your research, please reference it in your academic publications by citing one or both of the following articles : diff --git a/docs/source/conf.py b/docs/source/conf.py index 3290a406..38ee9dd6 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -37,7 +37,7 @@ # Including undocumented features autodoc_default_options = {'undoc-members': True} # The codeauthor and sectionauthor directives do produce output -show_authors = True +show_authors = False # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom diff --git a/docs/source/developers.rst b/docs/source/developers.rst index 71f5b59a..f02b7258 100644 --- a/docs/source/developers.rst +++ b/docs/source/developers.rst @@ -9,6 +9,8 @@ Developers information Contributing to Crappy ---------------------- +.. sectionauthor:: Antoine Weisrock + If you want to help developing Crappy with us, we'll be more than happy to welcome you in the community ! Here you'll find some practical information on **how Crappy works under the hood, and a few guidelines for contributors**. @@ -40,6 +42,8 @@ directly committed to. Technical description of Crappy ------------------------------- +.. sectionauthor:: Antoine Weisrock + .. note:: This is a very simplified overview of how the module actually works. Only the main ideas are presented, and many technical aspects are omitted. Reading the @@ -248,6 +252,8 @@ the examples. Detailed runtime sequence of Crappy ----------------------------------- +.. sectionauthor:: Antoine Weisrock + Crappy's main strength lies in the use of massive parallelization to maximize the performance of the module. Unfortunately, this means we had to cope with Python's notoriously complex :mod:`multiprocessing` architecture, and come up diff --git a/docs/source/features.rst b/docs/source/features.rst index caa3c8a1..f58483c6 100644 --- a/docs/source/features.rst +++ b/docs/source/features.rst @@ -2,6 +2,8 @@ Current functionalities ======================= +.. sectionauthor:: Antoine Weisrock + On this page are listed all the objects currently distributed with Crappy and exposed to the users. Information on how to use them can be found in the :ref:`Tutorials`, as well as guidelines for creating your own objects. For most @@ -14,6 +16,8 @@ can click on its name to open the complete documentation given in the Functionalities (Blocks) ------------------------ +.. sectionauthor:: Antoine Weisrock + The Blocks are the base bricks of Crappy, that fulfill various functions. In the tutorials, you can learn more about :ref:`how to use Blocks <1. Understanding Crappy's syntax>` and :ref:`how to create new Blocks @@ -366,6 +370,8 @@ Others Supported hardware (Cameras, InOuts, Actuators) ----------------------------------------------- +.. sectionauthor:: Antoine Weisrock + Supported Cameras +++++++++++++++++ @@ -810,6 +816,8 @@ LaMcube-specific hardware On-the-fly data modification (Modifiers) ---------------------------------------- +.. sectionauthor:: Antoine Weisrock + - :ref:`Demux` Takes the signal returned by a streaming :ref:`IOBlock` and transforms it diff --git a/docs/source/index.rst b/docs/source/index.rst index 952fb673..b3d7e34f 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -2,6 +2,8 @@ Crappy ====== +.. sectionauthor:: Antoine Weisrock + Crappy is a **Python module** that aims to provide easy-to-use and open-source tools for **command and data acquisition on complex experimental setups**. It is designed to let users drive most setups in **less than 100 lines of code**. diff --git a/docs/source/installation.rst b/docs/source/installation.rst index 757f267c..b99a9654 100644 --- a/docs/source/installation.rst +++ b/docs/source/installation.rst @@ -9,6 +9,8 @@ Installation Requirements ------------ +.. sectionauthor:: Antoine Weisrock + Crappy was successfully installed and tested on **Linux** (Ubuntu 18.04 and higher), **Windows** (8 and higher) and **MacOS** (Sierra and higher). It was also successfully installed on **Raspberry Pi** 3B+ and 4B. As a Python module, @@ -45,6 +47,8 @@ functionalities (this list is not exhaustive) : 1. Check your Python version ---------------------------- +.. sectionauthor:: Antoine Weisrock + Before installing Crappy, first check that you have **a compatible version of** **Python** installed. You can get the current version of Python by running :shell:`python --version` in a console. The version should then be displayed, @@ -67,6 +71,8 @@ are beyond the scope of this documentation. 2. Deploy a virtual environment (optional) ------------------------------------------ +.. sectionauthor:: Antoine Weisrock + It is **recommended** to install Crappy in a `virtual environment `_, to avoid conflicts with other Python packages installed at the user or system level. This step is however not @@ -85,6 +91,8 @@ console, containing an independent install of Python. 3. Install Crappy ----------------- +.. sectionauthor:: Antoine Weisrock + Once you have a compatible version of Python installed, and after optionally setting up a virtual environment, you're **ready to install Crappy**. A single line of code is necessary to install Crappy : @@ -143,6 +151,8 @@ you would need to use along with Crappy. For example : 4. Check your install --------------------- +.. sectionauthor:: Antoine Weisrock + Once you have installed Crappy, you can **run a few checks** to make sure it works fine on your system. First, try to simply import it : diff --git a/docs/source/troubleshooting.rst b/docs/source/troubleshooting.rst index a971af46..d06932db 100644 --- a/docs/source/troubleshooting.rst +++ b/docs/source/troubleshooting.rst @@ -2,6 +2,8 @@ Troubleshooting =============== +.. sectionauthor:: Antoine Weisrock + When encountering a problem with Crappy, please **first carefully read the** **present documentation**. Most of the difficulties users may face come from an incorrect use of Crappy. If you cannot find an answer in the documentation, you diff --git a/docs/source/tutorials/complex_custom_objects.rst b/docs/source/tutorials/complex_custom_objects.rst index 241ebc82..c342b015 100644 --- a/docs/source/tutorials/complex_custom_objects.rst +++ b/docs/source/tutorials/complex_custom_objects.rst @@ -2,6 +2,8 @@ More about custom objects in Crappy =================================== +.. sectionauthor:: Antoine Weisrock + .. role:: py(code) :language: python :class: highlight @@ -15,6 +17,8 @@ understanding of the module, or users with a specific need. 1. Custom Generator Paths ------------------------- +.. sectionauthor:: Antoine Weisrock + Starting from version 2.0.0, **it is now possible for users to create their** **own** :ref:`Generator Paths` ! There are two reasons why this possibility was added so late in the module. First, we're not certain that there is a need for @@ -146,6 +150,8 @@ labels). 2. More about custom InOuts --------------------------- +.. sectionauthor:: Antoine Weisrock + In addition to what was described in the tutorial section about :ref:`how to create custom InOut objects <3. Custom InOuts>`, there is one more minor feature that the :ref:`In / Out` possess and that is worth describing in the @@ -184,6 +190,8 @@ users to override it. 3. More about custom Actuators ------------------------------ +.. sectionauthor:: Antoine Weisrock + In the tutorial section about :ref:`how to create custom Actuator objects <2. Custom Actuators>`, then entire speed management aspect in :py:`position` mode was left out. **In this section, we're going to cover in more details** @@ -228,6 +236,8 @@ examples/blocks>`_. 4. More about custom Cameras ---------------------------- +.. sectionauthor:: Antoine Weisrock + Because image acquisition is such a complex topic, the :class:`~crappy.camera.Camera` object is by far the richest of the classes interfacing with hardware in Crappy. For that reason, not all of its features @@ -389,6 +399,8 @@ will change in future releases ! 5. Custom Camera Blocks ----------------------- +.. sectionauthor:: Antoine Weisrock + On the previous tutorial page, :ref:`a section <5. Custom Blocks>` was dedicated to the instantiation of custom :ref:`Blocks`. Always moving one step further into customization, we're going to see in this section how you can @@ -702,6 +714,8 @@ aspects in future releases. 6. Sharing custom objects and Blocks ------------------------------------ +.. sectionauthor:: Antoine Weisrock + You have been through all the tutorials of Crappy and have now become a master at creating and using your own objects, and you now **want to share your** **works with other user** ? No problem ! There are several options for that, diff --git a/docs/source/tutorials/custom_objects.rst b/docs/source/tutorials/custom_objects.rst index e73fc1f1..a0efaf7f 100644 --- a/docs/source/tutorials/custom_objects.rst +++ b/docs/source/tutorials/custom_objects.rst @@ -2,6 +2,8 @@ Creating and using custom objects in Crappy =========================================== +.. sectionauthor:: Antoine Weisrock + .. role:: py(code) :language: python :class: highlight @@ -19,6 +21,8 @@ custom object instantiation. 1. Custom Modifiers ------------------- +.. sectionauthor:: Antoine Weisrock + The first type of custom objects that we'll cover here are the :ref:`Modifiers`, because they are by far the simplest objects ! **The** **Modifiers come in use in a variety of situations, and it is often required** @@ -145,6 +149,8 @@ custom Modifiers ! They stand after all among the simplest objects in Crappy. 2. Custom Actuators ------------------- +.. sectionauthor:: Antoine Weisrock + After introducing how custom Modifiers work in the first section, this second section will focus on the use of custom :ref:`Actuators`. Knowing how to add and use your own :class:`~crappy.actuator.Actuator` objects in Crappy is @@ -315,6 +321,8 @@ see how the implementation of real-life Actuators looks like. 3. Custom InOuts ---------------- +.. sectionauthor:: Antoine Weisrock + Creating custom :ref:`In / Out` objects is extremely similar to creating custom :ref:`Actuators`, so make sure to first read and understand the previous section first ! Just like for Actuators, **anyone who wants to drive their** @@ -543,6 +551,8 @@ and in the `InOuts distributed with Crappy 4. Custom Cameras ----------------- +.. sectionauthor:: Antoine Weisrock + Now that you're getting familiar with the instantiation of custom objects in Crappy, adding your own :ref:`Cameras` to Crappy should not present any particular difficulty. **The camera management is one of the big strengths of** @@ -714,6 +724,8 @@ how they are implemented. 5. Custom Blocks ---------------- +.. sectionauthor:: Antoine Weisrock + For the last section of this tutorial page, we are going to **cover the most** **difficult but also most interesting and powerful object that you can** **customize in Crappy : the** :ref:`Block`. Unlike the other objects introduced diff --git a/docs/source/tutorials/getting_started.rst b/docs/source/tutorials/getting_started.rst index e0c1c422..3603c9ae 100644 --- a/docs/source/tutorials/getting_started.rst +++ b/docs/source/tutorials/getting_started.rst @@ -2,6 +2,8 @@ Getting started : writing scripts in Crappy =========================================== +.. sectionauthor:: Antoine Weisrock + .. role:: py(code) :language: python :class: highlight @@ -13,6 +15,8 @@ level in Python is required, don't worry ! 0. General concepts ------------------- +.. sectionauthor:: Antoine Weisrock + This first section of the tutorials introduces the very basic concepts of Crappy. No code is involved for now, it only describes the general way data can flow in Crappy. @@ -54,6 +58,8 @@ Block 3 that's using it ! 1. Understanding Crappy's syntax -------------------------------- +.. sectionauthor:: Antoine Weisrock + In this second part of the tutorials, we're going to **write step-by-step an** **actual script for Crappy** that you can run locally on your machine ! All the following tutorials will also follow the same principle. If a script would not @@ -189,6 +195,8 @@ there's still much more to learn in the following sections ! 2. The most used Blocks ----------------------- +.. sectionauthor:: Antoine Weisrock + In this third section, you will **learn how to handle the most used Blocks of** **Crappy**. These Blocks are all essential, and you'll come across at least one of them in most scripts. For an extensive list of all the implemented Blocks, @@ -509,6 +517,8 @@ found in the `examples folder on GitHub 3. Properly stopping a script ----------------------------- +.. sectionauthor:: Antoine Weisrock + In the previous sections, several different ways to stop a script in Crappy have been presented. In this section, **you will learn about the best** **practices for stopping Crappy** and the right objects to use. diff --git a/docs/source/tutorials/more_complexity.rst b/docs/source/tutorials/more_complexity.rst index 1baa5bc8..0af53b41 100644 --- a/docs/source/tutorials/more_complexity.rst +++ b/docs/source/tutorials/more_complexity.rst @@ -2,6 +2,8 @@ Towards more complexity ======================= +.. sectionauthor:: Antoine Weisrock + .. role:: py(code) :language: python :class: highlight @@ -15,6 +17,8 @@ complexity. So, make sure to read this page until the end ! 1. Using feedback loops ----------------------- +.. sectionauthor:: Antoine Weisrock + In the previous tutorials page, we only used linear data flow patterns. Here, we're going to **introduce the concept of feedback loops in a script**. The main idea is that although :ref:`Links` are unidirectional, it is totally @@ -71,6 +75,8 @@ how the PID will react. 2. Using Modifiers ------------------ +.. sectionauthor:: Antoine Weisrock + One of Crappy's most powerful features is the possibility to **use** :ref:`Modifiers` **to alter the data flowing through the** :ref:`Links`. The rationale behind is that the data that a Block outputs might not always be @@ -130,6 +136,8 @@ your machine. The Modifiers distributed with Crappy are also showcased in the 3. Advanced Generator condition ------------------------------- +.. sectionauthor:: Antoine Weisrock + In :ref:`a previous section <2.a. The Generator Block and its Paths>`, the :ref:`Generator` Block and its :ref:`Generator Paths` were introduced. In that section, two possible syntax were given for the :py:`'condition'` key of a @@ -194,6 +202,8 @@ the Generator, you are now ready to use this block to its full extent ! 4. Dealing with streams ----------------------- +.. sectionauthor:: Antoine Weisrock + In :ref:`the tutorial section dedicated to IOBlocks <2.e. The IOBlock Block>`, only the regular usage mode of the :ref:`IOBlock` was presented. In this mode, the data points are acquired from the :ref:`In / Out` object one by one, which @@ -260,6 +270,8 @@ if you would have trouble using it ! 5. Writing scripts efficiently ------------------------------ +.. sectionauthor:: Antoine Weisrock + Because Crappy requires script with a specific syntax to run, users may forget that they can still make use of Python's great flexibility and tools even inside scripts for Crappy ! This section is just a short and surely not @@ -376,6 +388,8 @@ Using :mod:`pathlib`, write instead : 6. Using Crappy objects outside of a Crappy test ------------------------------------------------ +.. sectionauthor:: Antoine Weisrock + In the new section of the tutorial, let's see how you can use the classes distributed with Crappy to interact freely with hardware outside the context of a Crappy test (i.e. without calling :ref:`crappy.start()` or an equivalent @@ -437,6 +451,8 @@ used here because it is more visual. 7. Advanced control over the runtime ------------------------------------ +.. sectionauthor:: Antoine Weisrock + For the last section of this tutorial page, let's see how you can achieve a finer-grained control over Crappy's runtime. **There are two ways to control** **Crappy in a more accurate way : passing arguments to** :ref:`crappy.start()`, diff --git a/docs/source/what_is_crappy.rst b/docs/source/what_is_crappy.rst index 769262c3..752d068b 100644 --- a/docs/source/what_is_crappy.rst +++ b/docs/source/what_is_crappy.rst @@ -10,6 +10,8 @@ What is Crappy ? Overview -------- +.. sectionauthor:: Antoine Weisrock + CRAPPY is an acronym and stands for **C**\ommand and **R**\eal-time **A**\cquisition in **P**\arallelized **PY**\thon. @@ -31,6 +33,8 @@ requires to run experimental tests. Key features of Crappy ---------------------- +.. sectionauthor:: Antoine Weisrock + - **open-source** : It is natural for us to make our work available to anyone, and to keep it open to outside contributions. All the code base is freely hosted on GitHub. @@ -60,6 +64,8 @@ Key features of Crappy Is Crappy for me ? ------------------ +.. sectionauthor:: Antoine Weisrock + Crappy is **the right solution** for you if : - You want to drive sensors and actuators in a synchronized and parallelized From 17d9c1db8b444a5c440646b7932d234ccd9963c5 Mon Sep 17 00:00:00 2001 From: PIERROOOTT Date: Thu, 16 Nov 2023 17:38:33 +0100 Subject: [PATCH 12/57] feat: create a parent class that handles parsing of v4l2-ctl --- src/crappy/camera/_v4l2_base.py | 293 ++++++++++++++++++++++++++++++++ 1 file changed, 293 insertions(+) create mode 100644 src/crappy/camera/_v4l2_base.py diff --git a/src/crappy/camera/_v4l2_base.py b/src/crappy/camera/_v4l2_base.py new file mode 100644 index 00000000..7612fd78 --- /dev/null +++ b/src/crappy/camera/_v4l2_base.py @@ -0,0 +1,293 @@ +from __future__ import annotations +from typing import Tuple, Optional, Callable, Union +from re import findall, search, finditer, split, Match +from dataclasses import dataclass +import logging +from subprocess import run +from multiprocessing import current_process + + +@dataclass +class Parameter: + """A class for the different parameters the user can adjust.""" + + name: str + type: str + min: Optional[str] = None + max: Optional[str] = None + step: Optional[str] = None + default: Optional[str] = None + value: Optional[str] = None + flags: Optional[str] = None + options: Optional[Tuple[str, ...]] = None + + @classmethod + def parse_info(cls, match: Match) -> Parameter: + """Instantiates the class Parameter, according to the information + collected with v4l2-ctl. + + Args: + match: Match object returned by successful matches of the regex with + a string. + + Returns: + The instantiated class. + """ + + return cls(name=match.group(1), + type=match.group(2), + min=match.group(4) if match.group(4) else None, + max=match.group(6) if match.group(6) else None, + step=match.group(8) if match.group(8) else None, + default=match.group(10) if match.group(10) else None, + value=match.group(11), + flags=match.group(13) if match.group(13) else None) + + def add_options(self, match: Match) -> None: + """Adds the different possible options for a menu parameter. + + Args: + match: Match object returned by successful matches of the regex with + a string. + """ + + menu_info = match.group(1) + menu_values = match.group(2) + menu_name = search(r'(\w+) \w+ \(menu\)', menu_info).group(1) + if self.name == menu_name: + options = findall(r'\d+: .+?(?=\n|$)', menu_values) + num_options = findall(r'(\d+): .+?(?=\n|$)', menu_values) + self.options = tuple(options) + for i in range(len(num_options)): + if self.default == num_options[i]: + self.default = options[i] + + +class V4L2: + """A class for getting parameters available in a camera by using v4l2-utils. + """ + + def __init__(self): + """Simply initializes the instance attributes.""" + + self._parameters = list() + self._formats = list() + self._logger: Optional[logging.Logger] = None + + def _get_param(self, device: Optional[Union[str, int]]) -> None: + """Extracts the different parameters and their information + by parsing v4l2-ctl with regex.""" + + # Trying to run v4l2-ctl to get the available settings + command = ['v4l2-ctl', '-L'] if device is None \ + else ['v4l2-ctl', '-d', str(device), '-L'] + self.log(logging.INFO, f"Getting the available image settings with " + f"command {command}") + try: + check = run(command, capture_output=True, text=True) + except FileNotFoundError: + check = None + check = check.stdout if check is not None else '' + + # Regex to extract the different parameters and their information + param_pattern = (r'(\w+)\s+0x\w+\s+\((\w+)\)\s+:\s*' + r'(min=(-?\d+)\s+)?' + r'(max=(-?\d+)\s+)?' + r'(step=(\d+)\s+)?' + r'(default=(-?\d+)\s+)?' + r'value=(-?\d+)\s*' + r'(flags=([^\\n]+))?') + + # Extract the different parameters and their information + matches = finditer(param_pattern, check) + for match in matches: + self._parameters.append(Parameter.parse_info(match)) + + # Regex to extract the different options in a menu + menu_options = finditer( + r'(\w+ \w+ \(menu\))([\s\S]+?)(?=\n\s*\w+ \w+ \(.+?\)|$)', check) + + # Extract the different options + for menu_option in menu_options: + for param in self._parameters: + param.add_options(menu_option) + + def _get_available_formats(self, device: Optional[Union[str, int]]) -> None: + """Extracts the different formats available + by parsing v4l2-ctl with regex.""" + + # Trying to run v4l2-ctl to get the available formats + command = ['v4l2-ctl', '--list-formats-ext'] if device is None \ + else ['v4l2-ctl', '-d', str(device), '--list-formats-ext'] + self.log(logging.INFO, f"Getting the available image formats with " + f"command {command}") + try: + check = run(command, capture_output=True, text=True) + except FileNotFoundError: + check = None + check = check.stdout if check is not None else '' + + # Splitting the returned string to isolate each encoding + if findall(r'\[\d+]', check): + check = split(r'\[\d+]', check)[1:] + elif findall(r'Pixel\sFormat', check): + check = split(r'Pixel\sFormat', check)[1:] + else: + check = [] + + if check: + for img_format in check: + # For each encoding, finding its name + name, *_ = search(r"'(\w+)'", img_format).groups() + sizes = findall(r'\d+x\d+', img_format) + fps_sections = split(r'\d+x\d+', img_format)[1:] + + # For each name, finding the available sizes + for size, fps_section in zip(sizes, fps_sections): + fps_list = findall(r'\((\d+\.\d+)\sfps\)', fps_section) + for fps in fps_list: + self._formats.append(f'{name} {size} ({fps} fps)') + + def _add_setter(self, + name: str, + device: Optional[Union[int, str]]) -> Callable: + """Creates a setter function for a setting named 'name'. + Args: + name: Name of the setting. + + Returns: + The setter function. + """ + + def setter(value) -> None: + """The method to set the value of a setting running v4l2-ctl. + """ + + if isinstance(value, str): + # The value to set the menu parameter is just the int + # at the beginning the string + value = search(r'(\d+): ', value).group(1) + if device is not None: + command = ['v4l2-ctl', '-d', str(device), '--set-ctrl', + f'{name}={value}'] + else: + command = ['v4l2-ctl', '--set-ctrl', f'{name}={int(value[0])}'] + self.log(logging.DEBUG, f"Setting {name} with command {command}") + run(command, capture_output=True, text=True) + else: + if device is not None: + command = ['v4l2-ctl', '-d', str(device), '--set-ctrl', + name+f'={int(value)}'] + else: + command = ['v4l2-ctl', '--set-ctrl', f'{name}={int(value)}'] + self.log(logging.DEBUG, f"Setting {name} with command {command}") + run(command, capture_output=True, text=True) + return setter + + @staticmethod + def _add_scale_getter(name: str, + device: Optional[Union[int, str]]) -> Callable: + """Creates a getter function for a setting named 'name'. + Args: + name: Name of the setting. + + Returns: + The getter function. + """ + + def getter() -> int: + """The method to get the current value of a scale setting + running v4l2-ctl. + """ + + # Trying to run v4l2-ctl to get the value + if device is not None: + command = ['v4l2-ctl', '-d', str(device), '--get-ctrl', name] + else: + command = ['v4l2-ctl', '--get-ctrl', name] + try: + value = run(command, capture_output=True, text=True).stdout + value = search(r': (-?\d+)', value).group(1) + except FileNotFoundError: + value = None + return int(value) + return getter + + @staticmethod + def _add_bool_getter(name: str, + device: Optional[Union[int, str]]) -> Callable: + """Creates a getter function for a setting named 'name'. + Args: + name: Name of the setting. + + Returns: + The getter function. + """ + + def getter() -> bool: + """The method to get the current value of a bool setting + running v4l2-ctl. + """ + + # Trying to run v4l2-ctl to get the value + if device is not None: + command = ['v4l2-ctl', '-d', str(device), '--get-ctrl', name] + else: + command = ['v4l2-ctl', '--get-ctrl', name] + try: + value = run(command, capture_output=True, text=True).stdout + value = search(r': (\d+)', value).group(1) + except FileNotFoundError: + value = None + return bool(int(value)) + return getter + + def _add_menu_getter(self, + name: str, + device: Optional[Union[int, str]]) -> Callable: + """Creates a getter function for a setting named 'name'. + Args: + name: Name of the setting. + + Returns: + The getter function. + """ + + def getter() -> str: + """The method to get the current value of a choice setting + running v4l2-ctl. + """ + + # Trying to run v4l2-ctl to get the value + if device is not None: + command = ['v4l2-ctl', '-d', str(device), '--get-ctrl', name] + else: + command = ['v4l2-ctl', '--get-ctrl', name] + try: + value = run(command, capture_output=True, text=True).stdout + value = search(r': (\d+)', value).group(1) + for param in self._parameters: + if param.name == name: + for option in param.options: + if value == search(r'(\d+):', option).group(1): + value = option + except FileNotFoundError: + value = None + return value + return getter + + def log(self, level: int, msg: str) -> None: + """Records log messages for the Modifiers. + + Also instantiates the logger when logging the first message. + + Args: + level: An :obj:`int` indicating the logging level of the message. + msg: The message to log, as a :obj:`str`. + """ + + if self._logger is None: + self._logger = logging.getLogger( + f"{current_process().name}.{type(self).__name__}") + + self._logger.log(level, msg) From 58ef49522efa31e598d48382f8ba80755a397732 Mon Sep 17 00:00:00 2001 From: PIERROOOTT Date: Thu, 16 Nov 2023 17:43:49 +0100 Subject: [PATCH 13/57] refactor: CameraGstreamerV4l2 inherits now from the V4L2 class --- src/crappy/camera/gstreamer_camera_v4l2.py | 301 +++------------------ 1 file changed, 31 insertions(+), 270 deletions(-) diff --git a/src/crappy/camera/gstreamer_camera_v4l2.py b/src/crappy/camera/gstreamer_camera_v4l2.py index 9c52b3d9..f4b9a5ca 100644 --- a/src/crappy/camera/gstreamer_camera_v4l2.py +++ b/src/crappy/camera/gstreamer_camera_v4l2.py @@ -1,16 +1,15 @@ # coding: utf-8 -from __future__ import annotations from time import time, sleep from numpy import uint8, ndarray, uint16, copy, squeeze -from typing import Tuple, Optional, Union, List, Callable +from typing import Tuple, Optional, Union, List from subprocess import Popen, PIPE, run -from re import findall, split, search, finditer, Match +from re import findall, search import logging from fractions import Fraction -from dataclasses import dataclass from .meta_camera import Camera +from ._v4l2_base import V4L2 from .._global import OptionalModule try: @@ -28,63 +27,7 @@ Gst = GstApp = OptionalModule('PyGObject') -@dataclass -class Parameter: - """A class for the different parameters the user can adjust.""" - - name: str - type: str - min: Optional[str] = None - max: Optional[str] = None - step: Optional[str] = None - default: Optional[str] = None - value: Optional[str] = None - flags: Optional[str] = None - options: Optional[Tuple[str, ...]] = None - - @classmethod - def parse_info(cls, match: Match) -> Parameter: - """Instantiates the class Parameter, according to the information - collected with v4l2-ctl. - - Args: - match: Match object returned by successful matches of the regex with - a string. - - Returns: - The instantiated class. - """ - - return cls(name=match.group(1), - type=match.group(2), - min=match.group(4) if match.group(4) else None, - max=match.group(6) if match.group(6) else None, - step=match.group(8) if match.group(8) else None, - default=match.group(10) if match.group(10) else None, - value=match.group(11), - flags=match.group(13) if match.group(13) else None) - - def add_options(self, match: Match) -> None: - """Adds the different possible options for a menu parameter. - - Args: - match: Match object returned by successful matches of the regex with - a string. - """ - - menu_info = match.group(1) - menu_values = match.group(2) - menu_name = search(r'(\w+) \w+ \(menu\)', menu_info).group(1) - if self.name == menu_name: - options = findall(r'\d+: .+?(?=\n|$)', menu_values) - num_options = findall(r'(\d+): .+?(?=\n|$)', menu_values) - self.options = tuple(options) - for i in range(len(num_options)): - if self.default == num_options[i]: - self.default = options[i] - - -class CameraGstreamer(Camera): +class CameraGstreamer(Camera, V4L2): """A class for reading images from a video device using Gstreamer in Linux. It can read images from the default video source, or a video device can be @@ -108,7 +51,8 @@ class CameraGstreamer(Camera): def __init__(self) -> None: """Simply initializes the instance attributes.""" - super().__init__() + Camera.__init__(self) + V4L2.__init__(self) Gst.init(None) self._last_frame_nr = 0 @@ -124,7 +68,6 @@ def __init__(self) -> None: self._img_depth: int = 8 self._formats: List[str] = list() self._app_sink = None - self.parameters = [] def open(self, device: Optional[Union[int, str]] = None, @@ -235,40 +178,7 @@ def open(self, # Defining the settings in case no custom pipeline was given if user_pipeline is None: - # Trying to get the available image encodings and formats - self._formats = [] - - # Trying to run v4l2-ctl to get the available formats - command = ['v4l2-ctl', '--list-formats-ext'] if device is None \ - else ['v4l2-ctl', '-d', device, '--list-formats-ext'] - self.log(logging.INFO, f"Getting the available image formats with " - f"command {command}") - try: - check = run(command, capture_output=True, text=True) - except FileNotFoundError: - check = None - check = check.stdout if check is not None else '' - - # Splitting the returned string to isolate each encoding - if findall(r'\[\d+]', check): - check = split(r'\[\d+]', check)[1:] - elif findall(r'Pixel\sFormat', check): - check = split(r'Pixel\sFormat', check)[1:] - else: - check = [] - - if check: - for img_format in check: - # For each encoding, finding its name - name, *_ = search(r"'(\w+)'", img_format).groups() - sizes = findall(r'\d+x\d+', img_format) - fps_sections = split(r'\d+x\d+', img_format)[1:] - - # For each name, finding the available sizes - for size, fps_section in zip(sizes, fps_sections): - fps_list = findall(r'\((\d+\.\d+)\sfps\)', fps_section) - for fps in fps_list: - self._formats.append(f'{name} {size} ({fps} fps)') + self._get_available_formats(self._device) # Finally, creating the parameter if applicable if self._formats: @@ -298,63 +208,37 @@ def open(self, self.add_choice_setting(name='format', choices=tuple(self._formats), setter=self._set_format) - # Trying to run v4l2-ctl to get the available settings - command = ['v4l2-ctl', '-L'] if device is None \ - else ['v4l2-ctl', '-d', device, '-L'] - self.log(logging.INFO, f"Getting the available image settings with " - f"command {command}") - try: - check = run(command, capture_output=True, text=True) - except FileNotFoundError: - check = None - check = check.stdout if check is not None else '' - - # Regex to extract the different parameters and their information - param_pattern = (r'(\w+)\s+0x\w+\s+\((\w+)\)\s+:\s*' - r'(min=(-?\d+)\s+)?' - r'(max=(-?\d+)\s+)?' - r'(step=(\d+)\s+)?' - r'(default=(-?\d+)\s+)?' - r'value=(-?\d+)\s*' - r'(flags=([^\\n]+))?') - - # Extract the different parameters and their information - matches = finditer(param_pattern, check) - for match in matches: - self.parameters.append(Parameter.parse_info(match)) - - # Regex to extract the different options in a menu - menu_options = finditer( - r'(\w+ \w+ \(menu\))([\s\S]+?)(?=\n\s*\w+ \w+ \(.+?\)|$)', check) - - # Extract the different options - for menu_option in menu_options: - for param in self.parameters: - param.add_options(menu_option) + self._get_param(self._device) # Create the different settings - for param in self.parameters: + for param in self._parameters: if not param.flags: if param.type == 'int': - self.add_scale_setting(name=param.name, - lowest=int(param.min), - highest=int(param.max), - getter=self._add_scale_getter(param.name), - setter=self._add_setter(param.name), - default=param.default, - step=int(param.step)) + self.add_scale_setting( + name=param.name, + lowest=int(param.min), + highest=int(param.max), + getter=self._add_scale_getter(param.name, self._device), + setter=self._add_setter(param.name, self._device), + default=param.default, + step=int(param.step)) + elif param.type == 'bool': - self.add_bool_setting(name=param.name, - getter=self._add_bool_getter(param.name), - setter=self._add_setter(param.name), - default=bool(int(param.default))) + self.add_bool_setting( + name=param.name, + getter=self._add_bool_getter(param.name, self._device), + setter=self._add_setter(param.name, self._device), + default=bool(int(param.default))) + elif param.type == 'menu': if param.options: - self.add_choice_setting(name=param.name, - choices=param.options, - getter=self._add_menu_getter(param.name), - setter=self._add_setter(param.name), - default=param.default) + self.add_choice_setting( + name=param.name, + choices=param.options, + getter=self._add_menu_getter(param.name, self._device), + setter=self._add_setter(param.name, self._device), + default=param.default) + else: self.log(logging.ERROR, f'The type {param.type} is not yet' f' implemented. Only int, bool and menu ' @@ -588,126 +472,3 @@ def _get_format(self) -> str: fps, *_ = search(r"Frames per second\s*:\s*(\d+.\d+)", check).groups() return f'{format_} {width}x{height} ({fps} fps)' - - def _add_setter(self, name: str) -> Callable: - """Creates a setter function for a setting named 'name'. - Args: - name: Name of the setting. - - Returns: - The setter function. - """ - - def setter(value) -> None: - """The method to set the value of a setting running v4l2-ctl. - """ - - if isinstance(value, str): - # The value to set the menu parameter is just the int - # at the beginning the string - value = search(r'(\d+): ', value).group(1) - if self._device is not None: - command = ['v4l2-ctl', '-d', self._device, '--set-ctrl', - f'{name}={value}'] - else: - command = ['v4l2-ctl', '--set-ctrl', f'{name}={int(value[0])}'] - self.log(logging.DEBUG, f"Setting {name} with command {command}") - run(command, capture_output=True, text=True) - else: - if self._device is not None: - command = ['v4l2-ctl', '-d', self._device, '--set-ctrl', - name+f'={int(value)}'] - else: - command = ['v4l2-ctl', '--set-ctrl', f'{name}={int(value)}'] - self.log(logging.DEBUG, f"Setting {name} with command {command}") - run(command, capture_output=True, text=True) - return setter - - def _add_scale_getter(self, name: str) -> Callable: - """Creates a getter function for a setting named 'name'. - Args: - name: Name of the setting. - - Returns: - The getter function. - """ - - def getter() -> int: - """The method to get the current value of a scale setting - running v4l2-ctl. - """ - - # Trying to run v4l2-ctl to get the value - if self._device is not None: - command = ['v4l2-ctl', '-d', self._device, '--get-ctrl', name] - else: - command = ['v4l2-ctl', '--get-ctrl', name] - try: - self.log(logging.DEBUG, f"Getting {name} with command {command}") - value = run(command, capture_output=True, text=True).stdout - value = search(r': (-?\d+)', value).group(1) - except FileNotFoundError: - value = None - return int(value) - return getter - - def _add_bool_getter(self, name: str) -> Callable: - """Creates a getter function for a setting named 'name'. - Args: - name: Name of the setting. - - Returns: - The getter function. - """ - - def getter() -> bool: - """The method to get the current value of a bool setting - running v4l2-ctl. - """ - - # Trying to run v4l2-ctl to get the value - if self._device is not None: - command = ['v4l2-ctl', '-d', self._device, '--get-ctrl', name] - else: - command = ['v4l2-ctl', '--get-ctrl', name] - try: - self.log(logging.DEBUG, f"Getting {name} with command {command}") - value = run(command, capture_output=True, text=True).stdout - value = search(r': (\d+)', value).group(1) - except FileNotFoundError: - value = None - return bool(int(value)) - return getter - - def _add_menu_getter(self, name: str) -> Callable: - """Creates a getter function for a setting named 'name'. - Args: - name: Name of the setting. - - Returns: - The getter function. - """ - - def getter() -> str: - """The method to get the current value of a choice setting - running v4l2-ctl. - """ - - # Trying to run v4l2-ctl to get the value - if self._device is not None: - command = ['v4l2-ctl', '-d', self._device, '--get-ctrl', name] - else: - command = ['v4l2-ctl', '--get-ctrl', name] - try: - self.log(logging.DEBUG, f"Getting {name} with command {command}") - value = run(command, capture_output=True, text=True).stdout - value = search(r': (\d+)', value).group(1) - for param in self.parameters: - if param.name == name: - for option in param.options: - if value == search(r'(\d+):', option).group(1): - value = option - except FileNotFoundError: - value = None - return value - return getter From 48bf1a63010cc38916829b7500ac2ca6f7f1f7dd Mon Sep 17 00:00:00 2001 From: PIERROOOTT Date: Thu, 16 Nov 2023 17:44:49 +0100 Subject: [PATCH 14/57] refactor: CameraOpencvV4l2 inherits now from the V4L2 class --- src/crappy/camera/opencv_camera_v4l2.py | 310 +++--------------------- 1 file changed, 36 insertions(+), 274 deletions(-) diff --git a/src/crappy/camera/opencv_camera_v4l2.py b/src/crappy/camera/opencv_camera_v4l2.py index 48e2794d..2e8358e5 100644 --- a/src/crappy/camera/opencv_camera_v4l2.py +++ b/src/crappy/camera/opencv_camera_v4l2.py @@ -1,15 +1,14 @@ # coding: utf-8 -from __future__ import annotations from time import time, sleep -from typing import Tuple, List, Optional, Callable +from typing import Tuple, List, Optional from numpy import ndarray from subprocess import run -from re import findall, split, search, finditer, Match +from re import findall, search import logging -from dataclasses import dataclass from .meta_camera import Camera +from ._v4l2_base import V4L2 from .._global import OptionalModule try: @@ -18,63 +17,7 @@ cv2 = OptionalModule("opencv-python") -@dataclass -class Parameter: - """A class for the different parameters the user can adjust.""" - - name: str - type: str - min: Optional[str] = None - max: Optional[str] = None - step: Optional[str] = None - default: Optional[str] = None - value: Optional[str] = None - flags: Optional[str] = None - options: Optional[Tuple[str, ...]] = None - - @classmethod - def parse_info(cls, match: Match) -> Parameter: - """Instantiates the class Parameter, according to the information - collected with v4l2-ctl. - - Args: - match: Match object returned by successful matches of the regex with - a string. - - Returns: - The instantiated class. - """ - - return cls(name=match.group(1), - type=match.group(2), - min=match.group(4) if match.group(4) else None, - max=match.group(6) if match.group(6) else None, - step=match.group(8) if match.group(8) else None, - default=match.group(10) if match.group(10) else None, - value=match.group(11), - flags=match.group(13) if match.group(13) else None) - - def add_options(self, match: Match) -> None: - """Adds the different possible options for a menu parameter. - - Args: - match: Match object returned by successful matches of the regex with - a string. - """ - - menu_info = match.group(1) - menu_values = match.group(2) - menu_name = search(r'(\w+) \w+ \(menu\)', menu_info).group(1) - if self.name == menu_name: - options = findall(r'\d+: .+?(?=\n|$)', menu_values) - num_options = findall(r'(\d+): .+?(?=\n|$)', menu_values) - self.options = tuple(options) - for i in range(len(num_options)): - if self.default == num_options[i]: - self.default = options[i] - - -class CameraOpencv(Camera): +class CameraOpencv(Camera, V4L2): """A class for reading images from any camera able to interface with OpenCv. The number of the video device to read images from can be specified. It is @@ -90,16 +33,12 @@ class CameraOpencv(Camera): def __init__(self) -> None: """Sets variables and adds the channels setting.""" - super().__init__() + Camera.__init__(self) + V4L2.__init__(self) self._cap = None self._device_num: Optional[int] = None self._formats: List[str] = list() - self.parameters = [] - - self.add_choice_setting(name="channels", - choices=('1', '3'), - default='1') def open(self, device_num: int = 0, **kwargs) -> None: """Opens the video stream and sets any user-specified settings. @@ -118,40 +57,10 @@ def open(self, device_num: int = 0, **kwargs) -> None: self._cap = cv2.VideoCapture(device_num) self._device_num = device_num - self._formats = [] - - # Trying to run v4l2-ctl to get the available formats - command = ['v4l2-ctl', '-d', str(device_num), '--list-formats-ext'] - try: - self.log(logging.INFO, f"Getting the available image formats with " - f"command {command}") - check = run(command, capture_output=True, text=True) - except FileNotFoundError: - check = None - check = check.stdout if check is not None else '' - - # Splitting the returned string to isolate each encoding - if findall(r'\[\d+]', check): - check = split(r'\[\d+]', check)[1:] - elif findall(r'Pixel\sFormat', check): - check = split(r'Pixel\sFormat', check)[1:] - else: - check = [] - - if check: - for img_format in check: - # For each encoding, finding its name - name, *_ = search(r"'(\w+)'", img_format).groups() - if name == 'MJPG' or name == 'YUYV': - sizes = findall(r'\d+x\d+', img_format) - fps_sections = split(r'\d+x\d+', img_format)[1:] - - # For each name, finding the available sizes - for size, fps_section in zip(sizes, fps_sections): - fps_list = findall(r'\((\d+\.\d+)\sfps\)', fps_section) - for fps in fps_list: - self._formats.append(f'{name} {size} ({fps} fps)') - + self._get_available_formats(device_num) + self._formats = [_format for _format in self._formats + if _format.split()[0] == 'MJPG' or + _format.split()[0] == 'YUYV'] if self._formats: # The format integrates the size selection if ' ' in self._formats[0]: @@ -166,69 +75,45 @@ def open(self, device_num: int = 0, **kwargs) -> None: getter=self._get_fourcc, setter=self._set_format) - # Trying to run v4l2-ctl to get the available settings - command = ['v4l2-ctl', '-L'] if device_num is None \ - else ['v4l2-ctl', '-d', str(device_num), '-L'] - self.log(logging.INFO, f"Getting the available image settings with " - f"command {command}") - try: - check = run(command, capture_output=True, text=True) - except FileNotFoundError: - check = None - check = check.stdout if check is not None else '' - - # Regex to extract the different parameters and their information - param_pattern = (r'(\w+)\s+0x\w+\s+\((\w+)\)\s+:\s*' - r'(min=(-?\d+)\s+)?' - r'(max=(-?\d+)\s+)?' - r'(step=(\d+)\s+)?' - r'(default=(-?\d+)\s+)?' - r'value=(-?\d+)\s*' - r'(flags=([^\\n]+))?') - - # Extract the different parameters and their information - matches = finditer(param_pattern, check) - for match in matches: - self.parameters.append(Parameter.parse_info(match)) - - # Regex to extract the different options in a menu - menu_options = finditer( - r'(\w+ \w+ \(menu\))([\s\S]+?)(?=\n\s*\w+ \w+ \(.+?\)|$)', check) - - # Extract the different options - for menu_option in menu_options: - for param in self.parameters: - param.add_options(menu_option) + self._get_param(device_num) # Create the different settings - for param in self.parameters: + for param in self._parameters: if not param.flags: if param.type == 'int': - self.add_scale_setting(name=param.name, - lowest=int(param.min), - highest=int(param.max), - getter=self._add_scale_getter(param.name), - setter=self._add_setter(param.name), - default=param.default, - step=int(param.step)) + self.add_scale_setting( + name=param.name, + lowest=int(param.min), + highest=int(param.max), + getter=self._add_scale_getter(param.name, self._device_num), + setter=self._add_setter(param.name, self._device_num), + default=param.default, + step=int(param.step)) + elif param.type == 'bool': - self.add_bool_setting(name=param.name, - getter=self._add_bool_getter(param.name), - setter=self._add_setter(param.name), - default=bool(int(param.default))) + self.add_bool_setting( + name=param.name, + getter=self._add_bool_getter(param.name, self._device_num), + setter=self._add_setter(param.name, self._device_num), + default=bool(int(param.default))) + elif param.type == 'menu': if param.options: - self.add_choice_setting(name=param.name, - choices=param.options, - getter=self._add_menu_getter(param.name), - setter=self._add_setter(param.name), - default=param.default) + self.add_choice_setting( + name=param.name, + choices=param.options, + getter=self._add_menu_getter(param.name, self._device_num), + setter=self._add_setter(param.name, self._device_num), + default=param.default) + else: self.log(logging.ERROR, f'The type {param.type} is not yet' f' implemented. Only int, bool and menu ' f'type are implemented. ') raise NotImplementedError + self.add_choice_setting(name="channels", choices=('1', '3'), default='1') + # Adding the software ROI selection settings if 'width' in self.settings and 'height' in self.settings: width, height = self._get_width(), self._get_height() @@ -352,126 +237,3 @@ def _get_format_size(self) -> str: fps, *_ = search(r"Frames per second\s*:\s*(\d+.\d+)", check).groups() return f'{format_} {width}x{height} ({fps} fps)' - - def _add_setter(self, name: str) -> Callable: - """Creates a setter function for a setting named 'name'. - Args: - name: Name of the setting. - - Returns: - The setter function. - """ - - def setter(value) -> None: - """The method to set the value of a setting running v4l2-ctl. - """ - - if isinstance(value, str): - # The value to set the menu parameter is just the int - # at the beginning the string - value = search(r'(\d+): ', value).group(1) - if self._device_num is not None: - command = ['v4l2-ctl', '-d', str(self._device_num), '--set-ctrl', - f'{name}={value}'] - else: - command = ['v4l2-ctl', '--set-ctrl', f'{name}={int(value[0])}'] - self.log(logging.DEBUG, f"Setting {name} with command {command}") - run(command, capture_output=True, text=True) - else: - if self._device_num is not None: - command = ['v4l2-ctl', '-d', str(self._device_num), '--set-ctrl', - name+f'={int(value)}'] - else: - command = ['v4l2-ctl', '--set-ctrl', f'{name}={int(value)}'] - self.log(logging.DEBUG, f"Setting {name} with command {command}") - run(command, capture_output=True, text=True) - return setter - - def _add_scale_getter(self, name: str) -> Callable: - """Creates a getter function for a setting named 'name'. - Args: - name: Name of the setting. - - Returns: - The getter function. - """ - - def getter() -> int: - """The method to get the current value of a scale setting - running v4l2-ctl. - """ - - # Trying to run v4l2-ctl to get the value - if self._device_num is not None: - command = ['v4l2-ctl', '-d', str(self._device_num), '--get-ctrl', name] - else: - command = ['v4l2-ctl', '--get-ctrl', name] - try: - self.log(logging.DEBUG, f"Getting {name} with command {command}") - value = run(command, capture_output=True, text=True).stdout - value = search(r': (-?\d+)', value).group(1) - except FileNotFoundError: - value = None - return int(value) - return getter - - def _add_bool_getter(self, name: str) -> Callable: - """Creates a getter function for a setting named 'name'. - Args: - name: Name of the setting. - - Returns: - The getter function. - """ - - def getter() -> bool: - """The method to get the current value of a bool setting - running v4l2-ctl. - """ - - # Trying to run v4l2-ctl to get the value - if self._device_num is not None: - command = ['v4l2-ctl', '-d', str(self._device_num), '--get-ctrl', name] - else: - command = ['v4l2-ctl', '--get-ctrl', name] - try: - self.log(logging.DEBUG, f"Getting {name} with command {command}") - value = run(command, capture_output=True, text=True).stdout - value = search(r': (\d+)', value).group(1) - except FileNotFoundError: - value = None - return bool(int(value)) - return getter - - def _add_menu_getter(self, name: str) -> Callable: - """Creates a getter function for a setting named 'name'. - Args: - name: Name of the setting. - - Returns: - The getter function. - """ - - def getter() -> str: - """The method to get the current value of a choice setting - running v4l2-ctl. - """ - - # Trying to run v4l2-ctl to get the value - if self._device_num is not None: - command = ['v4l2-ctl', '-d', str(self._device_num), '--get-ctrl', name] - else: - command = ['v4l2-ctl', '--get-ctrl', name] - try: - self.log(logging.DEBUG, f"Getting {name} with command {command}") - value = run(command, capture_output=True, text=True).stdout - value = search(r': (\d+)', value).group(1) - for param in self.parameters: - if param.name == name: - for option in param.options: - if value == search(r'(\d+):', option).group(1): - value = option - except FileNotFoundError: - value = None - return value - return getter From 6edfc7a216dff5005bb950c3516e2afc511675d4 Mon Sep 17 00:00:00 2001 From: PIERROOOTT Date: Fri, 17 Nov 2023 13:06:05 +0100 Subject: [PATCH 15/57] style: correct typo --- src/crappy/camera/_v4l2_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crappy/camera/_v4l2_base.py b/src/crappy/camera/_v4l2_base.py index 7612fd78..e130ff28 100644 --- a/src/crappy/camera/_v4l2_base.py +++ b/src/crappy/camera/_v4l2_base.py @@ -64,7 +64,7 @@ def add_options(self, match: Match) -> None: class V4L2: - """A class for getting parameters available in a camera by using v4l2-utils. + """A class for getting parameters available in a camera by using v4l-utils. """ def __init__(self): From 12d80ea47bd4c13ed6da1473e5b11eefcdf9b3d8 Mon Sep 17 00:00:00 2001 From: PIERROOOTT Date: Mon, 20 Nov 2023 11:01:40 +0100 Subject: [PATCH 16/57] style: add encoding utf-8 --- src/crappy/camera/_v4l2_base.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/crappy/camera/_v4l2_base.py b/src/crappy/camera/_v4l2_base.py index e130ff28..ae66b0b5 100644 --- a/src/crappy/camera/_v4l2_base.py +++ b/src/crappy/camera/_v4l2_base.py @@ -1,3 +1,5 @@ +# coding: utf-8 + from __future__ import annotations from typing import Tuple, Optional, Callable, Union from re import findall, search, finditer, split, Match From 6ec2eb288c599d63efcb9cba8526ce65a045718c Mon Sep 17 00:00:00 2001 From: Antoine Weisrock Date: Mon, 20 Nov 2023 22:53:45 +0100 Subject: [PATCH 17/57] docs: add changeset info to the docstrings of the documented objects It provides information on the release when features were added, changed, or removed --- docs/source/conf.py | 3 +- src/crappy/__init__.py | 5 ++ src/crappy/_global.py | 6 ++- src/crappy/actuator/adafruit_dc_motor_hat.py | 2 + src/crappy/actuator/fake_dc_motor.py | 11 +++- src/crappy/actuator/fake_stepper_motor.py | 2 + .../actuator/ft232h/adafruit_dc_motor_hat.py | 5 +- src/crappy/actuator/jvl_mac_140.py | 15 ++++-- .../actuator/kollmorgen_servostar_300.py | 14 ++++- src/crappy/actuator/meta_actuator/actuator.py | 26 +++++++++- .../actuator/meta_actuator/meta_actuator.py | 7 ++- src/crappy/actuator/newport_tra6ppd.py | 3 ++ src/crappy/actuator/oriental_ard_k.py | 10 +++- src/crappy/actuator/pololu_tic.py | 7 +++ src/crappy/actuator/schneider_mdrive_23.py | 11 +++- src/crappy/blocks/auto_drive_video_extenso.py | 7 +++ src/crappy/blocks/button.py | 6 +++ src/crappy/blocks/camera.py | 14 +++++ .../blocks/camera_processes/camera_process.py | 2 + src/crappy/blocks/camera_processes/dic_ve.py | 2 + .../blocks/camera_processes/dis_correl.py | 2 + src/crappy/blocks/camera_processes/display.py | 2 + .../blocks/camera_processes/gpu_correl.py | 2 + src/crappy/blocks/camera_processes/gpu_ve.py | 2 + src/crappy/blocks/camera_processes/record.py | 2 + .../blocks/camera_processes/video_extenso.py | 2 + src/crappy/blocks/canvas.py | 31 ++++++++++- src/crappy/blocks/client_server.py | 6 +++ src/crappy/blocks/dashboard.py | 27 ++++++++-- src/crappy/blocks/dic_ve.py | 23 +++++++++ src/crappy/blocks/dis_correl.py | 20 ++++++++ src/crappy/blocks/fake_machine.py | 9 ++++ src/crappy/blocks/generator.py | 7 +++ .../blocks/generator_path/conditional.py | 8 +++ src/crappy/blocks/generator_path/constant.py | 15 +++++- src/crappy/blocks/generator_path/custom.py | 7 +++ src/crappy/blocks/generator_path/cyclic.py | 7 +++ .../blocks/generator_path/cyclic_ramp.py | 9 ++++ .../blocks/generator_path/integrator.py | 11 ++++ .../generator_path/meta_path/meta_path.py | 5 +- .../blocks/generator_path/meta_path/path.py | 8 +++ src/crappy/blocks/generator_path/ramp.py | 10 +++- src/crappy/blocks/generator_path/sine.py | 9 +++- src/crappy/blocks/gpu_correl.py | 21 ++++++++ src/crappy/blocks/gpu_ve.py | 18 ++++++- src/crappy/blocks/grapher.py | 7 +++ src/crappy/blocks/hdf_recorder.py | 7 +++ src/crappy/blocks/ioblock.py | 8 +++ src/crappy/blocks/link_reader.py | 9 ++++ src/crappy/blocks/machine.py | 5 ++ src/crappy/blocks/mean.py | 13 ++++- src/crappy/blocks/meta_block/block.py | 51 +++++++++++++++++-- src/crappy/blocks/meta_block/meta_block.py | 5 +- src/crappy/blocks/multiplexer.py | 8 +++ src/crappy/blocks/pid.py | 9 ++++ src/crappy/blocks/recorder.py | 7 +++ src/crappy/blocks/sink.py | 6 +++ src/crappy/blocks/stop_block.py | 2 + src/crappy/blocks/stop_button.py | 2 + src/crappy/blocks/ucontroller.py | 5 ++ src/crappy/blocks/video_extenso.py | 20 ++++++++ .../cameralink/basler_ironman_cameralink.py | 8 +++ .../camera/cameralink/jai_go_5000c_pmcl.py | 11 ++++ src/crappy/camera/fake_camera.py | 6 +++ src/crappy/camera/file_reader.py | 8 +++ src/crappy/camera/gstreamer_camera_basic.py | 3 ++ src/crappy/camera/gstreamer_camera_v4l2.py | 3 ++ src/crappy/camera/meta_camera/camera.py | 29 ++++++++++- .../camera_setting/camera_bool_setting.py | 4 ++ .../camera_setting/camera_choice_setting.py | 6 +++ .../camera_setting/camera_scale_setting.py | 18 +++++-- .../camera_setting/camera_setting.py | 7 +++ src/crappy/camera/meta_camera/meta_camera.py | 7 ++- src/crappy/camera/raspberry_pi_camera.py | 3 ++ src/crappy/camera/seek_thermal_pro.py | 3 ++ src/crappy/camera/ximea_xiapi.py | 5 ++ src/crappy/inout/ads1115.py | 6 +++ src/crappy/inout/agilent_34420A.py | 2 + src/crappy/inout/comedi.py | 10 ++++ src/crappy/inout/daqmx.py | 7 +++ src/crappy/inout/fake_inout.py | 15 +++++- src/crappy/inout/ft232h/ads1115.py | 2 + src/crappy/inout/ft232h/gpio_switch.py | 2 + src/crappy/inout/ft232h/mcp9600.py | 2 + src/crappy/inout/ft232h/mprls.py | 2 + src/crappy/inout/ft232h/nau7802.py | 2 + src/crappy/inout/ft232h/waveshare_ad_da.py | 2 + src/crappy/inout/gpio_pwm.py | 3 ++ src/crappy/inout/gpio_switch.py | 6 +++ src/crappy/inout/kollmorgen_akd_pdmm.py | 9 ++++ src/crappy/inout/labjack_t7.py | 5 ++ src/crappy/inout/labjack_t7_streamer.py | 5 ++ src/crappy/inout/labjack_ue9.py | 5 ++ src/crappy/inout/mcp9600.py | 4 ++ src/crappy/inout/meta_inout/inout.py | 41 +++++++++++++-- src/crappy/inout/meta_inout/meta_inout.py | 7 ++- src/crappy/inout/mprls.py | 8 +++ src/crappy/inout/nau7802.py | 6 +++ src/crappy/inout/ni_daqmx.py | 6 +++ src/crappy/inout/opsens_handysens.py | 3 ++ src/crappy/inout/pijuice_hat.py | 5 ++ src/crappy/inout/sim868.py | 5 ++ src/crappy/inout/spectrum_m2i4711.py | 6 +++ src/crappy/inout/waveshare_ad_da.py | 3 ++ src/crappy/inout/waveshare_high_precision.py | 4 ++ src/crappy/lamcube/biaxe.py | 7 ++- src/crappy/lamcube/bispectral.py | 8 ++- src/crappy/links/link.py | 27 +++++++++- src/crappy/modifier/demux.py | 11 +++- src/crappy/modifier/differentiate.py | 12 ++++- src/crappy/modifier/integrate.py | 12 ++++- src/crappy/modifier/mean.py | 6 +++ src/crappy/modifier/median.py | 6 +++ .../modifier/meta_modifier/meta_modifier.py | 7 ++- src/crappy/modifier/meta_modifier/modifier.py | 11 +++- src/crappy/modifier/moving_avg.py | 10 +++- src/crappy/modifier/moving_med.py | 10 +++- src/crappy/modifier/offset.py | 7 ++- src/crappy/modifier/trig_on_change.py | 10 +++- src/crappy/modifier/trig_on_value.py | 10 +++- src/crappy/tool/apply_strain_image.py | 2 + .../tool/camera_config/camera_config.py | 21 +++++++- .../tool/camera_config/camera_config_boxes.py | 6 +++ .../tool/camera_config/config_tools/box.py | 2 + .../config_tools/histogram_process.py | 2 + .../config_tools/overlay_object.py | 2 + .../camera_config/config_tools/spots_boxes.py | 2 + .../config_tools/spots_detector.py | 2 + .../tool/camera_config/config_tools/zoom.py | 2 + .../tool/camera_config/dic_ve_config.py | 7 +++ .../tool/camera_config/dis_correl_config.py | 13 ++++- .../camera_config/video_extenso_config.py | 10 ++++ src/crappy/tool/ft232h/ft232h.py | 10 +++- src/crappy/tool/ft232h/ft232h_server.py | 11 +++- src/crappy/tool/ft232h/i2c_message.py | 3 ++ src/crappy/tool/ft232h/usb_server.py | 18 ++++++- src/crappy/tool/image_processing/dic_ve.py | 15 +++++- .../tool/image_processing/dis_correl.py | 20 +++++++- src/crappy/tool/image_processing/fields.py | 8 +++ .../tool/image_processing/gpu_correl.py | 21 ++++++++ .../image_processing/video_extenso/tracker.py | 2 + .../video_extenso/video_extenso.py | 10 ++++ 142 files changed, 1164 insertions(+), 68 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 38ee9dd6..5bd17c83 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -53,7 +53,8 @@ 'sphinx.ext.duration', 'sphinx_copybutton', 'sphinx_tabs.tabs', - 'sphinx_toolbox.collapse' + 'sphinx_toolbox.collapse', + 'sphinx_toolbox.changeset' ] # Tabs settings diff --git a/src/crappy/__init__.py b/src/crappy/__init__.py index 74165850..3b61e968 100644 --- a/src/crappy/__init__.py +++ b/src/crappy/__init__.py @@ -46,6 +46,9 @@ 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 """ open('https://crappy.readthedocs.io/en/latest/') @@ -58,6 +61,8 @@ class resources: 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: diff --git a/src/crappy/_global.py b/src/crappy/_global.py index 51f2d948..efdad44b 100644 --- a/src/crappy/_global.py +++ b/src/crappy/_global.py @@ -8,6 +8,8 @@ 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 """ def __init__(self, @@ -23,8 +25,10 @@ def __init__(self, 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. In stead, it will be imported only when necessary. + if it is installed. Instead, it will be imported only when necessary. Allows reducing the import time, especially on Window. + + .. versionadded:: 2.0.0 *lazy_import* argument """ self._name = module_name diff --git a/src/crappy/actuator/adafruit_dc_motor_hat.py b/src/crappy/actuator/adafruit_dc_motor_hat.py index 23fada11..855d7d8e 100644 --- a/src/crappy/actuator/adafruit_dc_motor_hat.py +++ b/src/crappy/actuator/adafruit_dc_motor_hat.py @@ -62,6 +62,8 @@ class DCMotorHat(Actuator): Note: The DC Motor Hat can also drive stepper motors, but this feature isn't included here. + + .. versionadded:: 2.0.0 """ def __init__(self, diff --git a/src/crappy/actuator/fake_dc_motor.py b/src/crappy/actuator/fake_dc_motor.py index c088bbd9..ad7bdd07 100644 --- a/src/crappy/actuator/fake_dc_motor.py +++ b/src/crappy/actuator/fake_dc_motor.py @@ -10,6 +10,9 @@ class FakeDCMotor(Actuator): voltage. It is mainly intended for testing scripts without requiring any hardware. + + .. versionadded:: 1.4.0 + .. versionchanged:: 2.0.0 Renamed from Fake_motor to FakeDCMotor """ def __init__(self, @@ -33,6 +36,9 @@ def __init__(self, it down. initial_speed: The initial speed of the motor, in RPM. initial_pos: The initial position of the motor, in turns. + + .. versionchanged:: 2.0.0 + renamed *sim_speed* argument to *simulation_speed* """ super().__init__() @@ -66,7 +72,10 @@ def get_speed(self) -> float: return self._rpm def get_position(self) -> float: - """Returns the position of the motor, in rounds.""" + """Returns the position of the motor, in rounds. + + .. versionchanged:: 1.5.2 renamed from get_pos to get_position + """ self._update() return self._pos diff --git a/src/crappy/actuator/fake_stepper_motor.py b/src/crappy/actuator/fake_stepper_motor.py index 9d70a2ab..2397ba75 100644 --- a/src/crappy/actuator/fake_stepper_motor.py +++ b/src/crappy/actuator/fake_stepper_motor.py @@ -21,6 +21,8 @@ class FakeStepperMotor(Actuator): Internally, the behavior of the motor is emulated in a separate :obj:`~threading.Thread` and is based on the fundamental equations of the constantly accelerated linear movement. + + .. versionadded:: 2.0.0 """ def __init__(self, diff --git a/src/crappy/actuator/ft232h/adafruit_dc_motor_hat.py b/src/crappy/actuator/ft232h/adafruit_dc_motor_hat.py index 521f9bc9..354965d7 100644 --- a/src/crappy/actuator/ft232h/adafruit_dc_motor_hat.py +++ b/src/crappy/actuator/ft232h/adafruit_dc_motor_hat.py @@ -45,7 +45,10 @@ class DCMotorHatFT232H(Actuator): Note: The DC Motor Hat can also drive stepper motors, but this feature isn't - included here.""" + included here. + + .. versionadded:: 2.0.0 + """ ft232h = True diff --git a/src/crappy/actuator/jvl_mac_140.py b/src/crappy/actuator/jvl_mac_140.py index 70413dea..cc40b321 100644 --- a/src/crappy/actuator/jvl_mac_140.py +++ b/src/crappy/actuator/jvl_mac_140.py @@ -25,14 +25,18 @@ class JVLMac140(Actuator): in position. It interfaces with the servomotor over a serial connection. + + .. versionadded:: 1.4.0 + .. versionchanged:: 2.0.0 renamed class from Biotens to JVLMac140 """ - def __init__(self, - port: str = '/dev/ttyUSB0') -> None: + def __init__(self, port: str = '/dev/ttyUSB0') -> None: """Initializes the parent class. Args: port: Path to the serial port to use for communication. + + .. deprecated:: 2.0.0 *baudrate* argument """ self._ser = None @@ -83,6 +87,8 @@ def set_position(self, position: float, speed: Optional[float]) -> None: position: The target position, in `mm`. speed: The target speed for reaching the desired position, in `mm/min`. The speed must be given, otherwise an exception is raised. + + .. versionchanged:: 2.0.0 *speed* is now a mandatory argument """ if speed is None: @@ -109,7 +115,10 @@ def set_position(self, position: float, speed: Optional[float]) -> None: self._ser.writelines(cmd) def get_position(self) -> float: - """Reads and returns the current position of the servomotor, in `mm`.""" + """Reads and returns the current position of the servomotor, in `mm`. + + .. versionchanged:: 1.5.2 renamed from get_pos to get_position + """ # We have 20 attempts for reading the position for _ in range(20): diff --git a/src/crappy/actuator/kollmorgen_servostar_300.py b/src/crappy/actuator/kollmorgen_servostar_300.py index 66d9ecff..35db3740 100644 --- a/src/crappy/actuator/kollmorgen_servostar_300.py +++ b/src/crappy/actuator/kollmorgen_servostar_300.py @@ -19,6 +19,9 @@ class ServoStar300(Actuator): It communicates with the servomotor over a serial connection. The :class:`~crappy.lamcube.Biaxe` Actuator can drive the same hardware, but only in speed. + + .. versionadded:: 1.4.0 + .. versionchanged:: 2.0.0 renamed from Servostar to ServoStar300 """ def __init__(self, @@ -33,6 +36,9 @@ def __init__(self, mode: The driving mode to use when starting the test. Can be `'analog'` or `'serial'`. It can be changed afterward while the test is running, by sending the right command. + + .. deprecated:: 2.0.0 *device argument* + .. versionchanged:: 2.0.0 use *port* instead of *device* """ self._ser = None @@ -84,6 +90,9 @@ def set_position(self, sets the driving mode to analog. speed: The speed at which the actuator should reach its target position. If no speed is specified, the default is `20000`. + + .. deprecated:: 2.0.0 remove *acc* and *dec* arguments + .. versionchanged:: 2.0.0 *speed* is now a mandatory argument """ if speed is None: @@ -118,7 +127,10 @@ def set_position(self, self._last_pos = pos def get_position(self) -> Optional[float]: - """Reads and returns the current position of the motor.""" + """Reads and returns the current position of the motor. + + .. versionchanged:: 1.5.2 renamed from get_pos to get_position + """ # Requesting a position reading self._ser.flushInput() diff --git a/src/crappy/actuator/meta_actuator/actuator.py b/src/crappy/actuator/meta_actuator/actuator.py index 6d37a760..73932769 100644 --- a/src/crappy/actuator/meta_actuator/actuator.py +++ b/src/crappy/actuator/meta_actuator/actuator.py @@ -15,6 +15,8 @@ class Actuator(metaclass=MetaActuator): The Actuator objects are helper classes used by the :class:`~crappy.blocks.Machine` Block to communicate with motors or other actuators. + + .. versionadded:: 1.4.0 """ ft232h: bool = False @@ -32,6 +34,8 @@ def log(self, level: int, msg: str) -> None: Args: level: An :obj:`int` indicating the logging level of the message. msg: The message to log, as a :obj:`str`. + + .. versionadded:: 2.0.0 """ if self._logger is None: @@ -51,6 +55,8 @@ def open(self) -> None: Blocks. It is fine for this method not to perform anything. + + .. versionadded:: 2.0.0 """ ... @@ -68,6 +74,8 @@ def set_speed(self, speed: float) -> None: Args: speed: The speed to reach, as a :obj:`float`. + + .. versionadded:: 1.5.10 """ self.log(logging.WARNING, f"The set_speed method was called but is not " @@ -109,6 +117,10 @@ def set_position(self, position: float, speed: Optional[float]) -> None: position: The position to reach, as a :obj:`float`. speed: The speed at which to move to the desired position, as a :obj:`float`, or :obj:`None` if no speed is specified. + + .. versionadded:: 1.5.10 + .. versionchanged:: 2.0.0 + *speed* is now a mandatory argument even if it is :obj:`None` """ self.log(logging.WARNING, f"The set_position method was called but is not " @@ -124,7 +136,10 @@ def get_speed(self) -> Optional[float]: :class:`~crappy.blocks.Machine` Block. It is also fine for this method to return :obj:`None` if the speed could - not be acquired.""" + not be acquired. + + .. versionadded:: 1.5.10 + """ self.log(logging.WARNING, f"The get_speed method as called but is not " f"defined ! Define such a method, don't set the " @@ -142,7 +157,10 @@ def get_position(self) -> Optional[float]: :class:`~crappy.blocks.Machine` Block. It is also fine for this method to return :obj:`None` if the position could - not be acquired.""" + not be acquired. + + .. versionadded:: 1.5.10 + """ self.log(logging.WARNING, f"The get_position method as called but is not " f"defined ! Define such a method, don't set the " @@ -162,6 +180,8 @@ def stop(self) -> None: default behavior is to call :meth:`set_speed` to set the speed to `0`. This method doesn't need to be overriden if the actuator doesn't have any feature for stopping other than speed control. + + .. versionadded:: 2.0.0 """ self.set_speed(0) @@ -176,6 +196,8 @@ def close(self) -> None: path, or because an exception was raised in any of the Blocks). It is fine for this method not to perform anything. + + .. versionadded:: 2.0.0 """ ... diff --git a/src/crappy/actuator/meta_actuator/meta_actuator.py b/src/crappy/actuator/meta_actuator/meta_actuator.py index b84a2d76..0bee320c 100644 --- a/src/crappy/actuator/meta_actuator/meta_actuator.py +++ b/src/crappy/actuator/meta_actuator/meta_actuator.py @@ -6,7 +6,12 @@ class MetaActuator(type): """Metaclass ensuring that two Actuators don't have the same name, and keeping track of all the :class:`~crappy.actuator.Actuator` classes. It also - allows including the user-defined Actuators.""" + allows including the user-defined Actuators. + + .. versionadded:: 1.4.0 + .. versionchanged:: 2.0.0 + not checking anymore for mandatory methods in :meth:`__init__` + """ classes = {} diff --git a/src/crappy/actuator/newport_tra6ppd.py b/src/crappy/actuator/newport_tra6ppd.py index e1cd7071..f46cd308 100644 --- a/src/crappy/actuator/newport_tra6ppd.py +++ b/src/crappy/actuator/newport_tra6ppd.py @@ -24,6 +24,9 @@ class NewportTRA6PPD(Actuator): Note: This Actuator ignores new position commands while it is moving. + + .. versionadded:: 1.5.10 + .. versionchanged:: 2.0.0 renamed from TRA6PPD to NewportTRA6PPD """ def __init__(self, diff --git a/src/crappy/actuator/oriental_ard_k.py b/src/crappy/actuator/oriental_ard_k.py index 1ca204c0..7988bde4 100644 --- a/src/crappy/actuator/oriental_ard_k.py +++ b/src/crappy/actuator/oriental_ard_k.py @@ -19,6 +19,9 @@ class OrientalARDK(Actuator): It communicates with the stepper motor over a serial connection. This class was designed so that the :class:`~crappy.blocks.Machine` Block drives several of its instances at a time, corresponding to different axes to drive. + + .. versionadded:: 1.4.0 + .. versionchanged:: 2.0.0 renamed from Oriental to OrientalARDK """ def __init__(self, @@ -128,6 +131,8 @@ def set_position(self, position: float, speed: Optional[float]) -> None: position: The target position to reach, in arbitrary units. speed: The speed to use for reaching the target position, in arbitrary units. A speed must be given, otherwise an exception is raised. + + .. versionchanged:: 2.0.0 *speed* is now a mandatory argument """ if speed is None: @@ -140,7 +145,10 @@ def set_position(self, position: float, speed: Optional[float]) -> None: self._ser.write(f'MA {position}'.encode()) def get_position(self) -> float: - """Reads and returns the current position of the motor.""" + """Reads and returns the current position of the motor. + + .. versionchanged:: 1.5.2 renamed from get_pos to get_position + """ # Sending the read command self._ser.flushInput() diff --git a/src/crappy/actuator/pololu_tic.py b/src/crappy/actuator/pololu_tic.py index d2e65bb4..1045d910 100644 --- a/src/crappy/actuator/pololu_tic.py +++ b/src/crappy/actuator/pololu_tic.py @@ -269,6 +269,9 @@ class PololuTic(Actuator): MODE=\\"0666\\\"" | sudo tee pololu.rules > /dev/null 2>&1 in a shell opened in ``/etc/udev/rules.d``. + + .. versionadded:: 1.4.0 + .. versionchanged:: 2.0.0 renamed from Pololu_tic to PololuTic """ def __init__(self, @@ -724,6 +727,8 @@ def get_position(self) -> float: Returns: The position in `mm` + + .. versionchanged:: 1.5.2 renamed from get_pos to get_position """ if self._backend == 'ticcmd': @@ -754,6 +759,8 @@ def set_position(self, position: float, speed: Optional[float]) -> None: maximum speed. The Tic will try to accelerate to the maximum speed but may remain slower if it doesn't have time to do so before reaching the given position. + + .. versionchanged:: 2.0.0 *speed* is now a mandatory argument """ if speed is not None: diff --git a/src/crappy/actuator/schneider_mdrive_23.py b/src/crappy/actuator/schneider_mdrive_23.py index b4d446d5..d7420b01 100644 --- a/src/crappy/actuator/schneider_mdrive_23.py +++ b/src/crappy/actuator/schneider_mdrive_23.py @@ -17,6 +17,9 @@ class SchneiderMDrive23(Actuator): and in position. It communicates with the motor over a serial connection. + + .. versionadded:: 1.4.0 + .. versionchanged:: 2.0.0 renamed from CM_drive to SchneiderMDrive23 """ def __init__(self, @@ -69,6 +72,9 @@ def set_position(self, position: float, _: Optional[float]) -> None: Args: position: The target position to reach, in `mm`. _: The speed argument is ignored. + + .. versionchanged:: 2.0.0 *speed* is now a mandatory argument + .. deprecated:: 2.0.0 *motion_type* argument """ # Closing and reopening to get rid of errors @@ -82,7 +88,10 @@ def set_position(self, position: float, _: Optional[float]) -> None: self._ser.readline() def get_position(self) -> float: - """Reads, displays and returns the current position in `mm`.""" + """Reads, displays and returns the current position in `mm`. + + .. versionchanged:: 1.5.2 renamed from get_pos to get_position + """ # Closing and reopening to get rid of errors self._ser.close() diff --git a/src/crappy/blocks/auto_drive_video_extenso.py b/src/crappy/blocks/auto_drive_video_extenso.py index 069e1df3..f584c89b 100644 --- a/src/crappy/blocks/auto_drive_video_extenso.py +++ b/src/crappy/blocks/auto_drive_video_extenso.py @@ -22,6 +22,9 @@ class AutoDriveVideoExtenso(Block): It also outputs the difference between the center of the image and the middle of the spots, along with a timestamp, over the ``'t(s)'`` and ``'diff(pix)'`` labels. It can then be used by downstream Blocks. + + .. versionadded:: 1.4.0 + .. versionchanged:: 2.0.0 renamed from AutoDrive to AutoDriveVideoExtenso """ def __init__(self, @@ -61,6 +64,10 @@ def __init__(self, :obj:`~logging.DEBUG` ones. If :obj:`False`, only displays the log messages with :obj:`~logging.INFO` level or higher. If :obj:`None`, disables logging for this Block. + + .. versionchanged:: 1.5.10 renamed *P* argument to *gain* + .. versionchanged:: 1.5.10 renamed *range* argument to *pixel_range* + .. versionadded:: 2.0.0 *display_freq* and *debug* arguments """ self._device: Optional[Actuator] = None diff --git a/src/crappy/blocks/button.py b/src/crappy/blocks/button.py index 2f0729aa..cb38dfc8 100644 --- a/src/crappy/blocks/button.py +++ b/src/crappy/blocks/button.py @@ -25,6 +25,9 @@ class Button(Block): triggering actions based on an experimenter's decision. It can be handy for taking pictures at precise moments, or when an action should only begin after the experimenter has completed a task, for example. + + .. versionadded:: 1.4.0 + .. versionchanged:: 2.0.0 renamed from GUI to Button """ def __init__(self, @@ -55,6 +58,9 @@ def __init__(self, :obj:`~logging.DEBUG` ones. If :obj:`False`, only displays the log messages with :obj:`~logging.INFO` level or higher. If :obj:`None`, disables logging for this Block. + + .. versionadded:: 1.5.10 *send_0* and *time_label* arguments + .. versionadded:: 2.0.0 *display_freq* and *debug* arguments """ self._root: Optional[tk.Tk] = None diff --git a/src/crappy/blocks/camera.py b/src/crappy/blocks/camera.py index 73a8b105..0f2dbef5 100644 --- a/src/crappy/blocks/camera.py +++ b/src/crappy/blocks/camera.py @@ -44,6 +44,8 @@ class Camera(Block): the recording by the :class:`~crappy.blocks.camera_processes.ImageSaver`. This Block manages the instantiation, the synchronisation and the termination of all the CameraProcess it controls. + + .. versionadded:: 1.4.0 """ cam_count = dict() @@ -190,6 +192,18 @@ def __init__(self, **kwargs: Any additional argument will be passed to the :class:`~crappy.camera.Camera` object, and used as a kwarg to its :meth:`~crappy.camera.Camera.open` method. + + .. versionadded:: 1.5.2 *no_loop* argument + .. versionadded:: 1.5.10 + *display_images*, *displayer_backend*, *displayer_framerate*, + *software_trig_label*, *freq*, *save_images* and *image_generator* + arguments + .. versionremoved:: + 1.5.10 *fps_label*, *ext*, *input_label* and *no_loop* arguments + .. versionadded:: 2.0.0 + *debug*, *img_extension*, *img_shape* and *img_dtype* arguments + .. versionremoved:: 2.0.0 *img_name* argument + .. versionchanged:: 2.0.0 renamed *verbose* argument to *display_freq* """ self._save_proc: Optional[ImageSaver] = None diff --git a/src/crappy/blocks/camera_processes/camera_process.py b/src/crappy/blocks/camera_processes/camera_process.py index b55b76bc..69d6aa85 100644 --- a/src/crappy/blocks/camera_processes/camera_process.py +++ b/src/crappy/blocks/camera_processes/camera_process.py @@ -44,6 +44,8 @@ class CameraProcess(Process): managed by the parent :class:`~crappy.blocks.Camera` Block, depending on the provided arguments. Users should normally not need to call this class themselves. + + .. versionadded:: 2.0.0 """ def __init__(self) -> None: diff --git a/src/crappy/blocks/camera_processes/dic_ve.py b/src/crappy/blocks/camera_processes/dic_ve.py index e38c136c..07689ef5 100644 --- a/src/crappy/blocks/camera_processes/dic_ve.py +++ b/src/crappy/blocks/camera_processes/dic_ve.py @@ -23,6 +23,8 @@ class DICVEProcess(CameraProcess): that the :class:`~crappy.tool.camera_config.config_tools.SpotsBoxes` are sent to the :class:`~crappy.blocks.camera_processes.Displayer` CameraProcess for display. + + .. versionadded:: 2.0.0 """ def __init__(self, diff --git a/src/crappy/blocks/camera_processes/dis_correl.py b/src/crappy/blocks/camera_processes/dis_correl.py index 4224fa62..6cea1a6f 100644 --- a/src/crappy/blocks/camera_processes/dis_correl.py +++ b/src/crappy/blocks/camera_processes/dis_correl.py @@ -22,6 +22,8 @@ class DISCorrelProcess(CameraProcess): that the :class:`~crappy.tool.camera_config.config_tools.SpotsBoxes` are sent to the :class:`~crappy.blocks.camera_processes.Displayer` CameraProcess for display. + + .. versionadded:: 2.0.0 """ def __init__(self, diff --git a/src/crappy/blocks/camera_processes/display.py b/src/crappy/blocks/camera_processes/display.py index fce5b41d..8f276693 100644 --- a/src/crappy/blocks/camera_processes/display.py +++ b/src/crappy/blocks/camera_processes/display.py @@ -36,6 +36,8 @@ class Displayer(CameraProcess): The images can be displayed using two different backends : either using :mod:`cv2` (OpenCV), or using :mod:`matplotlib`. OpenCV is by far the fastest and most convenient. + + .. versionadded:: 2.0.0 """ def __init__(self, diff --git a/src/crappy/blocks/camera_processes/gpu_correl.py b/src/crappy/blocks/camera_processes/gpu_correl.py index b2bf7c74..203afc42 100644 --- a/src/crappy/blocks/camera_processes/gpu_correl.py +++ b/src/crappy/blocks/camera_processes/gpu_correl.py @@ -23,6 +23,8 @@ class GPUCorrelProcess(CameraProcess): It is also this class that takes the decision to send or not the results to downstream Blocks based on the value of the calculated residuals, if this option is enabled by the user. + + .. versionadded:: 2.0.0 """ def __init__(self, diff --git a/src/crappy/blocks/camera_processes/gpu_ve.py b/src/crappy/blocks/camera_processes/gpu_ve.py index d4318872..23232bd6 100644 --- a/src/crappy/blocks/camera_processes/gpu_ve.py +++ b/src/crappy/blocks/camera_processes/gpu_ve.py @@ -30,6 +30,8 @@ class GPUVEProcess(CameraProcess): and that the :class:`~crappy.tool.camera_config.config_tools.SpotsBoxes` are sent to the :class:`~crappy.blocks.camera_processes.Displayer` CameraProcess for display. + + .. versionadded:: 2.0.0 """ def __init__(self, diff --git a/src/crappy/blocks/camera_processes/record.py b/src/crappy/blocks/camera_processes/record.py index e6d861ad..a54f40c6 100644 --- a/src/crappy/blocks/camera_processes/record.py +++ b/src/crappy/blocks/camera_processes/record.py @@ -37,6 +37,8 @@ class ImageSaver(CameraProcess): Various backends can be used for recording the images, some may be faster or slower depending on the machine. It is possible to only save one out of a given number of images, if not all frames are needed. + + .. versionadded:: 2.0.0 """ def __init__(self, diff --git a/src/crappy/blocks/camera_processes/video_extenso.py b/src/crappy/blocks/camera_processes/video_extenso.py index 790174fc..2cb62849 100644 --- a/src/crappy/blocks/camera_processes/video_extenso.py +++ b/src/crappy/blocks/camera_processes/video_extenso.py @@ -23,6 +23,8 @@ class VideoExtensoProcess(CameraProcess): that the :class:`~crappy.tool.camera_config.config_tools.SpotsBoxes` are sent to the :class:`~crappy.blocks.camera_processes.Displayer` CameraProcess for display. + + .. versionadded:: 2.0.0 """ def __init__(self, diff --git a/src/crappy/blocks/canvas.py b/src/crappy/blocks/canvas.py index a691a2a3..217438c5 100644 --- a/src/crappy/blocks/canvas.py +++ b/src/crappy/blocks/canvas.py @@ -14,7 +14,10 @@ class Text: - """Displays a simple text line on the drawing.""" + """Displays a simple text line on the drawing. + + .. versionadded:: 1.4.0 + """ def __init__(self, _: Canvas, @@ -30,6 +33,9 @@ def __init__(self, text: The text to display. label: The label carrying the information for updating the text. **__: Other unused arguments. + + .. versionchanged:: 1.5.10 + now explicitly listing the *_*, *coord*, *text* and *label* arguments """ x, y = coord @@ -47,6 +53,9 @@ def update(self, data: Dict[str, float]) -> None: class DotText: """Like :class:`Text`, but with a colored dot to visualize a numerical value. + + .. versionadded:: 1.4.0 + .. versionchanged:: 2.0.0 renamed from Dot_text to DotText """ def __init__(self, @@ -69,6 +78,10 @@ def __init__(self, The value received in label must be a numeric value. It will be normalized on the ``crange`` of the Block and the dot will change color from blue to red depending on this value. + + .. versionchanged:: 1.5.10 + now explicitly listing the *drawing*, *coord*, *text* and *label* + arguments """ x, y = coord @@ -95,7 +108,10 @@ def update(self, data: Dict[str, float]) -> None: class Time: """Displays a time counter on the drawing, starting at the beginning of the - test.""" + test. + + .. versionadded:: 1.4.0 + """ def __init__(self, drawing: Canvas, coord: Tuple[int, int], **__) -> None: """Sets the arguments. @@ -104,6 +120,9 @@ def __init__(self, drawing: Canvas, coord: Tuple[int, int], **__) -> None: drawing: The parent drawing Block. coord: The coordinates of the time counter on the drawing. **__: Other unused arguments. + + .. versionchanged:: 1.5.10 + now explicitly listing the *drawing* and *coord* arguments """ self._block = drawing @@ -132,6 +151,9 @@ class Canvas(Block): representation of data. For simpler displays, the :class:`~crappy.blocks.Dashboard`, :class:`~crappy.blocks.Grapher` and :class:`~crappy.blocks.LinkReader` Blocks should be preferred. + + .. versionadded:: 1.4.0 + .. versionchanged:: 2.0.0 renamed from Drawing to Canvas """ def __init__(self, @@ -183,6 +205,11 @@ def __init__(self, - ``label``: Mandatory for `'text'` and `'dot_text'` only, the label of the data to display. It will try to retrieve this data in the incoming Links. The ``text`` will then be updated with this data. + + .. versionchanged:: 1.5.10 renamed *crange* argument to *color_range* + .. versionadded:: 1.5.10 *verbose* argument + .. versionadded:: 2.0.0 *debug* argument + .. versionchanged:: 2.0.0 renamed *verbose* argument to *display_freq* """ super().__init__() diff --git a/src/crappy/blocks/client_server.py b/src/crappy/blocks/client_server.py index 3be21b0d..d48f433c 100644 --- a/src/crappy/blocks/client_server.py +++ b/src/crappy/blocks/client_server.py @@ -36,6 +36,9 @@ class ClientServer(Block): microcontrollers) acquiring data in enclosed areas or rotating parts, data transfer between machines to delegate processing, communication with other programs on a same machine, etc. + + .. versionadded:: 1.4.0 + .. versionchanged:: 2.0.0 renamed from Client_server to ClientServer """ def __init__(self, @@ -205,6 +208,9 @@ def __init__(self, ('sign',) + .. versionadded:: 1.5.10 *verbose*, *freq* and *spam* arguments + .. versionadded:: 2.0.0 *debug* argument + .. versionchanged:: 2.0.0 renamed *verbose* argument to *display_freq* """ self._client: Optional[mqtt.Client] = None diff --git a/src/crappy/blocks/dashboard.py b/src/crappy/blocks/dashboard.py index aec2453f..0d5084c7 100644 --- a/src/crappy/blocks/dashboard.py +++ b/src/crappy/blocks/dashboard.py @@ -8,7 +8,11 @@ class DashboardWindow(tk.Tk): - """The GUI for displaying the label values.""" + """The GUI for displaying the label values. + + .. versionadded:: 1.5.7 + .. versionchanged:: 2.0.0 renamed from Dashboard_window to DashboardWindow + """ def __init__(self, labels: List[str]) -> None: """Initializes the GUI and sets the layout.""" @@ -63,6 +67,8 @@ class Dashboard(Block): :class:`~crappy.blocks.LinkReader` Block. For displaying the evolution of a label over time, the :class:`~crappy.blocks.Grapher` Block should be used instead. + + .. versionadded:: 1.4.0 """ def __init__(self, @@ -84,6 +90,10 @@ def __init__(self, :obj:`~logging.DEBUG` ones. If :obj:`False`, only displays the log messages with :obj:`~logging.INFO` level or higher. If :obj:`None`, disables logging for this Block. + + .. versionadded:: 1.5.7 *verbose* and *freq* arguments + .. versionadded:: 2.0.0 *debug* argument + .. versionchanged:: 2.0.0 renamed *verbose* argument to *display_freq* """ self._dashboard: Optional[DashboardWindow] = None @@ -98,7 +108,10 @@ def __init__(self, def prepare(self) -> None: """Checks that there's at least one incoming :class:`~crappy.links.Link`, - and starts the GUI.""" + and starts the GUI. + + .. versionadded:: 1.5.7 + """ if not self.inputs: raise IOError("No Link pointing towards the Dashboard Block !") @@ -109,7 +122,10 @@ def prepare(self) -> None: def loop(self) -> None: """Receives the data from the incoming :class:`~crappy.links.Link` and - displays it.""" + displays it. + + .. versionadded:: 1.5.7 + """ data = self.recv_last_data(fill_missing=False) @@ -133,7 +149,10 @@ def loop(self) -> None: pass def finish(self) -> None: - """Closes the display.""" + """Closes the display. + + .. versionadded:: 1.5.7 + """ # In case the GUI has been destroyed, don't raise an error try: diff --git a/src/crappy/blocks/dic_ve.py b/src/crappy/blocks/dic_ve.py index 29fd8ae7..48bfb48a 100644 --- a/src/crappy/blocks/dic_ve.py +++ b/src/crappy/blocks/dic_ve.py @@ -45,6 +45,9 @@ class DICVE(Camera): :class:`~crappy.tool.camera_config.DICVEConfig` window before the test starts. Here, the user can also select the patches to track if they were not already specified as an argument. + + .. versionadded:: 1.4.0 + .. versionchanged:: 2.0.0 renamed from DISVE to DICVE """ def __init__(self, @@ -287,6 +290,23 @@ def __init__(self, **kwargs: Any additional argument will be passed to the :class:`~crappy.camera.Camera` object, and used as a kwarg to its :meth:`~crappy.camera.Camera.open` method. + + .. versionadded:: 1.5.7 *safe* and *follow* arguments + .. versionremoved:: 1.5.9 *fields* argument + .. versionadded:: 1.5.9 *method* argument + .. versionadded:: 1.5.10 + *transform*, *config*, *displayer_backend*, *displayer_framerate*, + *verbose*, *freq*, *save_images*, *img_name*, *save_folder*, + *save_period*, *save_backend* and *image_generator* arguments + .. versionchanged:: 1.5.10 + renamed *gditerations* argument to *gradient_iterations* + .. versionchanged:: 1.5.10 + renamed *show_image* argument to *display_images* + .. versionadded:: 2.0.0 + *debug*, *software_trig_label*, *img_extension*, *raise_on_patch_exit*, + *img_size* and *img_dtype* arguments + .. versionremoved:: 2.0.0 *img_name* argument + .. versionchanged:: 2.0.0 renamed *verbose* argument to *display_freq* """ if not config and patches is None: @@ -353,6 +373,9 @@ def prepare(self) -> None: In addition to that it instantiates the :class:`~crappy.blocks.camera_processes.DICVEProcess` object that performs the image correlation and the tracking. + + .. versionchanged:: 1.5.5 now accepting args and kwargs + .. versionchanged:: 1.5.10 not accepting arguments anymore """ # Instantiating the SpotsBoxes containing the patches to track diff --git a/src/crappy/blocks/dis_correl.py b/src/crappy/blocks/dis_correl.py index 63b833d2..b508487a 100644 --- a/src/crappy/blocks/dis_correl.py +++ b/src/crappy/blocks/dis_correl.py @@ -38,6 +38,8 @@ class DISCorrel(Camera): :class:`~crappy.tool.camera_config.DISCorrelConfig` window before the test starts. Here, the user can also select the patch to track if it was not already specified as an argument. + + .. versionadded:: 1.4.0 """ def __init__(self, @@ -254,6 +256,21 @@ def __init__(self, **kwargs: Any additional argument will be passed to the :class:`~crappy.camera.Camera` object, and used as a kwarg to its :meth:`~crappy.camera.Camera.open` method. + + .. versionadded:: 1.5.10 + *transform*, *config*, *displayer_backend*, *displayer_framerate*, + *verbose*, *freq*, *save_images*, *img_name*, *save_folder*, + *save_period*, *save_backend* and *image_generator* arguments + .. versionchanged:: 1.5.10 + renamed *gditerations* argument to *gradient_iterations* + .. versionchanged:: 1.5.10 + renamed *show_image* argument to *display_images* + .. versionremoved:: 1.5.10 *residual_full* argument + .. versionadded:: 2.0.0 + *debug*, *patch*, *software_trig_label*, *img_extension*, *img_size* and + *img_dtype* arguments + .. versionremoved:: 2.0.0 *img_name* argument + .. versionchanged:: 2.0.0 renamed *verbose* argument to *display_freq* """ if not config and patch is None: @@ -329,6 +346,9 @@ def prepare(self) -> None: In addition to that it instantiates the :class:`~crappy.blocks.camera_processes.DISCorrelProcess` object that performs the image correlation and the tracking. + + .. versionchanged:: 1.5.5 now accepting args and kwargs + .. versionchanged:: 1.5.10 not accepting arguments anymore """ # Instantiating the Box containing the patch to track diff --git a/src/crappy/blocks/fake_machine.py b/src/crappy/blocks/fake_machine.py index ea1bd1bd..ecf3c097 100644 --- a/src/crappy/blocks/fake_machine.py +++ b/src/crappy/blocks/fake_machine.py @@ -33,6 +33,9 @@ class FakeMachine(Block): This Block was originally designed for proposing examples that do not require any hardware to run, but still display the possibilities of Crappy. It can also be used to test a script without actually interacting with hardware. + + .. versionadded:: 1.4.0 + .. versionchanged:: 2.0.0 renamed from Fake_machine to FakeMachine """ def __init__(self, @@ -74,6 +77,12 @@ def __init__(self, :obj:`~logging.DEBUG` ones. If :obj:`False`, only displays the log messages with :obj:`~logging.INFO` level or higher. If :obj:`None`, disables logging for this Block. + + .. versionchanged:: 1.5.10 renamed *maxstrain* argument to *max_strain* + .. versionadded:: 1.5.10 *freq* and *verbose* arguments + .. versionadded:: 2.0.0 *debug* argument + .. versionchanged:: 2.0.0 renamed *k* argument to *rigidity* + .. versionchanged:: 2.0.0 renamed *verbose* argument to *display_freq* """ super().__init__() diff --git a/src/crappy/blocks/generator.py b/src/crappy/blocks/generator.py index 35692608..6fa54879 100644 --- a/src/crappy/blocks/generator.py +++ b/src/crappy/blocks/generator.py @@ -32,6 +32,8 @@ class Generator(Block): used by a :class:`~crappy.blocks.generator_path.meta_path.Path`. The most common use of this feature is to have the stop condition of a Path depend on the received values of a label. + + .. versionadded:: 1.4.0 """ def __init__(self, @@ -83,6 +85,11 @@ def __init__(self, :obj:`~logging.DEBUG` ones. If :obj:`False`, only displays the log messages with :obj:`~logging.INFO` level or higher. If :obj:`None`, disables logging for this Block. + + .. versionremoved:: 1.5.10 *cmd* and *trig_link* argument + .. versionadded:: 1.5.10 *safe_start* argument + .. versionadded:: 2.0.0 *debug* argument + .. versionchanged:: 2.0.0 renamed *verbose* argument to *display_freq* """ super().__init__() diff --git a/src/crappy/blocks/generator_path/conditional.py b/src/crappy/blocks/generator_path/conditional.py index 5c04a3cf..d999c974 100644 --- a/src/crappy/blocks/generator_path/conditional.py +++ b/src/crappy/blocks/generator_path/conditional.py @@ -13,6 +13,8 @@ class Conditional(Path): It is especially useful for controlling processes that need to behave differently based on input values, e.g. for preventing a heating element from overheating, or a motor from driving too far. + + .. versionadded:: 1.4.0 """ def __init__(self, @@ -38,6 +40,12 @@ def __init__(self, Note: This Generator Path never ends, it doesn't have a stop condition. + + .. versionchanged:: 1.5.10 renamed *time* argument to *_last_time* + .. versionchanged:: 1.5.10 renamed *cmd* argument to *_last_cmd* + .. versionremoved:: 1.5.10 *verbose* argument + .. versionremoved:: 2.0.0 *_last_time* and *_last_cmd* arguments + .. versionchanged:: 2.0.0 renamed from Protection to Conditional """ super().__init__() diff --git a/src/crappy/blocks/generator_path/constant.py b/src/crappy/blocks/generator_path/constant.py index 5c56eb9f..8d79907c 100644 --- a/src/crappy/blocks/generator_path/constant.py +++ b/src/crappy/blocks/generator_path/constant.py @@ -8,7 +8,10 @@ class Constant(Path): """The simplest Path, outputs the same constant value until the stop - condition is met.""" + condition is met. + + .. versionadded:: 1.4.0 + """ def __init__(self, condition: Union[str, ConditionType], @@ -20,6 +23,11 @@ def __init__(self, :class:`~crappy.blocks.generator_path.meta_path.Path` for more information. value: The value to output. + + .. versionchanged:: 1.5.10 renamed *time* argument to *_last_time* + .. versionchanged:: 1.5.10 renamed *cmd* argument to *_last_cmd* + .. versionremoved:: 1.5.10 *send_one* argument + .. versionremoved:: 2.0.0 *_last_time* and *_last_cmd* arguments """ super().__init__() @@ -33,7 +41,10 @@ def __init__(self, def get_cmd(self, data: Dict[str, list]) -> float: """Returns the value to send or raises :exc:`StopIteration` if the stop - condition is met.""" + condition is met. + + .. versionadded:: 1.5.10 + """ # Checking if the stop condition is met if self._condition(data): diff --git a/src/crappy/blocks/generator_path/custom.py b/src/crappy/blocks/generator_path/custom.py index baccc1ce..4a738349 100644 --- a/src/crappy/blocks/generator_path/custom.py +++ b/src/crappy/blocks/generator_path/custom.py @@ -14,6 +14,8 @@ class Custom(Path): The file can be in any text format, including the most common `.csv` and `.txt` extensions. + + .. versionadded:: 1.4.0 """ def __init__(self, @@ -30,6 +32,11 @@ def __init__(self, must contain two columns: the first one containing timestamps (starting from 0), the other one containing the values. delimiter: The delimiter between columns in the file, usually a coma. + + .. versionchanged:: 1.5.10 renamed *time* argument to *_last_time* + .. versionchanged:: 1.5.10 renamed *cmd* argument to *_last_cmd* + .. versionchanged:: 2.0.0 renamed *filename* argument to *file_name* + .. versionremoved:: 2.0.0 *_last_time* and *_last_cmd* arguments """ super().__init__() diff --git a/src/crappy/blocks/generator_path/cyclic.py b/src/crappy/blocks/generator_path/cyclic.py index 071fa65a..cb87f793 100644 --- a/src/crappy/blocks/generator_path/cyclic.py +++ b/src/crappy/blocks/generator_path/cyclic.py @@ -15,6 +15,8 @@ class Cyclic(Path): It can for example be used as a trigger, or used to drive an actuator cyclically. It is equivalent to a succession of :class:`~crappy.blocks.generator_path.Constant` Paths. + + .. versionadded:: 1.4.0 """ def __init__(self, @@ -50,6 +52,11 @@ def __init__(self, [{'type': 'Constant', 'value': 1,'condition': 'AIN0>2'}, {'type': 'Constant', 'value': 0, 'condition': 'AIN1<1'}] * 5 + + .. versionchanged:: 1.5.10 renamed *time* argument to *_last_time* + .. versionchanged:: 1.5.10 renamed *cmd* argument to *_last_cmd* + .. versionremoved:: 1.5.10 *verbose* argument + .. versionremoved:: 2.0.0 *_last_time* and *_last_cmd* arguments """ super().__init__() diff --git a/src/crappy/blocks/generator_path/cyclic_ramp.py b/src/crappy/blocks/generator_path/cyclic_ramp.py index 0e615d35..56e967f0 100644 --- a/src/crappy/blocks/generator_path/cyclic_ramp.py +++ b/src/crappy/blocks/generator_path/cyclic_ramp.py @@ -14,6 +14,9 @@ class CyclicRamp(Path): It is equivalent to a succession of :class:`~crappy.blocks.generator_path.Ramp` Paths. + + .. versionadded:: 1.4.0 + .. versionchanged:: 2.0.0 renamed from Cyclic_ramp to CyclicRamp """ def __init__(self, @@ -53,6 +56,12 @@ def __init__(self, [{'type': 'Ramp', 'speed': 5,'condition': 'AIN0>2'}, {'type': 'Ramp', 'value': -2, 'condition': 'AIN1<1'}] * 5 + + .. versionchanged:: 1.5.10 renamed *time* argument to *_last_time* + .. versionchanged:: 1.5.10 renamed *cmd* argument to *_last_cmd* + .. versionremoved:: 1.5.10 *verbose* argument + .. versionadded:: 1.5.10 *init_value* argument + .. versionremoved:: 2.0.0 *_last_time* and *_last_cmd* arguments """ super().__init__() diff --git a/src/crappy/blocks/generator_path/integrator.py b/src/crappy/blocks/generator_path/integrator.py index 505a1023..49c3c7b4 100644 --- a/src/crappy/blocks/generator_path/integrator.py +++ b/src/crappy/blocks/generator_path/integrator.py @@ -16,6 +16,9 @@ class Integrator(Path): Then the output value for this Path will be :math:`v(t) = v(t0) - [I(t0 -> t)f(t)dt] / m`. + + .. versionadded:: 1.4.0 + .. versionchanged:: 2.0.0 renamed from Inertia to Integrator """ def __init__(self, @@ -39,6 +42,14 @@ def __init__(self, starting point for the inertia path. In the specific case when this path is the first one in the Generator Paths, this argument must be given ! + + .. versionchanged:: 1.5.10 renamed *time* argument to *_last_time* + .. versionchanged:: 1.5.10 renamed *cmd* argument to *_last_cmd* + .. versionchanged:: 1.5.10 renamed *flabel* argument to *func_label* + .. versionchanged:: 1.5.10 renamed *tlabel* argument to *time_label* + .. versionchanged:: 1.5.10 renamed *value* argument to *init_value* + .. versionremoved:: 1.5.10 *const* argument + .. versionremoved:: 2.0.0 *_last_time* and *_last_cmd* arguments """ super().__init__() diff --git a/src/crappy/blocks/generator_path/meta_path/meta_path.py b/src/crappy/blocks/generator_path/meta_path/meta_path.py index 45f89185..ea9490d2 100644 --- a/src/crappy/blocks/generator_path/meta_path/meta_path.py +++ b/src/crappy/blocks/generator_path/meta_path/meta_path.py @@ -6,7 +6,10 @@ class MetaPath(type): """Metaclass ensuring that two Paths don't have the same name, and that all Paths define the required methods. Also keeps track of all the Path - classes, including the custom user-defined ones.""" + classes, including the custom user-defined ones. + + .. versionadded:: 2.0.0 + """ classes = dict() diff --git a/src/crappy/blocks/generator_path/meta_path/path.py b/src/crappy/blocks/generator_path/meta_path/path.py index 825edce5..020739d0 100644 --- a/src/crappy/blocks/generator_path/meta_path/path.py +++ b/src/crappy/blocks/generator_path/meta_path/path.py @@ -16,6 +16,8 @@ class Path(metaclass=MetaPath): The Path object are used by the :class:`~crappy.blocks.Generator` Block to generate signals. + + .. versionadded:: 1.4.0 """ t0: Optional[float] = None @@ -34,6 +36,10 @@ def __init__(self, *_, **__) -> None: previous :class:`~crappy.blocks.generator_path.meta_path.Path` was sent, and the ``self.last_cmd`` stores the value of the last command of the previous :class:`~crappy.blocks.generator_path.meta_path.Path`. + + .. versionchanged:: 1.5.10 renamed *time* argument to *_last_time* + .. versionchanged:: 1.5.10 renamed *cmd* argument to *_last_cmd* + .. versionremoved:: 2.0.0 *_last_time* and *_last_cmd* arguments """ self._logger: Optional[logging.Logger] = None @@ -84,6 +90,8 @@ def log(self, level: int, msg: str) -> None: Args: level: An :obj:`int` indicating the logging level of the message. msg: The message to log, as a :obj:`str`. + + .. versionadded:: 2.0.0 """ if self._logger is None: diff --git a/src/crappy/blocks/generator_path/ramp.py b/src/crappy/blocks/generator_path/ramp.py index 89da5e23..bbcdfd75 100644 --- a/src/crappy/blocks/generator_path/ramp.py +++ b/src/crappy/blocks/generator_path/ramp.py @@ -9,7 +9,10 @@ class Ramp(Path): """Sends a ramp signal varying linearly over time, until the stop condition - is met.""" + is met. + + .. versionadded:: 1.4.0 + """ def __init__(self, condition: Union[str, ConditionType], @@ -25,6 +28,11 @@ def __init__(self, init_value: If given, overwrites the last value of the signal as the starting point for the ramp. In the specific case when this path is the first one in the Generator Paths, this argument must be given ! + + .. versionchanged:: 1.5.10 renamed *time* argument to *_last_time* + .. versionchanged:: 1.5.10 renamed *cmd* argument to *_last_cmd* + .. versionadded:: 1.5.10 *init_value* argument + .. versionremoved:: 2.0.0 *_last_time* and *_last_cmd* arguments """ super().__init__() diff --git a/src/crappy/blocks/generator_path/sine.py b/src/crappy/blocks/generator_path/sine.py index de92fd12..716dc563 100644 --- a/src/crappy/blocks/generator_path/sine.py +++ b/src/crappy/blocks/generator_path/sine.py @@ -10,7 +10,10 @@ class Sine(Path): """This Path generates a sine wave varying with time until the stop condition - is met.""" + is met. + + .. versionadded:: 1.4.0 + """ def __init__(self, condition: Union[str, ConditionType], @@ -28,6 +31,10 @@ def __init__(self, amplitude: The amplitude of the sine wave (peak to peak). offset: The offset of the sine (average value). phase: The phase of the sine (in radians). + + .. versionchanged:: 1.5.10 renamed *time* argument to *_last_time* + .. versionchanged:: 1.5.10 renamed *cmd* argument to *_last_cmd* + .. versionremoved:: 2.0.0 *_last_time* and *_last_cmd* arguments """ super().__init__() diff --git a/src/crappy/blocks/gpu_correl.py b/src/crappy/blocks/gpu_correl.py index 3943b450..72d87ae9 100644 --- a/src/crappy/blocks/gpu_correl.py +++ b/src/crappy/blocks/gpu_correl.py @@ -34,6 +34,8 @@ class GPUCorrel(Camera): correlation is performed on the entire image. It is however possible to set a mask, so that only part of the image is considered when running the correlation. + + .. versionadded:: 1.4.0 """ def __init__(self, @@ -242,6 +244,22 @@ def __init__(self, **kwargs: Any additional argument will be passed to the :class:`~crappy.camera.Camera` object, and used as a kwarg to its :meth:`~crappy.camera.Camera.open` method. + + .. versionadded:: 1.5.10 + *transform*, *display_images*, *displayer_backend*, + *displayer_framerate*, *freq*, *save_images*, *image_generator*, + *levels*, *resampling_factor*, *kernel_file*, *iterations*, *mask*, + *mul* and *res* arguments + .. versionchanged:: 1.5.10 + renamed *discard_lim* argument to *discard_limit* + .. versionchanged:: 1.5.10 + renamed *imgref* argument to *img_ref* + .. versionremoved:: 1.5.10 + *fps_label*, *ext*, *input_label*, *config* and *cam_kwargs* arguments + .. versionadded:: 2.0.0 + *debug*, *software_trig_label*, *img_extension*, *img_shape* and + *img_dtype* arguments + .. versionremoved:: 2.0.0 *img_name* argument """ super().__init__(camera=camera, @@ -307,6 +325,9 @@ def prepare(self) -> None: In addition to that it instantiates the :class:`~crappy.blocks.camera_processes.GPUCorrelProcess` object that performs the GPU-accelerated image correlation. + + .. versionchanged:: 1.5.5 now accepting args and kwargs + .. versionchanged:: 1.5.10 not accepting arguments anymore """ # Instantiating the GPUCorrelProcess diff --git a/src/crappy/blocks/gpu_ve.py b/src/crappy/blocks/gpu_ve.py index 1bdea0dd..838a1137 100644 --- a/src/crappy/blocks/gpu_ve.py +++ b/src/crappy/blocks/gpu_ve.py @@ -28,6 +28,8 @@ class GPUVE(Camera): have a much greater accuracy. The :class:`~crappy.blocks.VideoExtenso` Block also performs video-extensometry, but it does so by tracking spots instead of textured patches, and it is not GPU-accelerated. + + .. versionadded:: 1.4.0 """ def __init__(self, @@ -110,7 +112,7 @@ def __init__(self, on the camera. verbose: The verbose level as an integer, between `0` and `3`. At level `0` no information is displayed, and at level `3` so much information - is displayed that is slows the code down. This argument allows to + is displayed that it slows the code down. This argument allows to adjust the precision of the log messages, while the ``debug`` argument is for enabling or disabling logging. freq: The target looping frequency for the Block. If :obj:`None`, loops @@ -201,6 +203,17 @@ def __init__(self, **kwargs: Any additional argument will be passed to the :class:`~crappy.camera.Camera` object, and used as a kwarg to its :meth:`~crappy.camera.Camera.open` method. + + .. versionadded:: 1.5.10 + *freq*, *save_images*, *image_generator*, *img_ref*, *kernel_file*, + *iterations* and *mul* arguments + .. versionremoved:: 1.5.10 + *fps_label*, *ext*, *input_label*, *config* and *cam_kwargs* arguments + .. versionadded:: 2.0.0 + *display_images*, *displayer_framerate*, *displayer_backend*, *debug*, + *software_trig_label*, *img_extension*, *img_shape* and *img_dtype* + arguments + .. versionremoved:: 2.0.0 *img_name* argument """ super().__init__(camera=camera, @@ -258,6 +271,9 @@ def prepare(self) -> None: In addition to that it instantiates the :class:`~crappy.blocks.camera_processes.GPUVEProcess` object that performs the GPU-accelerated image correlation. + + .. versionchanged:: 1.5.5 now accepting args and kwargs + .. versionchanged:: 1.5.10 not accepting arguments anymore """ # Instantiating the GPUVEProcess diff --git a/src/crappy/blocks/grapher.py b/src/crappy/blocks/grapher.py index 7de96606..3f172ebd 100644 --- a/src/crappy/blocks/grapher.py +++ b/src/crappy/blocks/grapher.py @@ -27,6 +27,8 @@ class Grapher(Block): the last values of given labels, the :class:`~crappy.blocks.LinkReader` and :class:`~crappy.blocks.Dashboard` Blocks are simpler solutions that go much easier on the CPU. + + .. versionadded:: 1.4.0 """ def __init__(self, @@ -88,6 +90,11 @@ def __init__(self, graph = Grapher(('t(s)', 'F(N)'), length=30) will plot a dynamic graph displaying the last 30 chunks of data. + + .. versionadded:: 1.5.6 *verbose* argument + .. versionadded 2.0.0 *debug* argument + .. versionchanged:: 2.0.0 renamed *verbose* argument to *display_freq* + .. versionchanged:: 2.0.0 renamed *maxpt* argument to *max_pt* """ super().__init__() diff --git a/src/crappy/blocks/hdf_recorder.py b/src/crappy/blocks/hdf_recorder.py index 8e00c78a..440c1004 100644 --- a/src/crappy/blocks/hdf_recorder.py +++ b/src/crappy/blocks/hdf_recorder.py @@ -30,6 +30,9 @@ class HDFRecorder(Block): Corrupted HDF5 files are not readable at all ! If anything goes wrong during a test, especially during the finish phase, it is not guaranteed that the recorded data will be readable. + + .. versionadded:: 1.4.0 + .. versionchanged:: 2.0.0 renamed from Hdf_recorder to HDFRecorder """ def __init__(self, @@ -66,6 +69,10 @@ def __init__(self, :obj:`~logging.DEBUG` ones. If :obj:`False`, only displays the log messages with :obj:`~logging.INFO` level or higher. If :obj:`None`, disables logging for this Block. + + .. versionadded:: 1.5.10 *freq* and *verbose* arguments + .. versionadded 2.0.0 *debug* argument + .. versionchanged:: 2.0.0 renamed *verbose* argument to *display_freq* """ self._hfile = None diff --git a/src/crappy/blocks/ioblock.py b/src/crappy/blocks/ioblock.py index 88541b8e..2dfbee0e 100644 --- a/src/crappy/blocks/ioblock.py +++ b/src/crappy/blocks/ioblock.py @@ -28,6 +28,8 @@ class IOBlock(Block): ``make_zero_delay`` argument allows offsetting the acquired values to zero at the beginning of the test. Refer to the documentation of each argument for a more detailed description. + + .. versionadded:: 1.4.0 """ def __init__(self, @@ -95,6 +97,12 @@ def __init__(self, messages with :obj:`~logging.INFO` level or higher. If :obj:`None`, disables logging for this Block. **kwargs: The arguments to be passed to the :class:`~crappy.inout.InOut`. + + .. versionchanged:: 1.5.10 renamed *trigger* argument to *trigger_label* + .. versionchanged:: 1.5.10 renamed *exit_values* argument to *exit_cmd* + .. versionadded:: 1.5.10 *make_zero_delay* argument + .. versionadded 2.0.0 *debug* argument + .. versionchanged:: 2.0.0 renamed *verbose* argument to *display_freq* """ self._device: Optional[InOut] = None diff --git a/src/crappy/blocks/link_reader.py b/src/crappy/blocks/link_reader.py index f08dba73..41912853 100644 --- a/src/crappy/blocks/link_reader.py +++ b/src/crappy/blocks/link_reader.py @@ -17,6 +17,9 @@ class LinkReader(Block): :class:`~crappy.blocks.Dashboard` Block can be used for a nicer layout, and the :class:`~crappy.blocks.Grapher` Block should be used for plotting data in a persistent way. + + .. versionadded:: 1.4.0 + .. versionchanged:: 2.0.0 renamed from Reader to LinkReader """ _index = 0 @@ -40,6 +43,12 @@ def __init__(self, :obj:`~logging.DEBUG` ones. If :obj:`False`, only displays the log messages with :obj:`~logging.INFO` level or higher. If :obj:`None`, disables logging for this Block. + + .. versionchanged:: 1.5.5 renamed *name* argument to *reader_name* + .. versionadded:: 1.5.10 *freq* and *verbose* arguments + .. versionchanged:: 1.5.5 renamed *reader_name* argument to *name* + .. versionadded 2.0.0 *debug* argument + .. versionchanged:: 2.0.0 renamed *verbose* argument to *display_freq* """ super().__init__() diff --git a/src/crappy/blocks/machine.py b/src/crappy/blocks/machine.py index 3c921ab7..c28686a8 100644 --- a/src/crappy/blocks/machine.py +++ b/src/crappy/blocks/machine.py @@ -46,6 +46,8 @@ class Machine(Block): commands, and optionally the labels over which it sends its current speed and/or position. The driving mode (`'speed'` or `'position'`) can also be set independently for each Actuator. + + .. versionadded:: 1.4.0 """ def __init__(self, @@ -111,6 +113,9 @@ def __init__(self, in `'position'` mode. Each time a value is received, the stored speed value is updated. It will also overwrite the ``speed`` key if given. + .. versionadded:: 1.5.10 *verbose* argument + .. versionadded 2.0.0 *debug* and *ft232h_ser_num* arguments + .. versionchanged:: 2.0.0 renamed *verbose* argument to *display_freq* """ self._actuators: List[ActuatorInstance] = list() diff --git a/src/crappy/blocks/mean.py b/src/crappy/blocks/mean.py index a080e6a1..1dd15ba0 100644 --- a/src/crappy/blocks/mean.py +++ b/src/crappy/blocks/mean.py @@ -29,6 +29,9 @@ class MeanBlock(Block): of the upstream Blocks, this Block may not always return the same number of labels ! This can cause errors in downstream Blocks expecting a fixed number of labels. + + .. versionadded:: 1.4.0 + .. versionchanged:: 2.0.0 renamed from Mean_block to MeanBlock """ def __init__(self, @@ -58,6 +61,11 @@ def __init__(self, :obj:`~logging.DEBUG` ones. If :obj:`False`, only displays the log messages with :obj:`~logging.INFO` level or higher. If :obj:`None`, disables logging for this Block. + + .. versionadded:: 1.5.10 *verbose* argument + .. versionchanged:: 1.5.10 renamed *t_label* argument to *time_label* + .. versionadded 2.0.0 *debug* argument + .. versionchanged:: 2.0.0 renamed *verbose* argument to *display_freq* """ super().__init__() @@ -79,7 +87,10 @@ def __init__(self, self._last_sent_t = time() def begin(self) -> None: - """Initializes the time counter.""" + """Initializes the time counter. + + .. versionadded:: 2.0.0 + """ self._last_sent_t = self.t0 diff --git a/src/crappy/blocks/meta_block/block.py b/src/crappy/blocks/meta_block/block.py index b85c16e6..ceaf0672 100644 --- a/src/crappy/blocks/meta_block/block.py +++ b/src/crappy/blocks/meta_block/block.py @@ -39,6 +39,8 @@ class Block(Process, metaclass=MetaBlock): This class also contains the class methods that allow driving a script with Crappy. They are always called in the `__main__` Process, and drive the execution of all the children Blocks. + + .. versionadded:: 1.4.0 """ instances = WeakSet() @@ -112,7 +114,10 @@ def __new__(cls, *args, **kwargs): @classmethod def get_name(cls, name: str) -> str: """Method attributing to each new Block a unique name, based on the name of - the class and the number of existing instances for this class.""" + the class and the number of existing instances for this class. + + .. versionadded:: 2.0.0 + """ i = 1 while f"crappy.{name}-{i}" in cls.names: @@ -155,6 +160,10 @@ def start_all(cls, execution of code that would come after Crappy, in case Crappy does not terminate as expected. This behavior can be disabled by setting this argument to :obj:`True`. + + .. versionchanged:: 2.0.0 renamed *high_prio* argument to *allow_root* + .. versionremoved:: 2.0.0 *t0*, *verbose*, *bg* arguments + .. versionadded:: 2.0.0 *log_level*, *no_raise* arguments """ cls.prepare_all(log_level) @@ -182,6 +191,9 @@ def prepare_all(cls, log_level: Optional[int] = logging.DEBUG) -> None: set to :obj:`None`, logging is totally disabled. Refer to the documentation of the :mod:`logging` module for information on the possible levels. + + .. versionremoved:: 2.0.0 *verbose* argument + .. versionadded:: 2.0.0 *log_level* argument """ # Flag indicating whether to perform the cleanup action or not @@ -317,6 +329,8 @@ def renice_all(cls, allow_root: bool) -> None: allow_root: If set to :obj:`True`, tries to renice the Processes with sudo privilege in Linux. It requires the Python script to be run with sudo privilege, otherwise it has no effect. + + .. versionchanged:: 2.0.0 renamed *high_prio* argument to *allow_root* """ # Flag indicating whether to perform the cleanup action or not @@ -413,6 +427,8 @@ def launch_all(cls, no_raise: bool = False) -> None: execution of code that would come after Crappy, in case Crappy does not terminate as expected. This behavior can be disabled by setting this argument to :obj:`True`. + + .. versionremoved:: 2.0.0 *t0*, *verbose* and *bg* arguments """ # Setting the no_raise flag @@ -682,7 +698,10 @@ def _set_logger(cls) -> None: @classmethod def stop_all(cls) -> None: """Method for stopping all the Blocks by setting the stop - :obj:`~multiprocessing.Event`.""" + :obj:`~multiprocessing.Event`. + + .. versionremoved:: 2.0.0 *verbose* argument + """ if cls.stop_event is not None: cls.stop_event.set() @@ -722,6 +741,8 @@ def cls_log(cls, level: int, msg: str) -> None: Ensures the Logger exists before trying to log, thus avoiding potential errors. + + .. versionadded:: 2.0.0 """ if cls.logger is None: @@ -1058,6 +1079,8 @@ def debug(self) -> Optional[bool]: level for the Block. And if :obj:`None`, displays only the :obj:`~logging.CRITICAL` logging level, which is equivalent to no information at all. + + .. versionadded:: 2.0.0 """ return self._debug @@ -1078,7 +1101,10 @@ def debug(self, val: Optional[bool]) -> None: @property def t0(self) -> float: """Returns the value of t0, the exact moment when the test started that is - shared between all the Blocks.""" + shared between all the Blocks. + + .. versionadded:: 2.0.0 + """ if self._instance_t0 is not None and self._instance_t0.value > 0: self.log(logging.DEBUG, "Start time value requested") @@ -1105,6 +1131,8 @@ def log(self, log_level: int, msg: str) -> None: Args: log_level: An :obj:`int` indicating the logging level of the message. msg: The message to log, as a :obj:`str`. + + .. versionadded:: 2.0.0 """ if self._logger is None: @@ -1160,7 +1188,10 @@ def send(self, data: Optional[Union[Dict[str, Any], Iterable[Any]]]) -> None: def data_available(self) -> bool: """Returns :obj:`True` if there's data available for reading in at least - one of the input :class:`~crappy.links.Link`.""" + one of the input :class:`~crappy.links.Link`. + + .. versionchanged:: 2.0.0 renamed from poll to data_available + """ self.log(logging.DEBUG, "Data availability requested") return self.inputs and any(link.poll() for link in self.inputs) @@ -1182,6 +1213,8 @@ def recv_data(self) -> Dict[str, Any]: Returns: A :obj:`dict` whose keys are the received labels and with a single value for each key (usually a :obj:`float` or a :obj:`str`). + + .. versionchanged:: 2.0.0 renamed from recv_all to recv_data """ ret = dict() @@ -1212,6 +1245,10 @@ def recv_last_data(self, fill_missing: bool = True) -> Dict[str, Any]: Returns: A :obj:`dict` whose keys are the received labels and with a single value for each key (usually a :obj:`float` or a :obj:`str`). + + .. versionremoved:: 1.5.10 *num* argument + .. versionadded:: 1.5.10 *blocking* argument + .. versionchanged:: 2.0.0 renamed from get_last to recv_last_data """ # Initializing the buffer storing the last received values @@ -1265,6 +1302,10 @@ def recv_all_data(self, A :obj:`dict` whose keys are the received labels and with a :obj:`list` of received values for each key. The first item in the list is the oldest one available in the Link, the last item is the newest available. + + .. versionremoved:: 1.5.10 *num* argument + .. versionadded:: 1.5.10 *blocking* argument + .. versionchanged:: 2.0.0 renamed from get_all_last to recv_all_data """ ret = defaultdict(list) @@ -1314,6 +1355,8 @@ def recv_all_data_raw(self, Returns: A :obj:`list` containing :obj:`dict`, whose keys are the received labels and with a :obj:`list` of received value for each key. + + .. versionadded:: 2.0.0 """ ret = [defaultdict(list) for _ in self.inputs] diff --git a/src/crappy/blocks/meta_block/meta_block.py b/src/crappy/blocks/meta_block/meta_block.py index 36b4c20c..4b3a5aca 100644 --- a/src/crappy/blocks/meta_block/meta_block.py +++ b/src/crappy/blocks/meta_block/meta_block.py @@ -6,7 +6,10 @@ class MetaBlock(type): """Metaclass ensuring that two Blocks don't have the same name, and that all Blocks define the required methods. Also keeps track of all the Block - classes, including the custom user-defined ones.""" + classes, including the custom user-defined ones. + + .. versionadded:: 2.0.0 + """ existing = list() diff --git a/src/crappy/blocks/multiplexer.py b/src/crappy/blocks/multiplexer.py index 4f589093..e751dee9 100644 --- a/src/crappy/blocks/multiplexer.py +++ b/src/crappy/blocks/multiplexer.py @@ -30,6 +30,9 @@ class Multiplexer(Block): should only be used with care as an input to a decision-making Block. This is especially true when the upstream Blocks have very different sampling rates. + + .. versionadded:: 1.4.0 + .. versionchanged:: 2.0.0 renamed from Multiplex to Multiplexer """ def __init__(self, @@ -59,6 +62,11 @@ def __init__(self, :obj:`~logging.DEBUG` ones. If :obj:`False`, only displays the log messages with :obj:`~logging.INFO` level or higher. If :obj:`None`, disables logging for this Block. + + .. versionadded:: 1.5.9 *verbose* argument + .. versionchanged:: 1.5.10 renamed *key* argument to *time_label* + .. versionadded 2.0.0 *debug*, *out_labels* and *interp_freq* arguments + .. versionchanged:: 2.0.0 renamed *verbose* argument to *display_freq* """ super().__init__() diff --git a/src/crappy/blocks/pid.py b/src/crappy/blocks/pid.py index 5445ee64..5942d3d0 100644 --- a/src/crappy/blocks/pid.py +++ b/src/crappy/blocks/pid.py @@ -21,6 +21,8 @@ class PID(Block): a :class:`~crappy.blocks.Machine` or :class:`~crappy.blocks.IOBlock` Block driving a physical system. This latter Block takes the command as input, and returns to the PID the measured value to be compared to the target. + + .. versionadded:: 1.4.0 """ def __init__(self, @@ -90,6 +92,13 @@ def __init__(self, :obj:`~logging.DEBUG` ones. If :obj:`False`, only displays the log messages with :obj:`~logging.INFO` level or higher. If :obj:`None`, disables logging for this Block. + + .. versionadded:: 1.5.10 *verbose* argument + .. versionadded 2.0.0 + *debug*, *kp_label*, *kd_label* and *ki_label* arguments + .. versionchanged:: 2.0.0 renamed *verbose* argument to *display_freq* + .. versionchanged:: 2.0.0 + renamed *target_label* argument to *setpoint_label* """ # Attributes of the parent class diff --git a/src/crappy/blocks/recorder.py b/src/crappy/blocks/recorder.py index 50277c1d..e590b6de 100644 --- a/src/crappy/blocks/recorder.py +++ b/src/crappy/blocks/recorder.py @@ -22,6 +22,8 @@ class Recorder(Block): be used instead. Alternatively, a :class:`~crappy.modifier.Demux` Modifier can be placed between the IOBlock and the Recorder, but most of the acquired data won't be saved. + + .. versionadded:: 1.4.0 """ def __init__(self, @@ -49,6 +51,11 @@ def __init__(self, :obj:`~logging.DEBUG` ones. If :obj:`False`, only displays the log messages with :obj:`~logging.INFO` level or higher. If :obj:`None`, disables logging for this Block. + + .. versionadded 1.5.10 *freq* and *verbose* arguments + .. versionadded 2.0.0 *debug* argument + .. versionchanged:: 2.0.0 renamed *verbose* argument to *display_freq* + .. versionchanged:: 2.0.0 renamed *filename* argument to *file_name* """ super().__init__() diff --git a/src/crappy/blocks/sink.py b/src/crappy/blocks/sink.py index 6095b2f0..b42cb501 100644 --- a/src/crappy/blocks/sink.py +++ b/src/crappy/blocks/sink.py @@ -11,6 +11,8 @@ class Sink(Block): It is only useful for debugging, e.g. with Blocks like the :class:`~crappy.blocks.IOBlock` that have a different behavior when they have output Links. + + .. versionadded:: 1.4.0 """ def __init__(self, @@ -28,6 +30,10 @@ def __init__(self, :obj:`~logging.DEBUG` ones. If :obj:`False`, only displays the log messages with :obj:`~logging.INFO` level or higher. If :obj:`None`, disables logging for this Block. + + .. versionadded 1.5.6 *verbose* and *freq* arguments + .. versionadded 2.0.0 *debug* argument + .. versionchanged:: 2.0.0 renamed *verbose* argument to *display_freq* """ super().__init__() diff --git a/src/crappy/blocks/stop_block.py b/src/crappy/blocks/stop_block.py index 50532941..20cb5e9c 100644 --- a/src/crappy/blocks/stop_block.py +++ b/src/crappy/blocks/stop_block.py @@ -14,6 +14,8 @@ class StopBlock(Block): Along with the :class:`~crappy.blocks.StopButton` Block, it allows to stop a test in a clean way without resorting to CTRL+C. + + .. versionadded 2.0.0 """ def __init__(self, diff --git a/src/crappy/blocks/stop_button.py b/src/crappy/blocks/stop_button.py index e84abf29..921b72d4 100644 --- a/src/crappy/blocks/stop_button.py +++ b/src/crappy/blocks/stop_button.py @@ -18,6 +18,8 @@ class StopButton(Block): Along with the :class:`~crappy.blocks.StopBlock`, it allows to stop a test in a clean way without resorting to CTRL+C. + + .. versionadded 2.0.0 """ def __init__(self, diff --git a/src/crappy/blocks/ucontroller.py b/src/crappy/blocks/ucontroller.py index 35bde698..4b67ebae 100644 --- a/src/crappy/blocks/ucontroller.py +++ b/src/crappy/blocks/ucontroller.py @@ -25,6 +25,8 @@ class UController(Block): MicroPython template located in the `tool` folder of Crappy, even though it is not mandatory. A given syntax needs to be followed for any data to be exchanged. + + .. versionadded:: 1.5.8 """ def __init__(self, @@ -79,6 +81,9 @@ def __init__(self, :obj:`~logging.DEBUG` ones. If :obj:`False`, only displays the log messages with :obj:`~logging.INFO` level or higher. If :obj:`None`, disables logging for this Block. + + .. versionadded:: 2.0.0 *debug* argument + .. versionchanged:: 2.0.0 renamed *verbose* argument to *display_freq* """ self._bus = None diff --git a/src/crappy/blocks/video_extenso.py b/src/crappy/blocks/video_extenso.py index ffda2f9d..149b49c1 100644 --- a/src/crappy/blocks/video_extenso.py +++ b/src/crappy/blocks/video_extenso.py @@ -36,6 +36,9 @@ class VideoExtenso(Camera): currently not possible to specify the coordinates of the spots to track as an argument, so the use of the configuration window is mandatory. This might change in the future. + + .. versionadded:: 1.4.0 + .. versionchanged:: 2.0.0 renamed from Video_extenso to VideoExtenso """ def __init__(self, @@ -238,6 +241,20 @@ def __init__(self, **kwargs: Any additional argument will be passed to the :class:`~crappy.camera.Camera` object, and used as a kwarg to its :meth:`~crappy.camera.Camera.open` method. + + .. versionadded:: 1.5.10 + *display_images*, *displayer_backend*, *displayer_framerate*, *freq*, + *save_images* and *image_generator* arguments + .. versionremoved:: 1.5.10 + *ext*, *fps_label*, *wait_l0* and *input_label* arguments + .. versionchanged:: 1.5.10 + renamed *show_image* argument to *display_images* + .. versionchanged:: 1.5.10 renamed *end* argument to *raise_on_lost_spot* + .. versionadded:: 2.0.0 + *debug*, *software_trig_label*, *img_extension*, *img_shape* and + *img_dtype* arguments + .. versionremoved:: 2.0.0 *img_name* argument + .. versionchanged:: 2.0.0 renamed *verbose* argument to *display_freq* """ super().__init__(camera=camera, @@ -292,6 +309,9 @@ def prepare(self) -> None: In addition to that it instantiates the :class:`~crappy.blocks.camera_processes.VideoExtensoProcess` object that performs the video-extensometry and the tracking. + + .. versionchanged:: 1.5.5 now accepting args and kwargs + .. versionchanged:: 1.5.10 not accepting arguments anymore """ # Instantiating the SpotsDetector containing the spots to track diff --git a/src/crappy/camera/cameralink/basler_ironman_cameralink.py b/src/crappy/camera/cameralink/basler_ironman_cameralink.py index 66dd5840..85fef8ab 100644 --- a/src/crappy/camera/cameralink/basler_ironman_cameralink.py +++ b/src/crappy/camera/cameralink/basler_ironman_cameralink.py @@ -29,6 +29,9 @@ class BaslerIronmanCameraLink(Camera): This Camera relies on a custom-written C library that hasn't been tested in a long time. It might not be functional anymore. This Camera also requires proprietary drivers to be installed. + + .. versionadded:: 1.4.0 + .. versionchanged:: 2.0.0 renamed from Cl_camera to BaslerIronmanCameraLink """ def __init__(self) -> None: @@ -57,6 +60,11 @@ def open(self, them in a persistent way. camera_type: The type of camera to drive, as a :obj:`str`. **kwargs: All the settings to set on the camera. + + .. versionadded:: 1.5.10 + explicitly listing the *num_device*, *config_file* and *camera_type* + arguments + .. versionchanged:: 2.0.0 renamed *numdevice* argument to *num_device* """ # Reading the camera type from the config file, if applicable diff --git a/src/crappy/camera/cameralink/jai_go_5000c_pmcl.py b/src/crappy/camera/cameralink/jai_go_5000c_pmcl.py index b89b7999..885cef45 100644 --- a/src/crappy/camera/cameralink/jai_go_5000c_pmcl.py +++ b/src/crappy/camera/cameralink/jai_go_5000c_pmcl.py @@ -33,6 +33,9 @@ class JaiGO5000CPMCL8Bits(BaslerIronmanCameraLink): This Camera relies on a custom-written C library that hasn't been tested in a long time. It might not be functional anymore. This Camera also requires proprietary drivers to be installed. + + .. versionadded:: 1.4.0 + .. versionchanged:: 2.0.0 renamed from Jai8 to JaiGO5000CPMCL8Bits """ def __init__(self) -> None: @@ -58,6 +61,9 @@ def open(self, them in a persistent way. camera_type: The type of camera to drive, as a :obj:`str`. **kwargs: All the settings to set on the camera. + + .. versionadded:: 1.5.10 + explicitly listing the *config_file* and *camera_type* arguments """ super().open(config_file=config_file, @@ -116,6 +122,9 @@ class JaiGO5000CPMCL(JaiGO5000CPMCL8Bits): This Camera relies on a custom-written C library that hasn't been tested in a long time. It might not be functional anymore. This Camera also requires proprietary drivers to be installed. + + .. versionadded:: 1.4.0 + .. versionchanged:: 2.0.0 renamed from Jai to JaiGO5000CPMCL """ def __init__(self) -> None: @@ -134,6 +143,8 @@ def open(self, Args: camera_type: The type of camera to drive, as a :obj:`str`. **kwargs: All the settings to set on the camera. + + .. versionadded:: 1.5.10 explicitly listing the *camera_type* argument """ super().open(camera_type=camera_type, **kwargs) diff --git a/src/crappy/camera/fake_camera.py b/src/crappy/camera/fake_camera.py index c3bc86cc..61dc6222 100644 --- a/src/crappy/camera/fake_camera.py +++ b/src/crappy/camera/fake_camera.py @@ -15,6 +15,9 @@ class FakeCamera(Camera): The generated images are just a gradient of grey levels, with a line moving as a function of time. It is possible to tune the dimension of the image, the frame rate and the speed of the moving line. + + .. versionadded:: 1.4.0 + .. versionchanged:: 2.0.0 renamed from Fake_camera to FakeCamera """ def __init__(self) -> None: @@ -44,6 +47,9 @@ def open(self, height: The height of the image to generate in pixels. speed: The evolution speed of the image, in pixels per second. fps: The maximum update frequency of the generated images. + + .. versionadded:: 2.0.0 + *width*, *height*, *speed* and *fps* arguments explicitly listed """ self.set_all(width=width, height=height, speed=speed, fps=fps) diff --git a/src/crappy/camera/file_reader.py b/src/crappy/camera/file_reader.py index dfd0be6b..21ab94db 100644 --- a/src/crappy/camera/file_reader.py +++ b/src/crappy/camera/file_reader.py @@ -35,6 +35,10 @@ class FileReader(Camera): be that the images cannot be read fast enough to match the original framerate, in which case the images are read as fast as possible and the delay keeps growing. + + .. versionadded:: 1.4.0 + .. versionchanged:: 1.5.10 renamed from Streamer to File_reader + .. versionchanged:: 2.0.0 renamed from File_reader to FileReader """ def __init__(self) -> None: @@ -68,6 +72,10 @@ def open(self, stop_at_end: If :obj:`True` (the default), stops the Crappy script once the available images are all exhausted. Otherwise, simply remains idle while waiting for the test to finish. + + .. versionchanged:: 1.5.10 renamed *path* argument to *reader_folder* + .. versionadded:: 1.5.10 *reader_backend* and *stop_at_end* arguments + .. deprecated:: 1.5.10 *pattern*, *start_delay* and *modifier* arguments """ # Selecting an available backend between first sitk and then cv2 diff --git a/src/crappy/camera/gstreamer_camera_basic.py b/src/crappy/camera/gstreamer_camera_basic.py index d3405790..64f19da7 100644 --- a/src/crappy/camera/gstreamer_camera_basic.py +++ b/src/crappy/camera/gstreamer_camera_basic.py @@ -42,6 +42,9 @@ class CameraGstreamer(Camera): Note: This Camera requires the module :mod:`PyGObject` to be installed, as well as GStreamer. + + .. versionadded:: 1.5.9 + .. versionchanged:: 2.0.0 renamed from Camera_gstreamer to CameraGstreamer """ def __init__(self) -> None: diff --git a/src/crappy/camera/gstreamer_camera_v4l2.py b/src/crappy/camera/gstreamer_camera_v4l2.py index 9c52b3d9..81e690bf 100644 --- a/src/crappy/camera/gstreamer_camera_v4l2.py +++ b/src/crappy/camera/gstreamer_camera_v4l2.py @@ -103,6 +103,9 @@ class CameraGstreamer(Camera): Note: This Camera requires the module :mod:`PyGObject` to be installed, as well as GStreamer. + + .. versionadded:: 1.5.9 + .. versionchanged:: 2.0.0 renamed from Camera_gstreamer to CameraGstreamer """ def __init__(self) -> None: diff --git a/src/crappy/camera/meta_camera/camera.py b/src/crappy/camera/meta_camera/camera.py index bf65627a..8210b476 100644 --- a/src/crappy/camera/meta_camera/camera.py +++ b/src/crappy/camera/meta_camera/camera.py @@ -19,6 +19,8 @@ class Camera(metaclass=MetaCamera): The Camera objects are helper classes used by the :class:`~crappy.blocks.Camera` Block to interface with cameras. + + .. versionadded:: 1.4.0 """ def __init__(self, *_, **__) -> None: @@ -31,6 +33,8 @@ def __init__(self, *_, **__) -> None: :meth:`add_choice_setting`, :meth:`add_trigger_setting`, or :meth:`add_software_roi` methods. Refer to the documentation of these methods for more information. + + .. versionchanged:: 2.0.0 now accepts *args* and *kwargs* """ self.settings: Dict[str, CameraSetting] = dict() @@ -55,6 +59,8 @@ def log(self, level: int, msg: str) -> None: Args: level: An :obj:`int` indicating the logging level of the message. msg: The message to log, as a :obj:`str`. + + .. versionadded:: 2.0.0 """ if self._logger is None: @@ -97,6 +103,8 @@ def open(self, **kwargs) -> None: end of the method. This is true even if no value to set was given in the kwargs. If :meth:`set_all` is not called, the settings won't actually be set on the camera. + + .. versionadded:: 1.5.10 """ self.set_all(**kwargs) @@ -123,7 +131,9 @@ def get_image(self) -> Optional[Tuple[Union[Dict[str, Any], float], the recorded images. If only a timestamp is provided, a metadata :obj:`dict` is built internally - but the user has no control over it + but the user has no control over it. + + .. versionadded:: 1.5.10 """ self.log(logging.WARNING, "The get_img method was called but is not " @@ -139,6 +149,8 @@ def close(self) -> None: This step is usually extremely important in order for the camera resources to be released. Otherwise, it might be impossible to re-open the camera from Crappy without resetting the hardware connection with it. + + .. versionadded:: 1.5.10 """ ... @@ -169,6 +181,8 @@ def add_bool_setting(self, setter: The method for setting the current value of the setting. If not given, the value to be set is simply stored. default: The default value to assign to the setting. + + .. versionadded:: 1.5.10 """ # Checking if the given name is valid @@ -218,6 +232,9 @@ def add_scale_setting(self, default: The default value to assign to the setting. If not given, will be the average of ``lowest`` and ``highest``. step: The step value for the variation of the setting values. + + .. versionadded:: 1.5.10 + .. versionchanged:: 2.0.0 add the *step* argument """ # Checking if the given name is valid @@ -260,6 +277,8 @@ def add_choice_setting(self, given, the value to be set is simply stored. default: The default value to assign to the setting. If not given, will be the fist item in ``choices``. + + .. versionadded:: 1.5.10 """ # Checking if the given name is valid @@ -311,6 +330,8 @@ def add_trigger_setting(self, given, the returned value is simply the last one that was set. setter: The method for setting the current value of the setting. If not given, the value to be set is simply stored. + + .. versionadded:: 2.0.0 """ if self.trigger_name in self.settings: @@ -358,6 +379,8 @@ def add_software_roi(self, width: int, height: int) -> None: Args: width: The width of the acquired images, in pixels. height: The height of the acquired images, in pixels. + + .. versionadded:: 2.0.0 """ # Checking that the software ROI setting does not already exist @@ -392,6 +415,8 @@ def reload_software_roi(self, width: int, height: int) -> None: Args: width: The width of the acquired images, in pixels. height: The height of the acquired images, in pixels. + + .. versionadded:: 2.0.0 """ if self._soft_roi_set: @@ -419,6 +444,8 @@ def apply_soft_roi(self, img: np.ndarray) -> Optional[np.ndarray]: Might return :obj:`None` in case there's no pixel left on the cropped image. Returns the original image if the software ROI settings were not defined using :meth:`add_software_roi`. + + .. versionadded:: 2.0.0 """ if self._soft_roi_set: diff --git a/src/crappy/camera/meta_camera/camera_setting/camera_bool_setting.py b/src/crappy/camera/meta_camera/camera_setting/camera_bool_setting.py index 0c080a50..3ff242cf 100644 --- a/src/crappy/camera/meta_camera/camera_setting/camera_bool_setting.py +++ b/src/crappy/camera/meta_camera/camera_setting/camera_bool_setting.py @@ -10,6 +10,10 @@ class CameraBoolSetting(CameraSetting): It is a child of :class:`~crappy.camera.meta_camera.camera_setting.CameraSetting`. + + .. versionadded:: 1.5.10 + .. versionchanged:: 2.0.0 + renamed from Camera_bool_setting to CameraBoolSetting """ def __init__(self, diff --git a/src/crappy/camera/meta_camera/camera_setting/camera_choice_setting.py b/src/crappy/camera/meta_camera/camera_setting/camera_choice_setting.py index be8abe12..7da9b675 100644 --- a/src/crappy/camera/meta_camera/camera_setting/camera_choice_setting.py +++ b/src/crappy/camera/meta_camera/camera_setting/camera_choice_setting.py @@ -13,6 +13,10 @@ class CameraChoiceSetting(CameraSetting): It is a child of :class:`~crappy.camera.meta_camera.camera_setting.CameraSetting`. + + .. versionadded:: 1.5.10 + .. versionchanged:: 2.0.0 + renamed from Camera_choice_setting to CameraChoiceSetting """ def __init__(self, @@ -50,6 +54,8 @@ def reload(self, cannot vary. It is thus not possible to propose more choices than those initially proposed. Reversely, if fewer new options ar proposed then some radio buttons won't be affected a value. + + .. versionadded:: 2.0.0 """ self.log(logging.DEBUG, f"Reloading the setting {self.name}") diff --git a/src/crappy/camera/meta_camera/camera_setting/camera_scale_setting.py b/src/crappy/camera/meta_camera/camera_setting/camera_scale_setting.py index 70ad2022..518fdc58 100644 --- a/src/crappy/camera/meta_camera/camera_setting/camera_scale_setting.py +++ b/src/crappy/camera/meta_camera/camera_setting/camera_scale_setting.py @@ -19,6 +19,10 @@ class CameraScaleSetting(CameraSetting): well as settings that can take :obj:`float` value. The type used is :obj:`int` is both of the given lowest or highest values are :obj:`int`, otherwise :obj:`float` is used. + + .. versionadded:: 1.5.10 + .. versionchanged:: 2.0.0 + renamed from Camera_scale_setting to CameraScaleSetting """ def __init__(self, @@ -39,6 +43,8 @@ def __init__(self, setter: The method for setting the current value of the setting. default: The default value to assign to the setting. step: The step value for the variation of the setting values. + + .. versionadded:: 2.0.0 add *step* argument """ self.lowest = lowest @@ -94,8 +100,11 @@ def reload(self, value: NbrType, default: Optional[NbrType] = None, step: Optional[NbrType] = None) -> None: - """Allows modifying the limits and the step of the scale bar - once it is already instantiated.""" + """Allows modifying the limits and the step of the scale bar once it is + already instantiated. + + .. versionadded:: 2.0.0 + """ self.log(logging.DEBUG, f"Reloading the setting {self.name}") @@ -121,7 +130,10 @@ def reload(self, def _check_value(self) -> None: """Checks if the step value is compatible with the limit values and - types of the scale settings.""" + types of the scale settings. + + .. versionadded:: 2.0.0 + """ if self.step is not None: if self.type == int and isinstance(self.step, float): diff --git a/src/crappy/camera/meta_camera/camera_setting/camera_setting.py b/src/crappy/camera/meta_camera/camera_setting/camera_setting.py index 9fb58da0..a909be96 100644 --- a/src/crappy/camera/meta_camera/camera_setting/camera_setting.py +++ b/src/crappy/camera/meta_camera/camera_setting/camera_setting.py @@ -18,6 +18,9 @@ class CameraSetting: :class:`~crappy.camera.meta_camera.camera_setting.CameraBoolSetting`, :class:`~crappy.camera.meta_camera.camera_setting.CameraChoiceSetting`, and :class:`~crappy.camera.meta_camera.camera_setting.CameraScaleSetting`. + + .. versionadded:: 1.4.0 + .. versionchanged:: 2.0.0 renamed from Camera_setting to CameraSetting """ def __init__(self, @@ -57,6 +60,8 @@ def log(self, level: int, msg: str) -> None: Args: level: An :obj:`int` indicating the logging level of the message. msg: The message to log, as a :obj:`str`. + + .. versionadded:: 2.0.0 """ if self._logger is None: @@ -101,6 +106,8 @@ def reload(self, *_, **__) -> None: GUI. Mostly helpful for adjusting the ranges of sliders. + + .. versionadded:: 2.0.0 """ ... diff --git a/src/crappy/camera/meta_camera/meta_camera.py b/src/crappy/camera/meta_camera/meta_camera.py index e67dec64..58660208 100644 --- a/src/crappy/camera/meta_camera/meta_camera.py +++ b/src/crappy/camera/meta_camera/meta_camera.py @@ -6,7 +6,12 @@ class MetaCamera(type): """Metaclass ensuring that two cameras don't have the same name, and that all cameras define the required methods. Also keeps track of all the Camera - classes, including the custom user-defined ones.""" + classes, including the custom user-defined ones. + + .. versionadded:: 1.4.0 + .. versionchanged:: 1.5.10 + not checking anymore for mandatory methods in :meth:`__init__` + """ classes = {} diff --git a/src/crappy/camera/raspberry_pi_camera.py b/src/crappy/camera/raspberry_pi_camera.py index abfb5d5f..748a4a9c 100644 --- a/src/crappy/camera/raspberry_pi_camera.py +++ b/src/crappy/camera/raspberry_pi_camera.py @@ -38,6 +38,9 @@ class RaspberryPiCamera(Camera): Warning: Only works on Raspberry Pi, with the picamera API. On the latest OS release "Bullseye", it has to be specifically activated in the configuration menu. + + .. versionadded:: 1.4.0 + .. versionchanged:: 2.0.0 renamed from Picamera to RaspberryPiCamera """ def __init__(self) -> None: diff --git a/src/crappy/camera/seek_thermal_pro.py b/src/crappy/camera/seek_thermal_pro.py index 1a40d05b..20926e93 100644 --- a/src/crappy/camera/seek_thermal_pro.py +++ b/src/crappy/camera/seek_thermal_pro.py @@ -63,6 +63,9 @@ class SeekThermalPro(Camera): MODE=\\"0777\\\"" | sudo tee seek_thermal.rules > /dev/null 2>&1 in a shell opened in ``/etc/udev/rules.d``. + + .. versionadded:: 1.4.0 + .. versionchanged:: 2.0.0 renamed from Seek_thermal_pro to SeekThermalPro """ def __init__(self) -> None: diff --git a/src/crappy/camera/ximea_xiapi.py b/src/crappy/camera/ximea_xiapi.py index e6a0fd87..d9d031f0 100644 --- a/src/crappy/camera/ximea_xiapi.py +++ b/src/crappy/camera/ximea_xiapi.py @@ -29,6 +29,9 @@ class XiAPI(Camera): Note: Both the Python module and the camera drivers have to be installed from Ximea's website in order for this class to run. + + .. versionadded:: 1.4.0 + .. versionchanged:: 2.0.0 renamed from Xiapi to XiAPI """ def __init__(self) -> None: @@ -58,6 +61,8 @@ def open(self, serial_number: Optional[str] = None, **kwargs) -> None: several cameras are available, one of them will be opened randomly. **kwargs: Values of the settings to set before opening the camera. Mostly useful if the configuration window is not used. + + .. versionchanged:: 2.0.0 renamed *sn* argument to *serial_number* """ if serial_number is not None: diff --git a/src/crappy/inout/ads1115.py b/src/crappy/inout/ads1115.py index f36c0c44..65fe45a5 100644 --- a/src/crappy/inout/ads1115.py +++ b/src/crappy/inout/ads1115.py @@ -87,6 +87,9 @@ class ADS1115(InOut): Various settings can be adjusted, like the sample rate, the input mode or the voltage range. + + .. versionadded:: 1.4.0 + .. versionchanged:: 2.0.0 renamed from Ads1115 to ADS1115 """ def __init__(self, @@ -156,6 +159,9 @@ def __init__(self, AINx voltages should not be higher than `VDD+0.3V` nor lower than `GND-0.3V`. Setting high ``v_range`` values does not allow measuring voltages higher than `VDD` !! + + .. versionadded:: 1.5.8 *dry_pin* argument + .. versionremoved:: 2.0.0 *ft232h_ser_num* argument """ self._bus = None diff --git a/src/crappy/inout/agilent_34420A.py b/src/crappy/inout/agilent_34420A.py index 03eccd72..9051b56d 100644 --- a/src/crappy/inout/agilent_34420A.py +++ b/src/crappy/inout/agilent_34420A.py @@ -21,6 +21,8 @@ class Agilent34420a(InOut): May also work on similar devices from the same manufacturer, although that was not tested. + + .. versionadded:: 1.4.0 """ def __init__(self, diff --git a/src/crappy/inout/comedi.py b/src/crappy/inout/comedi.py index 06df7d82..5cf7e155 100644 --- a/src/crappy/inout/comedi.py +++ b/src/crappy/inout/comedi.py @@ -38,6 +38,8 @@ class Comedi(InOut): Note: This class requires the `libcomedi` library to be installed on the computer. + + .. versionadded:: 1.4.0 """ def __init__(self, @@ -116,6 +118,14 @@ def __init__(self, the same length, and same for the output channels. If that's not the case, all the given iterables are treated as if they had the same length as the shortest given one. + + .. versionchanged:: 1.5.10 + now explicitly listing the *device*, *subdevice*, *channels*, + *range_num*, *gain*, *offset*, *make_zero*, *out_subdevice*, + *out_channels*, *out_range_num*, *out_gain* and *out_offset* arguments + .. versionchanged:: 2.0.0 renamed *subdevice* argument to *sub_device* + .. versionchanged:: 2.0.0 + renamed *out_subdevice* argument to *out_sub_device* """ self._device = None diff --git a/src/crappy/inout/daqmx.py b/src/crappy/inout/daqmx.py index 057f01d1..55e2a3fe 100644 --- a/src/crappy/inout/daqmx.py +++ b/src/crappy/inout/daqmx.py @@ -36,6 +36,9 @@ class DAQmx(InOut): Note: This class requires the NIDAQmx C driver to be installed, as well as the :mod:`PyDAQmx` module. + + .. versionadded:: 1.4.0 + .. versionchanged:: 2.0.0 renamed from Daqmx to DAQmx """ def __init__(self, @@ -114,6 +117,10 @@ def __init__(self, the same length, and same for the output channels. If that's not the case, all the given iterables are treated as if they had the same length as the shortest given one. + + .. versionchanged:: 1.5.10 renamed *range* argument to *ranges* + .. versionchanged:: 1.5.10 renamed *out_range* argument to *out_ranges* + .. versionremoved:: 1.5.10 *nperscan* argument """ self._handle = None diff --git a/src/crappy/inout/fake_inout.py b/src/crappy/inout/fake_inout.py index 7393380b..5714c31b 100644 --- a/src/crappy/inout/fake_inout.py +++ b/src/crappy/inout/fake_inout.py @@ -19,6 +19,9 @@ class FakeInOut(InOut): It can read and/or modify (to a certain extent) the memory usage of the computer. + + .. versionadded:: 1.5.5 + .. versionchanged:: 2.0.0 renamed from Fake_inout to FakeInOut """ def __init__(self) -> None: @@ -64,12 +67,18 @@ def get_data(self) -> List[float]: return [time(), virtual_memory().percent] def start_stream(self) -> None: - """Defining this method to avoid getting warnings in the logs.""" + """Defining this method to avoid getting warnings in the logs. + + .. versionadded:: 2.0.0 + """ ... def stop_stream(self) -> None: - """Defining this method to avoid getting warnings in the logs.""" + """Defining this method to avoid getting warnings in the logs. + + .. versionadded:: 2.0.0 + """ ... @@ -78,6 +87,8 @@ def get_stream(self) -> Tuple[np.ndarray, np.ndarray]: 10 values at once in the streamer format. It is just a demo for showcasing the use of the streamer mode. + + .. versionadded:: 2.0.0 """ values = np.array([self.get_data() for _ in range(10)]) diff --git a/src/crappy/inout/ft232h/ads1115.py b/src/crappy/inout/ft232h/ads1115.py index c44e9895..5e417a9b 100644 --- a/src/crappy/inout/ft232h/ads1115.py +++ b/src/crappy/inout/ft232h/ads1115.py @@ -52,6 +52,8 @@ class ADS1115FT232H(InOut): Various settings can be adjusted, like the sample rate, the input mode or the voltage range. + + .. versionadded:: 2.0.0 """ ft232h = True diff --git a/src/crappy/inout/ft232h/gpio_switch.py b/src/crappy/inout/ft232h/gpio_switch.py index 26212324..bbfb1b95 100644 --- a/src/crappy/inout/ft232h/gpio_switch.py +++ b/src/crappy/inout/ft232h/gpio_switch.py @@ -15,6 +15,8 @@ class is specific for use with an :class:`~crappy.tool.ft232h.FT232H` USB to When the command value is `1` the GPIO is turned high, when the command is `0` it is turned low. Any value other than `0` and `1` raises an error. + + .. versionadded:: 2.0.0 """ ft232h = True diff --git a/src/crappy/inout/ft232h/mcp9600.py b/src/crappy/inout/ft232h/mcp9600.py index be9121c3..dfc9b597 100644 --- a/src/crappy/inout/ft232h/mcp9600.py +++ b/src/crappy/inout/ft232h/mcp9600.py @@ -72,6 +72,8 @@ class MCP9600FT232H(InOut): operating mode that returns Volts. Several parameters can be tuned, like the thermocouple type, the reading resolution or the filter coefficient. Note that the MCP9600 can only achieve a data rate of a few Hz. + + .. versionadded:: 2.0.0 """ ft232h = True diff --git a/src/crappy/inout/ft232h/mprls.py b/src/crappy/inout/ft232h/mprls.py index 9cf54dc0..b9dbbdd7 100644 --- a/src/crappy/inout/ft232h/mprls.py +++ b/src/crappy/inout/ft232h/mprls.py @@ -21,6 +21,8 @@ class MPRLSFT232H(InOut): converter. It communicates over I2C with the sensor. + + .. versionadded:: 2.0.0 """ ft232h = True diff --git a/src/crappy/inout/ft232h/nau7802.py b/src/crappy/inout/ft232h/nau7802.py index 00d256bc..145b3f15 100644 --- a/src/crappy/inout/ft232h/nau7802.py +++ b/src/crappy/inout/ft232h/nau7802.py @@ -105,6 +105,8 @@ class NAU7802FT232H(InOut): that can read up to 320 samples per second. It communicates over the I2C protocol. The returned value of the InOut is in Volts by default, but can be converted to Newtons using the ``gain`` and ``offset`` arguments. + + .. versionadded:: 2.0.0 """ ft232h = True diff --git a/src/crappy/inout/ft232h/waveshare_ad_da.py b/src/crappy/inout/ft232h/waveshare_ad_da.py index 1680d3bf..75b678dc 100644 --- a/src/crappy/inout/ft232h/waveshare_ad_da.py +++ b/src/crappy/inout/ft232h/waveshare_ad_da.py @@ -81,6 +81,8 @@ class is specific for use with an :class:`~crappy.tool.ft232h.FT232H` USB to read values from the 8-channels ADC and/or to set the 2-channels DAC. The hat can acquire up to 30000 samples per second, although this data rate is impossible to achieve using Crappy. + + .. versionadded:: 2.0.0 """ ft232h = True diff --git a/src/crappy/inout/gpio_pwm.py b/src/crappy/inout/gpio_pwm.py index dc576866..996a600f 100644 --- a/src/crappy/inout/gpio_pwm.py +++ b/src/crappy/inout/gpio_pwm.py @@ -21,6 +21,9 @@ class GPIOPWM(InOut): Warning: Only works on Raspberry Pi ! + + .. versionadded:: 1.4.0 + .. versionchanged:: 2.0.0 renamed from Gpio_pwm to GPIOPWM """ def __init__(self, diff --git a/src/crappy/inout/gpio_switch.py b/src/crappy/inout/gpio_switch.py index cb9b0160..4502adf2 100644 --- a/src/crappy/inout/gpio_switch.py +++ b/src/crappy/inout/gpio_switch.py @@ -30,6 +30,9 @@ class GPIOSwitch(InOut): When the command value is `1` the GPIO is turned high, when the command is `0` it is turned low. Any value other than `0` and `1` raises an error. + + .. versionadded:: 1.4.0 + .. versionchanged:: 2.0.0 renamed from Gpio_switch to GPIOSwitch """ def __init__(self, @@ -51,6 +54,9 @@ def __init__(self, The `'Pi4'` backend only works on the Raspberry Pis. The `'blinka'` backend requires installing :mod:`Adafruit-Blinka`, but this module is compatible with and maintained on a wide variety of boards. + + .. versionadded:: 1.5.10 *backend* and *ft232h_ser_num* arguments + .. versionremoved:: 2.0.0 *ft232h_ser_num* argument """ self._pin_out = None diff --git a/src/crappy/inout/kollmorgen_akd_pdmm.py b/src/crappy/inout/kollmorgen_akd_pdmm.py index 2a5acbe6..0de787d9 100644 --- a/src/crappy/inout/kollmorgen_akd_pdmm.py +++ b/src/crappy/inout/kollmorgen_akd_pdmm.py @@ -39,6 +39,9 @@ class KollmorgenAKDPDMM(InOut): It can either drive it in speed or in position. Multiple axes can be driven. The values of the current speeds or positions can also be retrieved. + + .. versionadded:: 1.4.0 + .. versionchanged:: 2.0.0 renamed from Koll to KollmorgenAKDPDMM """ def __init__(self, @@ -55,6 +58,10 @@ def __init__(self, host: The IP address of the variator, given as a :obj:`str`. port: The network port over which to communicate with the variator, as an :obj:`int`. + + .. versionremoved:: 1.5.10 *speed*, *acc*, *decc* and *labels* arguments + .. versionchanged:: 1.5.10 renamed *axis* argument to *axes* + .. versionchanged:: 1.5.10 renamed *data* argument to *mode* """ self._variator = None @@ -113,6 +120,8 @@ def set_cmd(self, *cmd: float) -> None: If more commands than motors are given, the extra commands are ignored. If there are more motors than commands, only part of the motors will be set. + + .. versionadded:: 1.5.10 """ for axis, val in zip(self._axes, cmd): diff --git a/src/crappy/inout/labjack_t7.py b/src/crappy/inout/labjack_t7.py index d795971d..8a93ebd0 100644 --- a/src/crappy/inout/labjack_t7.py +++ b/src/crappy/inout/labjack_t7.py @@ -68,6 +68,9 @@ class LabjackT7(InOut): This class is not capable of streaming. For higher frequency, refer to the :class:`~crappy.inout.T7Streamer` class. + + .. versionadded:: 1.4.0 + .. versionchanged:: 2.0.0 renamed from Labjack_t7 to LabjackT7 """ def __init__(self, @@ -343,6 +346,8 @@ def make_zero(self, delay: float) -> None: Args: delay: The delay during which the data should be acquired for determining the offset. + + .. versionadded:: 1.5.10 """ # No need to acquire data if no channel should be zeroed diff --git a/src/crappy/inout/labjack_t7_streamer.py b/src/crappy/inout/labjack_t7_streamer.py index 25678272..c60cf513 100644 --- a/src/crappy/inout/labjack_t7_streamer.py +++ b/src/crappy/inout/labjack_t7_streamer.py @@ -65,6 +65,9 @@ class T7Streamer(InOut): The ``streamer`` argument of the IOBlock controlling this InOut must be set to :obj:`True` to enable streaming in this class. Otherwise, only single point acquisition can be performed. + + .. versionadded:: 1.4.0 + .. versionchanged:: 2.0.0 renamed from T7_streamer to T7Streamer """ def __init__(self, @@ -215,6 +218,8 @@ def make_zero(self, delay: float) -> None: It simply performs the regular zeroing, and resets the compensation for the channels that shouldn't be zeroed. + + .. versionadded:: 1.5.10 """ # No need to acquire data if no channel should be zeroed diff --git a/src/crappy/inout/labjack_ue9.py b/src/crappy/inout/labjack_ue9.py index d2ae1841..9e52070c 100644 --- a/src/crappy/inout/labjack_ue9.py +++ b/src/crappy/inout/labjack_ue9.py @@ -33,6 +33,9 @@ class LabjackUE9(InOut): UE9. The UE9 model has been discontinued, and replaced by the T7 model (see :class:`~crappy.inout.LabjackT7`). It is thus likely that this class won't be further improved in the future. + + .. versionadded:: 1.4.0 + .. versionchanged:: 2.0.0 renamed from Labjack_ue9 to LabjackUE9 """ def __init__(self, @@ -110,6 +113,8 @@ def make_zero(self, delay: float) -> None: It simply performs the regular zeroing, and resets the compensation to zero for the channels that shouldn't be zeroed. + + .. versionadded:: 1.5.10 """ # No need to acquire data if no channel should be zeroed diff --git a/src/crappy/inout/mcp9600.py b/src/crappy/inout/mcp9600.py index f59a3773..d9d3de5a 100644 --- a/src/crappy/inout/mcp9600.py +++ b/src/crappy/inout/mcp9600.py @@ -97,6 +97,9 @@ class MCP9600(InOut): operating mode that returns Volts. Several parameters can be tuned, like the thermocouple type, the reading resolution or the filter coefficient. Note that the MCP9600 can only achieve a data rate of a few Hz. + + .. versionadded:: 1.4.0 + .. versionchanged:: 2.0.0 renamed from Mcp9600 to MCP9600 """ def __init__(self, @@ -164,6 +167,7 @@ def __init__(self, 'Cold Junction Temperature', 'Raw Data ADC' + .. versionremoved:: 2.0.0 *ft232h_ser_num* argument """ self._bus = None diff --git a/src/crappy/inout/meta_inout/inout.py b/src/crappy/inout/meta_inout/inout.py index e56731b1..7f5ae391 100644 --- a/src/crappy/inout/meta_inout/inout.py +++ b/src/crappy/inout/meta_inout/inout.py @@ -16,12 +16,17 @@ class InOut(metaclass=MetaIO): The InOut objects are helper classes used by the :class:`~crappy.blocks.IOBlock` to interface with hardware. + + .. versionadded:: 1.4.0 """ ft232h: bool = False def __init__(self, *_, **__) -> None: - """Sets the attributes.""" + """Sets the attributes. + + .. versionchanged:: 2.0.0 now accepts args and kwargs + """ self._compensations: List[float] = list() self._compensations_dict: Dict[str, float] = dict() @@ -35,6 +40,8 @@ def log(self, level: int, msg: str) -> None: Args: level: An :obj:`int` indicating the logging level of the message. msg: The message to log, as a :obj:`str`. + + .. versionadded:: 2.0.0 """ if self._logger is None: @@ -54,6 +61,8 @@ def open(self) -> None: Blocks. It is fine for this method not to perform anything. + + .. versionadded:: 1.5.10 """ ... @@ -93,6 +102,8 @@ def get_data(self) -> Optional[Union[Iterable, Dict[str, Any]]]: It is alright for this method to return :obj:`None` if there's no data to acquire. + + .. versionadded:: 1.5.10 """ self.log(logging.WARNING, @@ -116,6 +127,8 @@ def set_cmd(self, *cmd) -> None: If ``{'t(s)': 20.5, 'value_1': 1.5, 'value_2': 3.6, 'value_3': 'OK'}`` is received from upstream Blocks, and ``cmd_labels=('value_3', 'value_1')``, then the call will be ``set_cmd('OK', 1.5)``. + + .. versionadded:: 1.5.10 """ self.log(logging.WARNING, @@ -125,7 +138,10 @@ def set_cmd(self, *cmd) -> None: return def start_stream(self) -> None: - """This method should start the acquisition of the stream.""" + """This method should start the acquisition of the stream. + + .. versionadded:: 1.5.10 + """ self.log(logging.WARNING, "The start_stream method was called but is not " "defined !") @@ -161,6 +177,8 @@ def get_stream(self) -> Optional[Union[Iterable[np.ndarray], It is technically possible to return more than two arrays, or even objects that are not arrays, but it is not the intended use for this method and might interfere with some functionalities of Crappy. + + .. versionadded:: 1.5.10 """ self.log(logging.WARNING, "The get_stream method was called but is not " @@ -169,7 +187,10 @@ def get_stream(self) -> Optional[Union[Iterable[np.ndarray], return def stop_stream(self) -> None: - """This method should stop the acquisition of the stream.""" + """This method should stop the acquisition of the stream. + + .. versionadded:: 1.5.10 + """ self.log(logging.WARNING, "The stop_stream method was called but is not " "defined !") @@ -184,6 +205,8 @@ def close(self) -> None: path, or because an exception was raised in any of the Blocks). It is fine for this method not to perform anything. + + .. versionadded:: 1.5.10 """ ... @@ -199,6 +222,8 @@ def make_zero(self, delay: float) -> None: This method uses :meth:`get_data` for acquiring the values, so it doesn't work for pure streams. It also doesn't work if the acquired values do not support arithmetic operations (like :obj:`str`). + + .. versionadded:: 1.5.10 """ buf = [] @@ -260,7 +285,10 @@ def make_zero(self, delay: float) -> None: def return_data(self) -> Optional[Union[List[Any], Dict[str, Any]]]: """Returns the data from :meth:`get_data`, corrected by an offset if the ``make_zero_delay`` argument of the :class:`~crappy.blocks.IOBlock` is - set.""" + set. + + .. versionadded:: 1.5.10 + """ data = self.get_data() @@ -313,7 +341,10 @@ def return_stream(self) -> Optional[Union[List[np.ndarray], Dict[str, np.ndarray]]]: """Returns the data from :meth:`get_stream`, corrected by an offset if the ``make_zero_delay`` argument of the :class:`~crappy.blocks.IOBlock` is - set.""" + set. + + .. versionadded:: 1.5.10 + """ data = self.get_stream() diff --git a/src/crappy/inout/meta_inout/meta_inout.py b/src/crappy/inout/meta_inout/meta_inout.py index a2bd8424..69960aa8 100644 --- a/src/crappy/inout/meta_inout/meta_inout.py +++ b/src/crappy/inout/meta_inout/meta_inout.py @@ -6,7 +6,12 @@ class MetaIO(type): """Metaclass ensuring that two InOuts don't have the same name, and that all InOuts define the required methods. Also keeps track of all the InOut - classes, including the custom user-defined ones.""" + classes, including the custom user-defined ones. + + .. versionadded:: 1.4.0 + .. versionchanged:: 1.5.10 + not checking anymore for mandatory methods in :meth:`__init__` + """ classes = dict() diff --git a/src/crappy/inout/mprls.py b/src/crappy/inout/mprls.py index 323fc0b3..0db70071 100644 --- a/src/crappy/inout/mprls.py +++ b/src/crappy/inout/mprls.py @@ -43,6 +43,9 @@ class MPRLS(InOut): """This class can read values from an MPRLS pressure sensor. It communicates over I2C with the sensor. + + .. versionadded:: 1.4.0 + .. versionchanged:: 2.0.0 renamed from Mprls to MPRLS """ def __init__(self, @@ -76,6 +79,11 @@ def __init__(self, another address. i2c_port: The I2C port over which the MPRLS should communicate. On most Raspberry Pi models the default I2C port is `1`. + + .. versionadded:: 1.5.8 + *backend*, *eoc_pin*, *device_address*, *i2c_port* and *ft232h_ser_num* + arguments + .. versionremoved:: 2.0.0 *ft232h_ser_num* argument """ self._bus = None diff --git a/src/crappy/inout/nau7802.py b/src/crappy/inout/nau7802.py index e16af9dc..da13d174 100644 --- a/src/crappy/inout/nau7802.py +++ b/src/crappy/inout/nau7802.py @@ -111,6 +111,9 @@ class NAU7802(InOut): that can read up to 320 samples per second. It communicates over the I2C protocol. The returned value of the InOut is in Volts by default, but can be converted to Newtons using the ``gain`` and ``offset`` arguments. + + .. versionadded:: 1.4.0 + .. versionchanged:: 2.0.0 renamed from Nau7802 to NAU7802 """ def __init__(self, @@ -151,6 +154,9 @@ def __init__(self, :math:`output = gain * tension + offset`. offset: Allows to tune the output value according to the formula : :math:`output = gain * tension + offset`. + + .. versionadded:: 1.5.8 *int_pin* argument + .. versionremoved:: 2.0.0 *backend* and *ft232h_ser_num* arguments """ self._bus = None diff --git a/src/crappy/inout/ni_daqmx.py b/src/crappy/inout/ni_daqmx.py index 3081b74b..34677022 100755 --- a/src/crappy/inout/ni_daqmx.py +++ b/src/crappy/inout/ni_daqmx.py @@ -69,6 +69,9 @@ class NIDAQmx(InOut): of data from analog channels, and set the voltage of analog and digital output channels. For analog input channels, several types of acquisition can be performed, like voltage, resistance, current, etc. + + .. versionadded:: 1.4.0 + .. versionchanged:: 2.0.0 renamed from Nidaqmx to NIDAQmx """ def __init__(self, @@ -125,6 +128,9 @@ def __init__(self, internally. Also note that for the analog output channels and the analog input channels of type `'voltage'`, the `'min_val'` and `'max_val'` arguments are internally set by default to `0` and `5`. + + .. versionchanged:: 1.5.10 renamed *samplerate* argument to *sample_rate* + .. versionchanged:: 1.5.10 renamed *nsamples* argument to *n_samples* """ super().__init__() diff --git a/src/crappy/inout/opsens_handysens.py b/src/crappy/inout/opsens_handysens.py index 807936dd..ab93489e 100644 --- a/src/crappy/inout/opsens_handysens.py +++ b/src/crappy/inout/opsens_handysens.py @@ -19,6 +19,9 @@ class HandySens(InOut): It can read data from various fiber optics sensors like temperature, pressure, position or strain. + + .. versionadded:: 1.4.0 + .. versionchanged:: 2.0.0 renamed from Opsens to HandySens """ def __init__(self, diff --git a/src/crappy/inout/pijuice_hat.py b/src/crappy/inout/pijuice_hat.py index 504fd376..aaa2fa26 100644 --- a/src/crappy/inout/pijuice_hat.py +++ b/src/crappy/inout/pijuice_hat.py @@ -49,6 +49,9 @@ class PiJuice(InOut): Warning: Only available on Raspberry Pi ! + + .. versionadded:: 1.4.0 + .. versionchanged:: 2.0.0 renamed from Pijuice to PiJuice """ def __init__(self, @@ -67,6 +70,8 @@ def __init__(self, `'pijuice'` backend is based on the :mod:`pijuice` module. i2c_port: The I2C port over which the PiJuice should communicate. address: The I2C address of the piJuice. The default address is `0x14`. + + .. versionadded:: 1.5.10 *backend* argument """ self._bus = None diff --git a/src/crappy/inout/sim868.py b/src/crappy/inout/sim868.py index 9d3bb8fd..6b1db879 100644 --- a/src/crappy/inout/sim868.py +++ b/src/crappy/inout/sim868.py @@ -23,6 +23,9 @@ class Sim868(InOut): Important: This InOut should be associated with a :class:`~crappy.modifier.Modifier` to manage the messages to send. + + .. versionadded:: 1.4.0 + .. versionchanged:: 2.0.0 renamed from Gsm to Sim868 """ def __init__(self, @@ -45,6 +48,8 @@ def __init__(self, pin_code: Optionally, a pin code to use for activating the SIM card. registration_timeout: The maximum number of seconds to allow for the Sim868 to register to a network once the SIM card has the ready status. + + .. versionadded:: 2.0.0 *pin_code* and *registration_timeout* arguments """ self._ser = None diff --git a/src/crappy/inout/spectrum_m2i4711.py b/src/crappy/inout/spectrum_m2i4711.py index 6b2844e7..d2f7fde2 100644 --- a/src/crappy/inout/spectrum_m2i4711.py +++ b/src/crappy/inout/spectrum_m2i4711.py @@ -19,6 +19,9 @@ class SpectrumM2I4711(InOut): This class can only acquire data by streaming, it cannot acquire single data points. + + .. versionadded:: 1.4.0 + .. versionchanged:: 2.0.0 renamed from Spectrum to SpectrumM2I4711 """ def __init__(self, @@ -46,6 +49,9 @@ def __init__(self, the data from the card, in bytes. The default is 67MB. notify_size: The size of each chunk of data to copy from the card, in bytes. The default is 65kB. + + .. versionchanged:: 1.5.10 renamed *samplerate* argument to *sample_rate* + .. versionremoved:: 1.5.10 *split_chan* argument """ self._spectrum = None diff --git a/src/crappy/inout/waveshare_ad_da.py b/src/crappy/inout/waveshare_ad_da.py index 57b6cc1b..0becc2f4 100644 --- a/src/crappy/inout/waveshare_ad_da.py +++ b/src/crappy/inout/waveshare_ad_da.py @@ -96,6 +96,9 @@ class WaveshareADDA(InOut): Important: This class is specifically meant to be used on a Raspberry Pi. + + .. versionadded:: 1.4.0 + .. versionchanged:: 2.0.0 renamed from Waveshare_ad_da to WaveshareADDA """ def __init__(self, diff --git a/src/crappy/inout/waveshare_high_precision.py b/src/crappy/inout/waveshare_high_precision.py index ebd697db..bb3daa08 100644 --- a/src/crappy/inout/waveshare_high_precision.py +++ b/src/crappy/inout/waveshare_high_precision.py @@ -113,6 +113,10 @@ class WaveshareHighPrecision(InOut): The Waveshare HAT is originally meant to be used with a Raspberry Pi, but it can be used with any device supporting SPI as long as the wiring is correct and the 3.3 and 5V power are supplied. + + .. versionadded:: 1.5.10 + .. versionchanged:: 2.0.0 + renamed from Waveshare_high_precision to WaveshareHighPrecision """ def __init__(self, diff --git a/src/crappy/lamcube/biaxe.py b/src/crappy/lamcube/biaxe.py index 9464171b..2925f1c1 100644 --- a/src/crappy/lamcube/biaxe.py +++ b/src/crappy/lamcube/biaxe.py @@ -16,6 +16,8 @@ class Biaxe(Actuator): It is used at the LaMcube for driving a bi-axial tensile test machine, hence its name. The :class:`~crappy.actuator.ServoStar300` Actuator can drive the same hardware, but only in position. + + .. versionadded:: 1.4.0 """ def __init__(self, @@ -64,7 +66,10 @@ def set_speed(self, speed: float) -> None: self._speed = speed def stop(self) -> None: - """Sets the speed of the motor to `0`""" + """Sets the speed of the motor to `0`. + + .. versionadded:: 2.0.0 + """ if self._ser is not None: self.set_speed(0) diff --git a/src/crappy/lamcube/bispectral.py b/src/crappy/lamcube/bispectral.py index 1678dbf4..7f453734 100644 --- a/src/crappy/lamcube/bispectral.py +++ b/src/crappy/lamcube/bispectral.py @@ -83,6 +83,9 @@ class BiSpectral(BaslerIronmanCameraLink): This Camera relies on a custom-written C library that hasn't been tested in a long time. It might not be functional anymore. This Camera also requires proprietary drivers to be installed. + + .. versionadded:: 1.4.0 + .. versionchanged:: 2.0.0 renamed from Bispectral to BiSpectral """ def __init__(self) -> None: @@ -102,7 +105,10 @@ def __init__(self) -> None: def open(self, camera_type: str = 'SingleAreaGray2DShading', **kwargs) -> None: - """Opens the Camera and sends initialization commands.""" + """Opens the Camera and sends initialization commands. + + .. versionadded:: 1.5.10 explicitly listing *camera_type* argument + """ super().open(camera_type=camera_type, **kwargs) diff --git a/src/crappy/links/link.py b/src/crappy/links/link.py index 65367912..f90555cf 100644 --- a/src/crappy/links/link.py +++ b/src/crappy/links/link.py @@ -28,6 +28,8 @@ class Link: modify the transferred value. The Modifiers should be callables taking a :obj:`dict` as argument and returning a :obj:`dict`. They can be functions, or preferably children of :class:`~crappy.modifier.Modifier`. + + .. versionadded:: 1.4.0 """ _count = 0 @@ -48,6 +50,10 @@ def __init__(self, name: Name of the Link, to differentiate it from the others when debugging. If no specific name is given, the Links are numbered in the order in which they are instantiated in the script. + + .. versionchanged:: 1.5.9 renamed *condition* argument to *conditions* + .. versionchanged:: 1.5.9 renamed *modifier* argument to *modifiers* + .. versionremoved:: 2.0.0 *conditions*, *timeout* and *action* arguments """ if modifiers is None: @@ -89,6 +95,8 @@ def log(self, log_level: int, msg: str) -> None: Args: log_level: An :obj:`int` indicating the logging level of the message. msg: The message to log, as a :obj:`str`. + + .. versionadded:: 2.0.0 """ if self._logger is None: @@ -98,7 +106,10 @@ def log(self, log_level: int, msg: str) -> None: self._logger.log(log_level, msg) def poll(self) -> bool: - """Returns :obj:`True` if there's data available for reading.""" + """Returns :obj:`True` if there's data available for reading. + + .. versionadded:: 2.0.0 + """ return self._in.poll() @@ -145,6 +156,8 @@ def recv(self) -> Dict[str, Any]: Returns: A :obj:`dict` whose keys are the labels being sent, and for each key a single value (usually a :obj:`float` or a :obj:`str`). + + .. versionremoved:: 2.0.0 *blocking* argument """ if self._in.poll(): @@ -161,6 +174,8 @@ def recv_last(self) -> Dict[str, Any]: Returns: A :obj:`dict` whose keys are the labels being sent, and for each key a single value (usually a :obj:`float` or a :obj:`str`). + + .. versionremoved:: 2.0.0 *blocking* argument """ data = dict() @@ -177,6 +192,10 @@ def recv_chunk(self) -> Dict[str, List[Any]]: A :obj:`dict` whose keys are the labels being sent, and for each key a :obj:`list` of the received values. The first item in the list is the oldest one available in the Link, the last item is the newest available. + + .. versionremoved:: 1.5.9 *length* argument + .. versionadded:: 1.5.9 *blocking* argument + .. versionremoved:: 2.0.0 *blocking* argument """ ret = defaultdict(list) @@ -214,6 +233,12 @@ def link(in_block, name: Name of the Link, to differentiate it from the others when debugging. If no specific name is given, the Links are numbered in the order in which they are instantiated in the script. + + .. versionadded:: 1.4.0 + .. versionchanged:: 1.5.9 + explicitly listing the *condition*, *modifier*, *timeout*, *action* and + *name* arguments + .. versionremoved:: 2.0.0 *condition*, *timeout* and *action* arguments """ # Forcing the modifiers into lists diff --git a/src/crappy/modifier/demux.py b/src/crappy/modifier/demux.py index 6ffa9598..51e734bd 100644 --- a/src/crappy/modifier/demux.py +++ b/src/crappy/modifier/demux.py @@ -26,6 +26,8 @@ class Demux(Modifier): information is lost ! This Modifier is intended to format the stream data for low-frequency plotting, or low-frequency decision-making. To save all the stream data, use the :class:`~crappy.blocks.HDFRecorder` Block. + + .. versionadded:: 1.4.0 """ def __init__(self, @@ -49,6 +51,8 @@ def __init__(self, time_label: The label carrying the time information. transpose: If :obj:`True`, each label corresponds to a row in the stream. Otherwise, a label corresponds to a column in the stream. + + .. versionchanged:: 1.5.10 renamed *stream* argument to *stream_label* """ super().__init__() @@ -64,7 +68,12 @@ def __init__(self, def __call__(self, data: Dict[str, np.ndarray]) -> Dict[str, Any]: """Retrieves for each label its value in the stream, also gets the - corresponding timestamp, and returns them.""" + corresponding timestamp, and returns them. + + .. versionchanged:: 1.5.10 + merge evaluate_mean and evaluate_nomean methods into evaluate + .. versionchanged:: 2.0.0 renamed from evaluate to __call__ + """ self.log(logging.DEBUG, f"Received {data}") diff --git a/src/crappy/modifier/differentiate.py b/src/crappy/modifier/differentiate.py index e96c10b0..559099be 100644 --- a/src/crappy/modifier/differentiate.py +++ b/src/crappy/modifier/differentiate.py @@ -8,7 +8,10 @@ class Diff(Modifier): """This Modifier calculates the time derivative of a given label and adds the - derivative to the returned data.""" + derivative to the returned data. + + .. versionadded:: 1.4.0 + """ def __init__(self, label: str, @@ -21,6 +24,8 @@ def __init__(self, time_label: The label carrying the time information. out_label: The label carrying the calculated derivative. If not given, defaults to ``'d_