pax_global_header00006660000000000000000000000064146326334260014523gustar00rootroot0000000000000052 comment=b36e03c7637a07ece03e225f4946898ac70e27a9 DataLabSimpleClient-0.10.1/000077500000000000000000000000001463263342600154035ustar00rootroot00000000000000DataLabSimpleClient-0.10.1/.coveragerc000066400000000000000000000007631463263342600175320ustar00rootroot00000000000000[run] #--- Ignore this warning because the process isolation feature of DataLab #--- causes coverage to report 0% coverage when no computation is performed #--- in the isolated process during the session. disable_warnings = no-data-collected parallel = True concurrency = multiprocessing,thread omit = */cdlclient/utils/tests.py */cdlclient/tests/* */guidata/* */plotpy/* #--- Workaround for certain builds of python-opencv package: ./config-3.py ./config.py #---DataLabSimpleClient-0.10.1/.env.template000066400000000000000000000000141463263342600200010ustar00rootroot00000000000000PYTHONPATH=.DataLabSimpleClient-0.10.1/.gitignore000066400000000000000000000020231463263342600173700ustar00rootroot00000000000000winpython.env .spyderproject doc.zip Thumbs.db doctmp/ .vs/ *.pyproj *.sln releases/ *.chm .doctrees/ doc/install_requires.txt doc/extras_require-dev.txt doc/extras_require-doc.txt *.bak # Visual Studio Code .venv .env # Created by https://www.gitignore.io/api/python ### Python ### # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python env/ build/ _build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ *.egg-info/ .installed.cfg *.egg # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest # *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *,cover # Translations *.mo *.pot # Django stuff: *.log # Sphinx documentation docs/_build/ cdlclient/data/doc/ # PyBuilder target/ /.spyproject DataLabSimpleClient-0.10.1/.pylintrc000066400000000000000000000007161463263342600172540ustar00rootroot00000000000000[FORMAT] # Essential to be able to compare code side-by-side (`black` default setting) # and best compromise to minimize file size max-line-length=88 [TYPECHECK] ignored-modules=qtpy.QtWidgets,qtpy.QtCore,qtpy.QtGui [MESSAGES CONTROL] disable=wrong-import-order [DESIGN] max-args=8 # default: 5 max-attributes=12 # default: 7 max-branches=17 # default: 12 max-locals=20 # default: 15 min-public-methods=0 # default: 2 max-public-methods=25 # default: 20DataLabSimpleClient-0.10.1/.readthedocs.yaml000066400000000000000000000005301463263342600206300ustar00rootroot00000000000000# Read the Docs configuration file for Sphinx projects # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details version: 2 build: os: ubuntu-22.04 tools: python: "3.11" sphinx: configuration: doc/conf.py formats: - pdf python: install: - method: pip path: . extra_requirements: - doc DataLabSimpleClient-0.10.1/.vscode/000077500000000000000000000000001463263342600167445ustar00rootroot00000000000000DataLabSimpleClient-0.10.1/.vscode/launch.json000066400000000000000000000032411463263342600211110ustar00rootroot00000000000000{ // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "Run Test Launcher", "type": "python", "request": "launch", "program": "${workspaceFolder}/cdlclient/tests/__init__.py", "console": "integratedTerminal", "envFile": "${workspaceFolder}/.env", "python": "${config:python.defaultInterpreterPath}", "justMyCode": true, "env": { "QT_COLOR_MODE": "light", } }, { "name": "Run current file", "type": "python", "request": "launch", "program": "${file}", "console": "integratedTerminal", "envFile": "${workspaceFolder}/.env", "python": "${config:python.defaultInterpreterPath}", "justMyCode": false, "env": { "QT_COLOR_MODE": "light", // "LANG": "fr", } }, { "name": "Run current file (unattended)", "type": "python", "request": "launch", "program": "${file}", "console": "integratedTerminal", "envFile": "${workspaceFolder}/.env", "python": "${config:python.defaultInterpreterPath}", "justMyCode": false, "args": [ "--unattended", ], "env": { "GUIDATA_PARSE_ARGS": "1", } }, ] }DataLabSimpleClient-0.10.1/.vscode/settings.json000066400000000000000000000014011463263342600214730ustar00rootroot00000000000000{ "[bat]": { "files.encoding": "cp850", }, "editor.rulers": [ 88 ], "files.exclude": { "**/__pycache__": true, "**/*.pyc": true, "**/*.pyo": true }, "files.trimFinalNewlines": true, "files.trimTrailingWhitespace": true, "python.defaultInterpreterPath": "${env:CDL_PYTHONEXE}", "editor.formatOnSave": true, "python.analysis.autoFormatStrings": true, "python.testing.unittestEnabled": false, "python.testing.pytestEnabled": true, "python.testing.pytestPath": "pytest", "python.testing.pytestArgs": [], "[python]": { "editor.defaultFormatter": "charliermarsh.ruff" }, "editor.codeActionsOnSave": { "source.organizeImports": "explicit", }, }DataLabSimpleClient-0.10.1/.vscode/tasks.json000066400000000000000000000145571463263342600210000ustar00rootroot00000000000000{ // See https://go.microsoft.com/fwlink/?LinkId=733558 // for the documentation about the tasks.json format "version": "2.0.0", "tasks": [ { "label": "gettext - Scan", "type": "shell", "command": "cmd", "args": [ "/c", "gettext_scan.bat" ], "options": { "cwd": "scripts", "env": { "UNATTENDED": "1", "PYTHON": "${env:CDL_PYTHONEXE}" } }, "group": { "kind": "build", "isDefault": true }, "presentation": { "echo": true, "reveal": "always", "focus": false, "panel": "shared", "showReuseMessage": true, "clear": false } }, { "label": "gettext - Compile", "type": "shell", "command": "cmd", "args": [ "/c", "gettext.bat", "compile" ], "options": { "cwd": "scripts", "env": { "UNATTENDED": "1", "PYTHON": "${env:CDL_PYTHONEXE}" } }, "group": { "kind": "build", "isDefault": true }, "presentation": { "echo": true, "reveal": "always", "focus": false, "panel": "shared", "showReuseMessage": true, "clear": false } }, { "label": "Run Pylint", "type": "shell", "command": "cmd", "args": [ "/c", "run_pylint.bat", "--disable=fixme", ], "options": { "cwd": "scripts", "env": { "UNATTENDED": "1", "PYTHON": "${env:CDL_PYTHONEXE}" } }, "group": { "kind": "build", "isDefault": true }, "presentation": { "echo": true, "reveal": "always", "focus": false, "panel": "dedicated", "showReuseMessage": true, "clear": true } }, { "label": "Run Ruff", "type": "shell", "command": "cmd", "args": [ "/c", "run_ruff.bat", ], "options": { "cwd": "scripts", "env": { "PYTHON": "${env:CDL_PYTHONEXE}", "UNATTENDED": "1" } }, "group": { "kind": "build", "isDefault": true }, "presentation": { "echo": true, "reveal": "always", "focus": false, "panel": "dedicated", "showReuseMessage": true, "clear": true } }, { "label": "Run Coverage", "type": "shell", "command": "cmd", "args": [ "/c", "run_coverage.bat" ], "options": { "cwd": "scripts", "env": { "UNATTENDED": "1", "PYTHON": "${env:CDL_PYTHONEXE}" } }, "group": { "kind": "build", "isDefault": true }, "presentation": { "echo": true, "reveal": "always", "focus": false, "panel": "dedicated", "showReuseMessage": true, "clear": true } }, { "label": "Clean Up", "type": "shell", "command": "cmd", "args": [ "/c", "clean_up.bat" ], "options": { "cwd": "scripts" }, "group": { "kind": "build", "isDefault": true }, "presentation": { "echo": true, "reveal": "always", "focus": false, "panel": "shared", "showReuseMessage": true, "clear": false } }, { "label": "Build documentation", "type": "shell", "command": "cmd", "options": { "cwd": "scripts", "env": { "PYTHON": "${env:CDL_PYTHONEXE}", "QT_COLOR_MODE": "light", "UNATTENDED": "1" } }, "args": [ "/c", "build_doc.bat" ], "problemMatcher": [], "group": { "kind": "build", "isDefault": true }, "presentation": { "echo": true, "reveal": "always", "focus": false, "panel": "shared", "showReuseMessage": true, "clear": true } }, { "label": "Build Python packages", "type": "shell", "command": "cmd", "options": { "cwd": "scripts", "env": { "PYTHON": "${env:CDL_PYTHONEXE}", "UNATTENDED": "1" } }, "args": [ "/c", "build_dist.bat" ], "problemMatcher": [], "group": { "kind": "build", "isDefault": true }, "presentation": { "echo": true, "reveal": "always", "focus": false, "panel": "shared", "showReuseMessage": true, "clear": true }, "dependsOrder": "sequence", "dependsOn": [ "Clean Up" ] }, ] }DataLabSimpleClient-0.10.1/CHANGELOG.md000066400000000000000000000077741463263342600172330ustar00rootroot00000000000000# DataLab Simple Client Releases # ## Version 0.10.1 ## 🛠️ Bug fixes: * Fixed `get_version` method in `SimpleRemoteProxy` to handle server versions with a development suffix (e.g. `0.14.dev0`) ## Version 0.10.0 ## DataLab Simple Client is fully compatible with **DataLab 0.14.2** and above. With older versions of the DataLab server, some features may not work. 💥 Changes: * Remote API (`SimpleRemoteProxy`): * Merged `open_object` and `open_objects` methods to `load_from_files` ## Version 0.9.1 ## DataLab Simple Client is fully compatible with **DataLab 0.14.0** and above. With older versions of the DataLab server, some features may not work. 💥 Changes: * Remote API (`SimpleRemoteProxy`): * Changed constructor signature to accept `autoconnect` as argument, defaulting to `True` * Thus, when creating a `SimpleRemoteProxy` instance, the connection to the server is now established automatically by default (i.e. same behavior as DataLab's `cdl.proxy.RemoteProxy` class) * `get_object_titles` method now accepts "macro" as panel name and returns the list of macro titles * New `run_macro`, `stop_macro` and `import_macro_from_file` methods ## Version 0.8.1 ## DataLab Simple Client is fully compatible with **DataLab 0.11.0** and above. With older versions of the DataLab server, some features may not work. 💥 Changes: * Added `keep_roi` argument to `SimpleRemoteProxy.delete_metadata` method 🛠️ Bug fixes: * Fixed `SimpleRemoteProxy.get_object` method when there is no object to return (`None` is returned instead of an exception) ## Version 0.7.0 ## DataLab Simple Client is fully compatible with **DataLab 0.10.0** and above. With older versions of the DataLab server, some features may not work. 💥 Changes: * Added `toggle_auto_refresh` method to `SimpleRemoteProxy` * Added `context_no_refresh` method to `SimpleRemoteProxy` (context manager) * Added `toggle_show_titles` method to `SimpleRemoteProxy` * Remote client is now checking the server version and shows a warning message if the server version may not be fully compatible with the client version. ## Version 0.6.0 ## 💥 Changes: * Remote API (`SimpleRemoteProxy`): * Added `get_group_titles_with_object_infos` method * New `widgets` module: * New `GetObjectDialog` class: * Ready-to-use dialog box to retrieve an object from a DataLab server * `from cdlclient.widgets import GetObjectDialog` * See example in `cdlclient/tests/get_object_dialog.py` ## Version 0.5.0 ## 💥 Changes: * Remote API (`SimpleRemoteProxy`): * Added `is_connected` method * New `widgets` module: * New `ConnectionDialog` class: * Ready-to-use dialog box to connect to a DataLab server * `from cdlclient.widgets import ConnectionDialog` * See example in `cdlclient/tests/connect_dialog.py` ## Version 0.4.0 ## 💥 Changes: * Remote API (`SimpleRemoteProxy`): * Added dict-like interface (e.g. `proxy['obj123']`) * Renamed `switch_to_panel` method to `set_current_panel` (compatibility with DataLab 0.9) * Added `get_current_panel` method * Changed `select_groups` first argument `selection` (compatibility with DataLab 0.9) * Changed `select_objects` arguments (compatibility with DataLab 0.9) ## Version 0.3.0 ## 💥 Changes: * Remote API (`SimpleRemoteProxy`): * `get_object` method now takes either object number, UUID or a title * `get_object_shapes` method now takes either object number, UUID or a title * Removed deprecated `get_object_from_uuid` and `get_object_from_title` methods * Simplified DataLab object model: * Added `SignalObj.uuid` item * Added `ImageObj.uuid` item ## Version 0.2.0 ## 💥 Changes: * Remote API (`SimpleRemoteProxy`): * New `raise_window` method * New `get_object_shapes` method * New `get_object` method * New `get_object_from_uuid` method * New `get_object_from_title` method * Added simplified DataLab object model: * `simplemodel.SignalObj` class * `simplemodel.ImageObj` class ## Version 0.1.0 ## First release of the DataLab Simple Client. DataLabSimpleClient-0.10.1/LICENSE000066400000000000000000000027721463263342600164200ustar00rootroot00000000000000BSD 3-Clause License Copyright (c) 2023, Codra, Pierre Raybaut. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. DataLabSimpleClient-0.10.1/MANIFEST.in000066400000000000000000000000111463263342600171310ustar00rootroot00000000000000graft docDataLabSimpleClient-0.10.1/README.md000066400000000000000000000052331463263342600166650ustar00rootroot00000000000000## DataLab Simple Client ![DataLab](https://raw.githubusercontent.com/DataLab-Platform/DataLabSimpleClient/main/doc/images/DataLab-banner.png) [![license](https://img.shields.io/pypi/l/cdlclient.svg)](./LICENSE) [![pypi version](https://img.shields.io/pypi/v/cdlclient.svg)](https://pypi.org/project/cdlclient/) [![PyPI status](https://img.shields.io/pypi/status/cdlclient.svg)](https://github.com/DataLab-Platform/DataLabSimpleClient) [![PyPI pyversions](https://img.shields.io/pypi/pyversions/cdlclient.svg)](https://pypi.python.org/pypi/cdlclient/) ℹ️ Created by [Codra](https://codra.net/)/[Pierre Raybaut](https://github.com/PierreRaybaut) in 2023, developed and maintained by DataLab open-source project team. ℹ️ DataLab is powered by [PlotPyStack](https://github.com/PlotPyStack) 🚀. ![PlotPyStack](https://raw.githubusercontent.com/PlotPyStack/.github/main/data/plotpy-stack-powered.png) ---- ## About DataLab DataLab is a generic signal and image processing software based on Python scientific libraries (such as NumPy, SciPy or scikit-image) and Qt graphical user interfaces (thanks to the powerful [PlotPyStack](https://github.com/PlotPyStack) - mostly the [guidata](https://github.com/PlotPyStack/guidata) and [PlotPy](https://github.com/PlotPyStack/PlotPy) libraries). DataLab is available as a **stand-alone** application (see for example our all-in-one Windows installer) or as an **addon to your Python-Qt application** thanks to advanced automation and embedding features. See [DataLab website](https://datalab-platform.com/) for more details. ## About this package DataLab Simple Client is a Python library that can be used to interact with a DataLab application (server). This allows to control DataLab application from a remote computer, or/and from a third-party application. DataLab Simple Client also provides ready-to-use widgets that can be used to communicate with a DataLab application: * `ConnectionDialog`: a dialog box that allows to connect to a DataLab application * `GetObjetDialog`: a dialog box that allows to retrieve an object from a DataLab application `ConnectionDialog` | `GetObjectDialog` :-------------------------:|:-------------------------: ![ConnectionDialog](https://raw.githubusercontent.com/DataLab-Platform/DataLabSimpleClient/main/doc/images/shots/connect_dialog.png) | ![GetObjectDialog](https://raw.githubusercontent.com/DataLab-Platform/DataLabSimpleClient/main/doc/images/shots/get_object_dialog.png) See [documentation](https://cdlclient.readthedocs.io/en/latest/) for more details on the library and [changelog](https://github.com/DataLab-Platform/DataLabSimpleClient/blob/main/CHANGELOG.md) for recent history of changes. DataLabSimpleClient-0.10.1/cdlclient/000077500000000000000000000000001463263342600173445ustar00rootroot00000000000000DataLabSimpleClient-0.10.1/cdlclient/__init__.py000066400000000000000000000012241463263342600214540ustar00rootroot00000000000000# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file. """ DataLab Simple Client ===================== DataLab Simple Client (`cdlclient`) is a Python library providing a proxy to `DataLab`_ application through XML-RPC protocol. .. _DataLab: https://datalab-platform.com/ """ # pylint: disable=unused-import from cdlclient.baseproxy import SimpleBaseProxy # noqa: F401 from cdlclient.remote import SimpleRemoteProxy # noqa: F401 __version__ = "0.10.1" __required_server_version__ = "0.14.2" __docurl__ = "https://cdlclient.readthedocs.io/en/latest/" __homeurl__ = "https://github.com/DataLab-Platform/DataLabSimpleClient/" DataLabSimpleClient-0.10.1/cdlclient/baseproxy.py000066400000000000000000000543501463263342600217410ustar00rootroot00000000000000# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file. """ DataLab base proxy module ------------------------- """ from __future__ import annotations import abc from collections.abc import Callable from typing import TYPE_CHECKING import guidata.dataset as gds import numpy as np if TYPE_CHECKING: from collections.abc import Iterator from cdlclient.remote import ServerProxy from cdlclient.simplemodel import ImageObj, SignalObj class SimpleAbstractCDLControl(abc.ABC): """Simple abstract base class for controlling DataLab This is a subset of DataLab's AbstractCDLControl, with only the methods that do not require DataLab object model to be implemented.""" def __len__(self) -> int: """Return number of objects""" return len(self.get_object_uuids()) def __getitem__( self, nb_id_title: int | str | None = None, ) -> SignalObj | ImageObj: """Return object""" return self.get_object(nb_id_title) def __iter__(self) -> Iterator[SignalObj | ImageObj]: """Iterate over objects""" uuids = self.get_object_uuids() for uuid in uuids: yield self.get_object(uuid) def __str__(self) -> str: """Return object string representation""" return super().__repr__() def __repr__(self) -> str: """Return object representation""" titles = self.get_object_titles() uuids = self.get_object_uuids() text = f"{str(self)} (DataLab, {len(titles)} items):\n" for uuid, title in zip(uuids, titles): text += f" {uuid}: {title}\n" return text def __bool__(self) -> bool: """Return True if model is not empty""" return bool(self.get_object_uuids()) def __contains__(self, id_title: str) -> bool: """Return True if object (UUID or title) is in model""" return id_title in (self.get_object_titles() + self.get_object_uuids()) @classmethod def get_public_methods(cls) -> list[str]: """Return all public methods of the class, except itself. Returns: list[str]: List of public methods """ return [ method for method in dir(cls) if not method.startswith("_") and method != "get_public_methods" ] @abc.abstractmethod def get_version(self) -> str: """Return DataLab version. Returns: str: DataLab version """ @abc.abstractmethod def close_application(self) -> None: """Close DataLab application""" @abc.abstractmethod def raise_window(self) -> None: """Raise DataLab window""" @abc.abstractmethod def get_current_panel(self) -> str: """Return current panel name. Returns: str: Panel name (valid values: "signal", "image", "macro")) """ @abc.abstractmethod def set_current_panel(self, panel: str) -> None: """Switch to panel. Args: panel (str): Panel name (valid values: "signal", "image", "macro")) """ @abc.abstractmethod def reset_all(self) -> None: """Reset all application data""" @abc.abstractmethod def toggle_auto_refresh(self, state: bool) -> None: """Toggle auto refresh state. Args: state (bool): Auto refresh state """ @abc.abstractmethod def toggle_show_titles(self, state: bool) -> None: """Toggle show titles state. Args: state (bool): Show titles state """ @abc.abstractmethod def save_to_h5_file(self, filename: str) -> None: """Save to a DataLab HDF5 file. Args: filename (str): HDF5 file name """ @abc.abstractmethod def open_h5_files( self, h5files: list[str] | None = None, import_all: bool | None = None, reset_all: bool | None = None, ) -> None: """Open a DataLab HDF5 file or import from any other HDF5 file. Args: h5files (list[str] | None): List of HDF5 files to open. Defaults to None. import_all (bool | None): Import all objects from HDF5 files. Defaults to None. reset_all (bool | None): Reset all application data. Defaults to None. """ @abc.abstractmethod def import_h5_file(self, filename: str, reset_all: bool | None = None) -> None: """Open DataLab HDF5 browser to Import HDF5 file. Args: filename (str): HDF5 file name reset_all (bool | None): Reset all application data. Defaults to None. """ @abc.abstractmethod def load_from_files(self, filenames: list[str]) -> None: """Open objects from files in current panel (signals/images). Args: filenames: list of file names """ @abc.abstractmethod def add_signal( self, title: str, xdata: np.ndarray, ydata: np.ndarray, xunit: str | None = None, yunit: str | None = None, xlabel: str | None = None, ylabel: str | None = None, ) -> bool: # pylint: disable=too-many-arguments """Add signal data to DataLab. Args: title (str): Signal title xdata (numpy.ndarray): X data ydata (numpy.ndarray): Y data xunit (str | None): X unit. Defaults to None. yunit (str | None): Y unit. Defaults to None. xlabel (str | None): X label. Defaults to None. ylabel (str | None): Y label. Defaults to None. Returns: bool: True if signal was added successfully, False otherwise Raises: ValueError: Invalid xdata dtype ValueError: Invalid ydata dtype """ @abc.abstractmethod # pylint: disable=too-many-arguments def add_image( self, title: str, data: np.ndarray, xunit: str | None = None, yunit: str | None = None, zunit: str | None = None, xlabel: str | None = None, ylabel: str | None = None, zlabel: str | None = None, ) -> bool: """Add image data to DataLab. Args: title (str): Image title data (numpy.ndarray): Image data xunit (str | None): X unit. Defaults to None. yunit (str | None): Y unit. Defaults to None. zunit (str | None): Z unit. Defaults to None. xlabel (str | None): X label. Defaults to None. ylabel (str | None): Y label. Defaults to None. zlabel (str | None): Z label. Defaults to None. Returns: bool: True if image was added successfully, False otherwise Raises: ValueError: Invalid data dtype """ @abc.abstractmethod def get_sel_object_uuids(self, include_groups: bool = False) -> list[str]: """Return selected objects uuids. Args: include_groups: If True, also return objects from selected groups. Returns: List of selected objects uuids. """ @abc.abstractmethod def select_objects( self, selection: list[int | str], panel: str | None = None, ) -> None: """Select objects in current panel. Args: selection: List of object numbers (1 to N) or uuids to select panel: panel name (valid values: "signal", "image"). If None, current panel is used. Defaults to None. """ @abc.abstractmethod def select_groups( self, selection: list[int | str] | None = None, panel: str | None = None ) -> None: """Select groups in current panel. Args: selection: List of group numbers (1 to N), or list of group uuids, or None to select all groups. Defaults to None. panel (str | None): panel name (valid values: "signal", "image"). If None, current panel is used. Defaults to None. """ @abc.abstractmethod def delete_metadata( self, refresh_plot: bool = True, keep_roi: bool = False ) -> None: """Delete metadata of selected objects Args: refresh_plot: Refresh plot. Defaults to True. keep_roi: Keep ROI. Defaults to False. """ @abc.abstractmethod def get_group_titles_with_object_infos( self, ) -> tuple[list[str], list[list[str]], list[list[str]]]: """Return groups titles and lists of inner objects uuids and titles. Returns: Tuple: groups titles, lists of inner objects uuids and titles """ @abc.abstractmethod def get_object_titles(self, panel: str | None = None) -> list[str]: """Get object (signal/image) list for current panel. Objects are sorted by group number and object index in group. Args: panel: panel name (valid values: "signal", "image", "macro"). If None, current data panel is used (i.e. signal or image panel). Returns: List of object titles Raises: ValueError: if panel not found """ @abc.abstractmethod def get_object( self, nb_id_title: int | str | None = None, panel: str | None = None, ) -> SignalObj | ImageObj: """Get object (signal/image) from index. Args: nb_id_title: Object number, or object id, or object title. Defaults to None (current object). panel: Panel name. Defaults to None (current panel). Returns: Object Raises: KeyError: if object not found """ @abc.abstractmethod def get_object_uuids(self, panel: str | None = None) -> list[str]: """Get object (signal/image) uuid list for current panel. Objects are sorted by group number and object index in group. Args: panel (str | None): panel name (valid values: "signal", "image"). If None, current panel is used. Returns: list[str]: list of object uuids Raises: ValueError: if panel not found """ @abc.abstractmethod def get_object_shapes( self, nb_id_title: int | str | None = None, panel: str | None = None, ) -> list: """Get plot item shapes associated to object (signal/image). Args: nb_id_title: Object number, or object id, or object title. Defaults to None (current object). panel: Panel name. Defaults to None (current panel). Returns: List of plot item shapes """ @abc.abstractmethod def add_annotations_from_items( self, items: list, refresh_plot: bool = True, panel: str | None = None ) -> None: """Add object annotations (annotation plot items). Args: items (list): annotation plot items refresh_plot (bool | None): refresh plot. Defaults to True. panel (str | None): panel name (valid values: "signal", "image"). If None, current panel is used. """ @abc.abstractmethod def add_label_with_title( self, title: str | None = None, panel: str | None = None ) -> None: """Add a label with object title on the associated plot Args: title (str | None): Label title. Defaults to None. If None, the title is the object title. panel (str | None): panel name (valid values: "signal", "image"). If None, current panel is used. """ @abc.abstractmethod def run_macro(self, number_or_title: int | str | None = None) -> None: """Run macro. Args: number: Number of the macro (starting at 1). Defaults to None (run current macro, or does nothing if there is no macro). """ @abc.abstractmethod def stop_macro(self, number_or_title: int | str | None = None) -> None: """Stop macro. Args: number: Number of the macro (starting at 1). Defaults to None (stop current macro, or does nothing if there is no macro). """ @abc.abstractmethod def import_macro_from_file(self, filename: str) -> None: """Import macro from file Args: filename: Filename. """ @abc.abstractmethod def calc(self, name: str, param: gds.DataSet | None = None) -> gds.DataSet: """Call compute function ``name`` in current panel's processor. Args: name (str): Compute function name param (guidata.dataset.DataSet | None): Compute function parameter. Defaults to None. Returns: guidata.dataset.DataSet: Compute function result """ def __getattr__(self, name: str) -> Callable: """Return compute function ``name`` in current panel's processor. Args: name (str): Compute function name Returns: Callable: Compute function Raises: AttributeError: If compute function ``name`` does not exist """ def compute_func(param: gds.DataSet | None = None) -> gds.DataSet: """Compute function. Args: param (guidata.dataset.DataSet | None): Compute function parameter. Defaults to None. Returns: guidata.dataset.DataSet: Compute function result """ return self.calc(name, param) if name.startswith("compute_"): return compute_func raise AttributeError(f"DataLab has no compute function '{name}'") class SimpleBaseProxy(SimpleAbstractCDLControl, metaclass=abc.ABCMeta): """Simple common base class for DataLab proxies This is a subset of DataLab's BaseProxy, with only the methods that do not require DataLab object model to be implemented. Args: cdlclient (CDLMainWindow | ServerProxy | None): CDLMainWindow instance or ServerProxy instance. If None, then the proxy implementation will have to set it later (e.g. see SimpleRemoteProxy). """ def __init__(self, cdlclient: ServerProxy | None = None) -> None: self._cdl = cdlclient def get_version(self) -> str: """Return DataLab version. Returns: str: DataLab version """ return self._cdl.get_version() def close_application(self) -> None: """Close DataLab application""" self._cdl.close_application() def raise_window(self) -> None: """Raise DataLab window""" self._cdl.raise_window() def get_current_panel(self) -> str: """Return current panel name. Returns: str: Panel name (valid values: "signal", "image", "macro")) """ return self._cdl.get_current_panel() def set_current_panel(self, panel: str) -> None: """Switch to panel. Args: panel (str): Panel name (valid values: "signal", "image", "macro")) """ self._cdl.set_current_panel(panel) def reset_all(self) -> None: """Reset all application data""" self._cdl.reset_all() def toggle_auto_refresh(self, state: bool) -> None: """Toggle auto refresh state. Args: state (bool): Auto refresh state """ self._cdl.toggle_auto_refresh(state) # Returns a context manager to temporarily disable autorefresh def context_no_refresh(self) -> Callable: """Return a context manager to temporarily disable auto refresh. Returns: Context manager Example: >>> with proxy.context_no_refresh(): ... proxy.add_image("image1", data1) ... proxy.compute_fft() ... proxy.compute_wiener() ... proxy.compute_ifft() ... # Auto refresh is disabled during the above operations """ class NoRefreshContextManager: """Context manager to temporarily disable auto refresh""" def __init__(self, cdl: SimpleAbstractCDLControl) -> None: self._cdl = cdl def __enter__(self) -> None: self._cdl.toggle_auto_refresh(False) def __exit__(self, exc_type, exc_value, traceback) -> None: self._cdl.toggle_auto_refresh(True) return NoRefreshContextManager(self) def toggle_show_titles(self, state: bool) -> None: """Toggle show titles state. Args: state (bool): Show titles state """ self._cdl.toggle_show_titles(state) def save_to_h5_file(self, filename: str) -> None: """Save to a DataLab HDF5 file. Args: filename (str): HDF5 file name """ self._cdl.save_to_h5_file(filename) def open_h5_files( self, h5files: list[str] | None = None, import_all: bool | None = None, reset_all: bool | None = None, ) -> None: """Open a DataLab HDF5 file or import from any other HDF5 file. Args: h5files (list[str] | None): List of HDF5 files to open. Defaults to None. import_all (bool | None): Import all objects from HDF5 files. Defaults to None. reset_all (bool | None): Reset all application data. Defaults to None. """ self._cdl.open_h5_files(h5files, import_all, reset_all) def import_h5_file(self, filename: str, reset_all: bool | None = None) -> None: """Open DataLab HDF5 browser to Import HDF5 file. Args: filename (str): HDF5 file name reset_all (bool | None): Reset all application data. Defaults to None. """ self._cdl.import_h5_file(filename, reset_all) def load_from_files(self, filenames: list[str]) -> None: """Open objects from files in current panel (signals/images). Args: filenames: list of file names """ self._cdl.load_from_files(filenames) def get_sel_object_uuids(self, include_groups: bool = False) -> list[str]: """Return selected objects uuids. Args: include_groups: If True, also return objects from selected groups. Returns: List of selected objects uuids. """ return self._cdl.get_sel_object_uuids(include_groups) def select_objects( self, selection: list[int | str], panel: str | None = None, ) -> None: """Select objects in current panel. Args: selection: List of object numbers (1 to N) or uuids to select panel: panel name (valid values: "signal", "image"). If None, current panel is used. Defaults to None. """ self._cdl.select_objects(selection, panel) def select_groups( self, selection: list[int | str] | None = None, panel: str | None = None ) -> None: """Select groups in current panel. Args: selection: List of group numbers (1 to N), or list of group uuids, or None to select all groups. Defaults to None. panel (str | None): panel name (valid values: "signal", "image"). If None, current panel is used. Defaults to None. """ self._cdl.select_groups(selection, panel) def delete_metadata( self, refresh_plot: bool = True, keep_roi: bool = False ) -> None: """Delete metadata of selected objects Args: refresh_plot: Refresh plot. Defaults to True. keep_roi: Keep ROI. Defaults to False. """ self._cdl.delete_metadata(refresh_plot, keep_roi) def get_group_titles_with_object_infos( self, ) -> tuple[list[str], list[list[str]], list[list[str]]]: """Return groups titles and lists of inner objects uuids and titles. Returns: Tuple: groups titles, lists of inner objects uuids and titles """ return self._cdl.get_group_titles_with_object_infos() def get_object_titles(self, panel: str | None = None) -> list[str]: """Get object (signal/image) list for current panel. Objects are sorted by group number and object index in group. Args: panel: panel name (valid values: "signal", "image", "macro"). If None, current data panel is used (i.e. signal or image panel). Returns: List of object titles Raises: ValueError: if panel not found """ return self._cdl.get_object_titles(panel) def get_object_uuids(self, panel: str | None = None) -> list[str]: """Get object (signal/image) uuid list for current panel. Objects are sorted by group number and object index in group. Args: panel (str | None): panel name (valid values: "signal", "image"). If None, current panel is used. Returns: list[str]: list of object uuids Raises: ValueError: if panel not found """ return self._cdl.get_object_uuids(panel) def add_label_with_title( self, title: str | None = None, panel: str | None = None ) -> None: """Add a label with object title on the associated plot Args: title (str | None): Label title. Defaults to None. If None, the title is the object title. panel (str | None): panel name (valid values: "signal", "image"). If None, current panel is used. """ self._cdl.add_label_with_title(title, panel) def run_macro(self, number_or_title: int | str | None = None) -> None: """Run macro. Args: number: Number of the macro (starting at 1). Defaults to None (run current macro, or does nothing if there is no macro). """ self._cdl.run_macro(number_or_title) def stop_macro(self, number_or_title: int | str | None = None) -> None: """Stop macro. Args: number: Number of the macro (starting at 1). Defaults to None (stop current macro, or does nothing if there is no macro). """ self._cdl.stop_macro(number_or_title) def import_macro_from_file(self, filename: str) -> None: """Import macro from file Args: filename: Filename. """ return self._cdl.import_macro_from_file(filename) DataLabSimpleClient-0.10.1/cdlclient/config.py000066400000000000000000000006561463263342600211720ustar00rootroot00000000000000# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file. """ DataLab Simple Client configuration module ------------------------------------------ This module handles `DataLab Simple Client` configuration. """ from __future__ import annotations from guidata import configtools MOD_NAME = "cdlclient" _ = configtools.get_translation(MOD_NAME) MOD_PATH = configtools.get_module_data_path(MOD_NAME) DataLabSimpleClient-0.10.1/cdlclient/locale/000077500000000000000000000000001463263342600206035ustar00rootroot00000000000000DataLabSimpleClient-0.10.1/cdlclient/locale/fr/000077500000000000000000000000001463263342600212125ustar00rootroot00000000000000DataLabSimpleClient-0.10.1/cdlclient/locale/fr/LC_MESSAGES/000077500000000000000000000000001463263342600227775ustar00rootroot00000000000000DataLabSimpleClient-0.10.1/cdlclient/locale/fr/LC_MESSAGES/cdlclient.po000066400000000000000000000054151463263342600253050ustar00rootroot00000000000000# -*- coding: utf-8 -*- # cdl module translation file # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "POT-Creation-Date: 2023-11-23 17:34+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: utf-8\n" "Generated-By: pygettext.py 1.5\n" #: cdlclient\tests\remoteclient_app.py:62 msgid "This the client application, which connects to DataLab." msgstr "Ceci l'application cliente, qui se connecte à DataLab." #: cdlclient\tests\remoteclient_app.py:63 msgid "Connect to DataLab" msgstr "Se connecter à DataLab" #: cdlclient\tests\remoteclient_app.py:100 msgid "Execute multiple commands" msgstr "Exécuter des commandes multiples" #: cdlclient\tests\remoteclient_app.py:101 msgid "Get object titles" msgstr "Obtenir la liste des titres" #: cdlclient\tests\remoteclient_app.py:102 msgid "Get object uuids" msgstr "Obtenir la liste des uuids" #: cdlclient\tests\remoteclient_app.py:103 msgid "Get object" msgstr "Obtenir un objet" #: cdlclient\tests\remoteclient_app.py:104 msgid "Get object using dialog box" msgstr "Obtenir un objet en utilisant une boîte de dialogue" #: cdlclient\tests\remoteclient_base.py:88 msgid "Host application" msgstr "Application hôte" #: cdlclient\tests\remoteclient_base.py:115 msgid "Raise window" msgstr "Mettre la fenêtre au premier plan" #: cdlclient\tests\remoteclient_base.py:117 msgid "Add signal objects" msgstr "Ajouter des objets signal" #: cdlclient\tests\remoteclient_base.py:118 msgid "Add image objects" msgstr "Ajouter des objets image" #: cdlclient\tests\remoteclient_base.py:119 msgid "Remove all objects" msgstr "Supprimer tous les objets" #: cdlclient\tests\remoteclient_base.py:120 msgid "Close DataLab" msgstr "Fermer DataLab" #: cdlclient\widgets\connection.py:58 msgid "Connection to DataLab" msgstr "Connexion à DataLab" #: cdlclient\widgets\connection.py:70 msgid "Waiting for connection..." msgstr "En attente de connexion..." #: cdlclient\widgets\connection.py:101 msgid "Connecting to server..." msgstr "Connexion au serveur..." #: cdlclient\widgets\connection.py:109 msgid "Connection successful!" msgstr "Connecté avec succès !" #: cdlclient\widgets\connection.py:117 msgid "Connection failed." msgstr "Echec de la connexion." #: cdlclient\widgets\objectdialog.py:174 msgid "Select object" msgstr "Sélectionner un objet" #: cdlclient\widgets\objectdialog.py:191 msgid "Signals" msgstr "Signaux" #: cdlclient\widgets\objectdialog.py:192 msgid "Images" msgstr "Images" #: cdlclient\widgets\objectdialog.py:196 msgid "Active panel:" msgstr "Panneau actif :" #~ msgid "Get object from DataLab" #~ msgstr "Obtenir un objet depuis DataLab" DataLabSimpleClient-0.10.1/cdlclient/qthelpers.py000066400000000000000000000053431463263342600217320ustar00rootroot00000000000000# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file. """Qt helpers""" from __future__ import annotations import os import os.path as osp from collections.abc import Generator from contextlib import contextmanager from qtpy import QtCore as QC from qtpy import QtGui as QG from qtpy import QtSvg as QS from qtpy import QtWidgets as QW from cdlclient.config import MOD_PATH WIDGETS_PATH = osp.join(MOD_PATH, "widgets") def svgtext_to_icon(text: str) -> QG.QIcon: """Convert SVG text to QIcon Args: text: SVG text Returns: Icon """ svg_bytes = QC.QByteArray(text.encode("utf-8")) renderer = QS.QSvgRenderer(svg_bytes) # pylint: disable=no-member pixmap = QG.QPixmap(64, 64) # You can adjust the size as needed pixmap.fill(QC.Qt.transparent) # Fill the pixmap with transparency painter = QG.QPainter(pixmap) renderer.render(painter) painter.end() return QG.QIcon(pixmap) def svgfile_to_base64(filename: str) -> bytes: """Convert SVG file to Base64-encoded bytes Args: filename: SVG filename Returns: Base64-encoded bytes """ image = QG.QImage(filename) data = QC.QByteArray() buf = QC.QBuffer(data) image.save(buf, "PNG") return data.toBase64().data() def imagefile_to_base64(filename: str) -> bytes: """Convert image file to Base64-encoded bytes Args: filename: image filename Returns: Base64-encoded bytes """ image = QG.QImage(filename) data = QC.QByteArray() buf = QC.QBuffer(data) image.save(buf, "PNG") return data.toBase64().data() def imagefile_to_python_module(filename: str, destmod: str) -> None: """Convert image file to Python module Args: filename: image filename destmod: destination module name """ data = imagefile_to_base64(filename) destmod_path = osp.join(WIDGETS_PATH, destmod + ".py") if osp.isfile(destmod_path): os.remove(destmod_path) with open(destmod_path, "wb") as fn: fn.write("# -*- coding: utf-8 -*-\n\n".encode("utf-8")) fn.write("# pylint: skip-file\n\n".encode("utf-8")) fn.write("DATA = b'".encode("utf-8")) fn.write(data) fn.write("'".encode("utf-8")) @contextmanager def block_signals(widget: QW.QWidget, enable: bool) -> Generator[None, None, None]: """Eventually block/unblock widget Qt signals before/after doing some things (enable: True if feature is enabled)""" if enable: widget.blockSignals(True) try: yield finally: if enable: widget.blockSignals(False) widget.blockSignals(False) widget.blockSignals(False) widget.blockSignals(False) DataLabSimpleClient-0.10.1/cdlclient/remote.py000066400000000000000000000400371463263342600212150ustar00rootroot00000000000000# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file. """ DataLab remote control ---------------------- This module provides utilities to control DataLab from a Python script (e.g. with Spyder) or from a Jupyter notebook. The :class:`SimpleRemoteProxy` class provides the main interface to DataLab XML-RPC server. """ from __future__ import annotations import configparser as cp import importlib import json import os import os.path as osp import sys import time import warnings from io import BytesIO from xmlrpc.client import Binary, ServerProxy import guidata.dataset as gds import numpy as np from guidata.env import execenv from guidata.io import JSONReader, JSONWriter from guidata.userconfig import get_config_basedir import cdlclient from cdlclient import simplemodel from cdlclient.baseproxy import SimpleBaseProxy from cdlclient.simplemodel import ImageObj, SignalObj # pylint: disable=invalid-name # Allows short reference names like x, y, ... # pylint: disable=duplicate-code XMLRPCPORT_ENV = "CDL_XMLRPCPORT" def get_xmlrpcport_from_env() -> int | None: """Get XML-RPC port number from environment variable.""" try: return int(os.environ.get(XMLRPCPORT_ENV)) except (TypeError, ValueError): return None def array_to_rpcbinary(data: np.ndarray) -> Binary: """Convert NumPy array to XML-RPC Binary object, with shape and dtype. The array is converted to a binary string using NumPy's native binary format. Args: data: NumPy array to convert Returns: XML-RPC Binary object """ dbytes = BytesIO() np.save(dbytes, data, allow_pickle=False) return Binary(dbytes.getvalue()) def rpcbinary_to_array(binary: Binary) -> np.ndarray: """Convert XML-RPC binary to NumPy array. Args: binary: XML-RPC Binary object Returns: NumPy array """ dbytes = BytesIO(binary.data) return np.load(dbytes, allow_pickle=False) def dataset_to_json(param: gds.DataSet) -> list[str]: """Convert guidata DataSet to JSON data. The JSON data is a list of three elements: - The first element is the module name of the DataSet class - The second element is the class name of the DataSet class - The third element is the JSON data of the DataSet instance Args: param: guidata DataSet to convert Returns: JSON data """ writer = JSONWriter() param.serialize(writer) param_json = writer.get_json() klass = param.__class__ return [klass.__module__, klass.__name__, param_json] def json_to_dataset(param_data: list[str]) -> gds.DataSet: """Convert JSON data to guidata DataSet. Args: param_data: JSON data Returns: guidata DataSet """ param_module, param_clsname, param_json = param_data # Replacing DataLab's model by DataLab Simple Client's model: param_module = param_module.replace("cdl.core.model.signal", simplemodel.__name__) param_module = param_module.replace("cdl.core.model.image", simplemodel.__name__) mod = importlib.__import__(param_module, fromlist=[param_clsname]) klass = getattr(mod, param_clsname) param = klass() reader = JSONReader(param_json) param.deserialize(reader) return param # === Python 2.7 client side: # # # See DataLab's doc/remotecontrol_py27.py for an almost complete Python 2.7 # # implementation of SimpleRemoteProxy class # # import io # from xmlrpclib import ServerProxy, Binary # import numpy as np # def array_to_binary(data): # """Convert NumPy array to XML-RPC Binary object, with shape and dtype""" # dbytes = io.BytesIO() # np.save(dbytes, data, allow_pickle=False) # return Binary(dbytes.getvalue()) # s = ServerProxy("http://127.0.0.1:8000") # data = np.array([[3, 4, 5], [7, 8, 0]], dtype=np.uint16) # s.add_image("toto", array_to_binary(data)) def get_cdl_xmlrpc_port(): """Return DataLab current XML-RPC port""" if sys.platform == "win32" and "HOME" in os.environ: os.environ.pop("HOME") # Avoid getting old WinPython settings dir fname = osp.join(get_config_basedir(), ".DataLab", "DataLab.ini") ini = cp.ConfigParser() ini.read(fname) try: return ini.get("main", "rpc_server_port") except (cp.NoSectionError, cp.NoOptionError) as exc: raise ConnectionRefusedError("DataLab has not yet been executed") from exc def items_to_json(items: list) -> str | None: """Convert plot items to JSON string. Args: items (list): list of plot items Returns: str: JSON string or None if items is empty """ from plotpy.io import save_items # pylint: disable=import-outside-toplevel if items: writer = JSONWriter(None) save_items(writer, items) return writer.get_json(indent=4) return None def json_to_items(json_str: str | None) -> list: """Convert JSON string to plot items. Args: json_str (str): JSON string or None Returns: list: list of plot items """ from plotpy.io import load_items # pylint: disable=import-outside-toplevel items = [] if json_str: try: for item in load_items(JSONReader(json_str)): items.append(item) except json.decoder.JSONDecodeError: pass return items def is_version_at_least(version1: str, version2: str) -> bool: """ Compare two version strings to check if the first version is at least equal to the second. Args: version1 (str): The first version string. version2 (str): The second version string. Returns: bool: True if version1 is greater than or equal to version2, False otherwise. .. note:: Development, alpha, beta, and rc versions are considered to be equal to the corresponding release version. """ # Split the version strings into parts parts1 = [part.strip() for part in version1.split(".")] parts2 = [part.strip() for part in version2.split(".")] for part1, part2 in zip(parts1, parts2): if part1.isdigit() and part2.isdigit(): if int(part1) > int(part2): return True elif int(part1) < int(part2): return False elif part1 > part2: return True elif part1 < part2: return False return len(parts1) >= len(parts2) class SimpleRemoteProxy(SimpleBaseProxy): """Object representing a proxy/client to DataLab XML-RPC server. This object is used to call DataLab functions from a Python script. This is a subset of DataLab's `RemoteClient` class, with only the methods that do not require DataLab object model to be implemented. Args: autoconnect: If True, automatically connect to DataLab XML-RPC server. Defaults to True. Raises: ConnectionRefusedError: DataLab is currently not running Examples: Here is a simple example of how to use SimpleRemoteProxy in a Python script or in a Jupyter notebook: >>> from cdlclient import SimpleRemoteProxy >>> proxy = SimpleRemoteProxy() # autoconnect is on by default Connecting to DataLab XML-RPC server...OK (port: 28867) >>> proxy.get_version() '1.0.0' >>> proxy.add_signal("toto", np.array([1., 2., 3.]), np.array([4., 5., -1.])) True >>> proxy.get_object_titles() ['toto'] >>> proxy["toto"] >>> "toto" in proxy True >>> proxy[1] >>> proxy[1].data array([1., 2., 3.]) """ def __init__(self, autoconnect: bool = True) -> None: super().__init__() self.port: str = None self._cdl: ServerProxy if autoconnect: self.connect() def __connect_to_server(self, port: str | None = None) -> None: """Connect to DataLab XML-RPC server. Args: port (str | None): XML-RPC port to connect to. If not specified, the port is automatically retrieved from DataLab configuration. Raises: ConnectionRefusedError: DataLab is currently not running """ if port is None: port = get_xmlrpcport_from_env() if port is None: port = get_cdl_xmlrpc_port() self.port = port self._cdl = ServerProxy(f"http://127.0.0.1:{port}", allow_none=True) try: version = self.get_version() except ConnectionRefusedError as exc: raise ConnectionRefusedError("DataLab is currently not running") from exc # If DataLab version is not compatible with this client, show a warning using # standard `warnings` module: if not is_version_at_least(version, cdlclient.__required_server_version__): warnings.warn( f"DataLab version {version} is not fully compatible with " f"DataLab Simple Client version {cdlclient.__version__}.\n" f"Please upgrade DataLab to {cdlclient.__required_server_version__} " f"or higher." ) def connect( self, port: str | None = None, timeout: float | None = None, retries: int | None = None, ) -> None: """Try to connect to DataLab XML-RPC server. Args: port (str | None): XML-RPC port to connect to. If not specified, the port is automatically retrieved from DataLab configuration. timeout (float | None): Timeout in seconds. Defaults to 5.0. retries (int | None): Number of retries. Defaults to 10. Raises: ConnectionRefusedError: Unable to connect to DataLab ValueError: Invalid timeout (must be >= 0.0) ValueError: Invalid number of retries (must be >= 1) """ timeout = 5.0 if timeout is None else timeout retries = 10 if retries is None else retries if timeout < 0.0: raise ValueError("timeout must be >= 0.0") if retries < 1: raise ValueError("retries must be >= 1") execenv.print("Connecting to DataLab XML-RPC server...", end="") for _index in range(retries): try: self.__connect_to_server(port=port) break except ConnectionRefusedError: time.sleep(timeout / retries) else: execenv.print("KO") raise ConnectionRefusedError("Unable to connect to DataLab") execenv.print(f"OK (port: {self.port})") def disconnect(self) -> None: """Disconnect from DataLab XML-RPC server.""" # This is not mandatory with XML-RPC, but if we change protocol in the # future, it may be useful to have a disconnect method. self._cdl = None def is_connected(self) -> bool: """Return True if connected to DataLab XML-RPC server.""" if self._cdl is not None: try: self.get_version() return True except ConnectionRefusedError: self._cdl = None return False def get_method_list(self) -> list[str]: """Return list of available methods.""" return self._cdl.system.listMethods() # === Following methods should match the register functions in XML-RPC server def add_signal( self, title: str, xdata: np.ndarray, ydata: np.ndarray, xunit: str | None = None, yunit: str | None = None, xlabel: str | None = None, ylabel: str | None = None, ) -> bool: # pylint: disable=too-many-arguments """Add signal data to DataLab. Args: title (str): Signal title xdata (numpy.ndarray): X data ydata (numpy.ndarray): Y data xunit (str | None): X unit. Defaults to None. yunit (str | None): Y unit. Defaults to None. xlabel (str | None): X label. Defaults to None. ylabel (str | None): Y label. Defaults to None. Returns: bool: True if signal was added successfully, False otherwise Raises: ValueError: Invalid xdata dtype ValueError: Invalid ydata dtype """ xbinary = array_to_rpcbinary(xdata) ybinary = array_to_rpcbinary(ydata) return self._cdl.add_signal( title, xbinary, ybinary, xunit, yunit, xlabel, ylabel ) # pylint: disable=too-many-arguments def add_image( self, title: str, data: np.ndarray, xunit: str | None = None, yunit: str | None = None, zunit: str | None = None, xlabel: str | None = None, ylabel: str | None = None, zlabel: str | None = None, ) -> bool: """Add image data to DataLab. Args: title (str): Image title data (numpy.ndarray): Image data xunit (str | None): X unit. Defaults to None. yunit (str | None): Y unit. Defaults to None. zunit (str | None): Z unit. Defaults to None. xlabel (str | None): X label. Defaults to None. ylabel (str | None): Y label. Defaults to None. zlabel (str | None): Z label. Defaults to None. Returns: bool: True if image was added successfully, False otherwise Raises: ValueError: Invalid data dtype """ zbinary = array_to_rpcbinary(data) return self._cdl.add_image( title, zbinary, xunit, yunit, zunit, xlabel, ylabel, zlabel ) def calc(self, name: str, param: gds.DataSet | None = None) -> gds.DataSet: """Call compute function ``name`` in current panel's processor. Args: name (str): Compute function name param (guidata.dataset.DataSet | None): Compute function parameter. Defaults to None. Returns: guidata.dataset.DataSet: Compute function result """ if param is None: return self._cdl.calc(name) return self._cdl.calc(name, dataset_to_json(param)) def get_object( self, nb_id_title: int | str | None = None, panel: str | None = None, ) -> SignalObj | ImageObj: """Get object (signal/image) from index. Args: nb_id_title: Object number, or object id, or object title. Defaults to None (current object). panel: Panel name. Defaults to None (current panel). Returns: Object Raises: KeyError: if object not found """ param_data = self._cdl.get_object(nb_id_title, panel) if param_data is None: return None return json_to_dataset(param_data) def get_object_shapes( self, nb_id_title: int | str | None = None, panel: str | None = None, ) -> list: """Get plot item shapes associated to object (signal/image). Args: nb_id_title: Object number, or object id, or object title. Defaults to None (current object). panel: Panel name. Defaults to None (current panel). Returns: List of plot item shapes """ items_json = self._cdl.get_object_shapes(nb_id_title, panel) return json_to_items(items_json) def add_annotations_from_items( self, items: list, refresh_plot: bool = True, panel: str | None = None ) -> None: """Add object annotations (annotation plot items). .. note:: This method is only available if PlotPy is installed. Args: items (list): annotation plot items refresh_plot (bool | None): refresh plot. Defaults to True. panel (str | None): panel name (valid values: "signal", "image"). If None, current panel is used. """ try: items_json = items_to_json(items) except ImportError as exc: raise ImportError("PlotPy is not installed") from exc if items_json is not None: self._cdl.add_annotations_from_items(items_json, refresh_plot, panel) DataLabSimpleClient-0.10.1/cdlclient/simplemodel.py000066400000000000000000000103641463263342600222340ustar00rootroot00000000000000# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file. """ DataLab Simple Model """ # pylint: disable=invalid-name # Allows short reference names like x, y, ... from __future__ import annotations from typing import Any import guidata.dataset as gds from guidata.io import JSONReader ROI_KEY = "_roi_" ANN_KEY = "_ann_" class BaseObj: """Base object""" # This is overriden in children classes with a gds.DictItem instance: metadata: dict[str, Any] = {} def __set_annotations(self, annotations: str | None) -> None: """Set object annotations (JSON string describing annotation plot items) Args: annotations (str | None): JSON string describing annotation plot items, or None to remove annotations """ if annotations is None: if ANN_KEY in self.metadata: self.metadata.pop(ANN_KEY) else: self.metadata[ANN_KEY] = annotations def __get_annotations(self) -> str: """Get object annotations (JSON string describing annotation plot items)""" return self.metadata.get(ANN_KEY, "") annotations = property(__get_annotations, __set_annotations) def get_annotated_shapes(self): """Get annotated shapes""" from plotpy.io import load_items # pylint: disable=import-outside-toplevel if self.annotations: return load_items(JSONReader(self.annotations)) return [] class SignalObj(gds.DataSet, BaseObj): """Signal object (simplified version of DataLab's Signal object)""" uuid = gds.StringItem("UUID").set_prop("display", hide=True) _tabs = gds.BeginTabGroup("all") _datag = gds.BeginGroup("Data and metadata") title = gds.StringItem("Signal title", default="Untitled") xydata = gds.FloatArrayItem("Data", transpose=True, minmax="rows") metadata = gds.DictItem("Metadata", default={}) _e_datag = gds.EndGroup("Data and metadata") _unitsg = gds.BeginGroup("Titles and units") title = gds.StringItem("Signal title", default="Untitled") _tabs_u = gds.BeginTabGroup("units") _unitsx = gds.BeginGroup("X-axis") xlabel = gds.StringItem("Title", default="") xunit = gds.StringItem("Unit", default="") _e_unitsx = gds.EndGroup("X-axis") _unitsy = gds.BeginGroup("Y-axis") ylabel = gds.StringItem("Title", default="") yunit = gds.StringItem("Unit", default="") _e_unitsy = gds.EndGroup("Y-axis") _e_tabs_u = gds.EndTabGroup("units") _e_unitsg = gds.EndGroup("Titles and units") _e_tabs = gds.EndTabGroup("all") class ImageObj(gds.DataSet, BaseObj): """Image object (simplified version of DataLab's Image object)""" uuid = gds.StringItem("UUID").set_prop("display", hide=True) _tabs = gds.BeginTabGroup("all") _datag = gds.BeginGroup("Data") data = gds.FloatArrayItem("Data") metadata = gds.DictItem("Metadata", default={}) _e_datag = gds.EndGroup("Data") _dxdyg = gds.BeginGroup("Origin / Pixel spacing") _origin = gds.BeginGroup("Origin") x0 = gds.FloatItem("X0", default=0.0) y0 = gds.FloatItem("Y0", default=0.0).set_pos(col=1) _e_origin = gds.EndGroup("Origin") _pixel_spacing = gds.BeginGroup("Pixel spacing") dx = gds.FloatItem("Δx", default=1.0, nonzero=True) dy = gds.FloatItem("Δy", default=1.0, nonzero=True).set_pos(col=1) _e_pixel_spacing = gds.EndGroup("Pixel spacing") _e_dxdyg = gds.EndGroup("Origin / Pixel spacing") _unitsg = gds.BeginGroup("Titles / Units") title = gds.StringItem("Image title", default="Untitled") _tabs_u = gds.BeginTabGroup("units") _unitsx = gds.BeginGroup("X-axis") xlabel = gds.StringItem("Title", default="") xunit = gds.StringItem("Unit", default="") _e_unitsx = gds.EndGroup("X-axis") _unitsy = gds.BeginGroup("Y-axis") ylabel = gds.StringItem("Title", default="") yunit = gds.StringItem("Unit", default="") _e_unitsy = gds.EndGroup("Y-axis") _unitsz = gds.BeginGroup("Z-axis") zlabel = gds.StringItem("Title", default="") zunit = gds.StringItem("Unit", default="") _e_unitsz = gds.EndGroup("Z-axis") _e_tabs_u = gds.EndTabGroup("units") _e_unitsg = gds.EndGroup("Titles / Units") _e_tabs = gds.EndTabGroup("all") DataLabSimpleClient-0.10.1/cdlclient/tests/000077500000000000000000000000001463263342600205065ustar00rootroot00000000000000DataLabSimpleClient-0.10.1/cdlclient/tests/__init__.py000066400000000000000000000005431463263342600226210ustar00rootroot00000000000000# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file. """ DataLab Simple Client unit tests """ from __future__ import annotations from guidata.guitest import run_testlauncher import cdlclient def run() -> None: """Run DataLab test launcher""" run_testlauncher(cdlclient) if __name__ == "__main__": run() DataLabSimpleClient-0.10.1/cdlclient/tests/connect_dialog.py000066400000000000000000000013541463263342600240330ustar00rootroot00000000000000# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file. """ DataLab Remote client connection dialog example """ # guitest: show from guidata.qthelpers import qt_app_context from qtpy import QtWidgets as QW from cdlclient import SimpleRemoteProxy from cdlclient.widgets import ConnectionDialog def test_dialog(): """Test connection dialog""" proxy = SimpleRemoteProxy(autoconnect=False) with qt_app_context(): dlg = ConnectionDialog(proxy.connect) if dlg.exec(): QW.QMessageBox.information(None, "Connection", "Successfully connected") else: QW.QMessageBox.critical(None, "Connection", "Connection failed") if __name__ == "__main__": test_dialog() DataLabSimpleClient-0.10.1/cdlclient/tests/get_object_dialog.py000066400000000000000000000021031463263342600245000ustar00rootroot00000000000000# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file. """ DataLab Remote client get object dialog example """ # guitest: show from guidata.qthelpers import qt_app_context from cdlclient import SimpleRemoteProxy from cdlclient.widgets import GetObjectDialog def test_dialog(): """Test connection dialog""" proxy = SimpleRemoteProxy() with qt_app_context(): # 1. Select an image or signal object dlg = GetObjectDialog(None, proxy) if dlg.exec(): obj = proxy.get_object(dlg.get_current_object_uuid()) print(str(obj)) # 2. Select a signal object only dlg = GetObjectDialog(None, proxy, panel="signal") if dlg.exec(): obj = proxy.get_object(dlg.get_current_object_uuid()) print(str(obj)) # 3. Select an image object only dlg = GetObjectDialog(None, proxy, panel="image") if dlg.exec(): obj = proxy.get_object(dlg.get_current_object_uuid()) print(str(obj)) if __name__ == "__main__": test_dialog() DataLabSimpleClient-0.10.1/cdlclient/tests/imagefile_to_code.py000066400000000000000000000025211463263342600244760ustar00rootroot00000000000000# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file. """Convert PNG image to Python code""" # guitest: skip import os import os.path as osp from guidata.qthelpers import qt_app_context from qtpy import QtCore as QC from qtpy import QtGui as QG from qtpy import QtWidgets as QW from cdlclient.config import MOD_NAME, MOD_PATH from cdlclient.qthelpers import imagefile_to_python_module RES_PATH = osp.join(MOD_PATH, os.pardir, "resources") def test_conv(filename: str, destmod: str) -> None: """Test image to code conversion Args: filename: image filename destmod: destination module name """ with qt_app_context(exec_loop=True): widget = QW.QWidget() vlayout = QW.QVBoxLayout() widget.setLayout(vlayout) label1 = QW.QLabel() label1.setPixmap(QG.QPixmap(filename)) label2 = QW.QLabel() imagefile_to_python_module(filename, destmod) mod = __import__(f"{MOD_NAME}.widgets.{destmod}", fromlist=[destmod]) pixmap = QG.QPixmap() pixmap.loadFromData(QC.QByteArray.fromBase64(mod.DATA)) label2.setPixmap(pixmap) vlayout.addWidget(label1) vlayout.addWidget(label2) widget.show() if __name__ == "__main__": test_conv(osp.join(RES_PATH, "DataLab-Banner-200.png"), "datalab_banner") DataLabSimpleClient-0.10.1/cdlclient/tests/remoteclient_app.py000066400000000000000000000173021463263342600244150ustar00rootroot00000000000000# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file. """ DataLab Remote client application test """ # pylint: disable=invalid-name # Allows short reference names like x, y, ... # pylint: disable=duplicate-code # guitest: show from __future__ import annotations import functools from contextlib import contextmanager import numpy as np from guidata.env import execenv from guidata.qthelpers import qt_app_context, qt_wait from qtpy import QtWidgets as QW from cdlclient import SimpleRemoteProxy from cdlclient.config import _ from cdlclient.tests.remoteclient_base import AbstractClientWindow from cdlclient.tests.remoteclient_unit import multiple_commands from cdlclient.widgets import ConnectionDialog, GetObjectDialog APP_NAME = "Remote client test" def try_send_command(): """Try and send command to DataLab application remotely""" def try_send_command_decorator(func): """Try... except... decorator""" @functools.wraps(func) def method_wrapper(*args, **kwargs): """Decorator wrapper function""" self: HostWindow = args[0] # extracting 'self' from method arguments output = None try: output = func(*args, **kwargs) except ConnectionRefusedError: self.cdl = None message = "🔥 Connection refused 🔥 (server is not ready?)" self.host.log(message) QW.QMessageBox.critical(self, APP_NAME, message) return output return method_wrapper return try_send_command_decorator class HostWindow(AbstractClientWindow): """Test main view""" PURPOSE = _("This the client application, which connects to DataLab.") INIT_BUTTON_LABEL = _("Connect to DataLab") SIG_TITLES = ("Oscilloscope", "Digitizer", "Radiometer", "Voltmeter", "Sensor") IMA_TITLES = ( "Camera", "Streak Camera", "Image Scanner", "Laser Beam Profiler", "Gated Imaging Camera", ) def init_cdl(self): """Open DataLab test""" if self.cdl is None: self.cdl = SimpleRemoteProxy(autoconnect=False) connect_dlg = ConnectionDialog(self.cdl.connect, self) ok = connect_dlg.exec() if ok: self.host.log("✨ Initialized DataLab connection ✨") self.host.log(f" Communication port: {self.cdl.port}") self.host.log(" List of exposed methods:") for name in self.cdl.get_method_list(): self.host.log(f" {name}") else: self.cdl = None self.host.log("🔥 Connection refused 🔥 (server is not ready?)") @try_send_command() def close_cdl(self): """Close DataLab window""" if self.cdl is not None: self.cdl.close_application() self.host.log("🎬 Closed DataLab!") self.cdl = None def add_additional_buttons(self): """Add additional buttons""" add_btn = self.host.add_button add_btn(_("Execute multiple commands"), self.exec_multiple_cmd, 10) add_btn(_("Get object titles"), self.get_object_titles, 10) add_btn(_("Get object uuids"), self.get_object_uuids, 10) add_btn(_("Get object"), self.get_object) add_btn(_("Get object using dialog box"), self.get_object_dialog) @try_send_command() def exec_multiple_cmd(self): """Execute multiple commands in DataLab""" if self.cdl is not None: self.host.log("Starting command sequence...") multiple_commands(self.cdl) self.host.log("...end") @try_send_command() def get_object_titles(self): """Get object (signal/image) titles for current panel""" if self.cdl is not None: self.host.log("Object titles:") titles = self.cdl.get_object_titles() if titles: for name in titles: self.host.log(f" {name}") else: self.host.log(" Empty.") @try_send_command() def get_object_uuids(self): """Get object (signal/image) uuids for current panel""" if self.cdl is not None: self.host.log("Object uuids:") uuids = self.cdl.get_object_uuids() if uuids: for uuid in uuids: self.host.log(f" {uuid}") else: self.host.log(" Empty.") @try_send_command() def get_object(self): """Get object (signal/image) at index for current panel""" if self.cdl is not None: titles = self.cdl.get_object_titles() if titles: obj = self.cdl.get_object() self.host.log(f"Object '{obj.title}'") self.host.log(str(obj)) else: self.host.log("🏴‍☠️ Object list is empty!") @try_send_command() def get_object_dialog(self): """Get object (signal/image) using dialog box""" if self.cdl is not None: dialog = GetObjectDialog(self, self.cdl) if dialog.exec(): uuid = dialog.get_current_object_uuid() obj = self.cdl.get_object(uuid) self.host.log(f"Object '{obj.title}'") self.host.log(str(obj)) def add_signals(self): """Add signals to DataLab""" if self.cdl is not None: x = np.linspace(0, 10, 1000) for title, y in ( ("Sinus", np.sin(x)), ("Cosinus", np.cos(x)), ("Tan", np.tan(x)), ): self.cdl.add_signal(title, x, y) self.host.log(f"Added signal: {title}") def add_images(self): """Add images to DataLab""" if self.cdl is not None: for title, z in ( ("Zeros", np.zeros((100, 100))), ("Ones", np.ones((100, 100))), ("Random", np.random.random((100, 100))), ): self.cdl.add_image(title, z) self.host.log(f"Added image: {title}") @try_send_command() def remove_all(self): """Remove all objects from DataLab""" if self.cdl is not None: self.cdl.reset_all() self.host.log("Removed all objects") @contextmanager def qt_wait_print(dt: float, message: str): """Wait and print message""" qt_wait(dt) execenv.print(f"{message}...", end="") yield execenv.print("OK") def test_remote_client(): """Remote client test""" with qt_app_context(exec_loop=True): window = HostWindow() window.resize(800, 800) window.show() dt = 1 if execenv.unattended: qt_wait(2) window.init_cdl() with qt_wait_print(dt, "Executing multiple commands"): window.exec_multiple_cmd() with qt_wait_print(dt, "Raising DataLab window"): window.raise_cdl() with qt_wait_print(dt, "Getting object titles"): window.get_object_titles() with qt_wait_print(dt, "Getting object uuids"): window.get_object_uuids() with qt_wait_print(dt, "Getting object"): window.cdl.select_objects([1]) window.get_object() with qt_wait_print(dt, "Adding signals"): window.add_signals() with qt_wait_print(dt, "Adding images"): window.add_images() with qt_wait_print(dt, "Removing all objects"): window.remove_all() with qt_wait_print(dt, "Closing DataLab"): window.close_cdl() if __name__ == "__main__": test_remote_client() DataLabSimpleClient-0.10.1/cdlclient/tests/remoteclient_base.py000066400000000000000000000112151463263342600245440ustar00rootroot00000000000000# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file. """ DataLab Remote client test base classes """ # pylint: disable=invalid-name # Allows short reference names like x, y, ... # guitest: skip import abc from guidata.qthelpers import get_std_icon, win32_fix_title_bar_background from guidata.widgets.codeeditor import CodeEditor from qtpy import QtWidgets as QW from cdlclient.config import _ class HostWidget(QW.QWidget): """Host widget: menu with action buttons, log viewer""" def __init__(self, parent=None): super().__init__(parent) self.button_layout = QW.QVBoxLayout() self.logwidget = CodeEditor(self) self.logwidget.setMinimumWidth(500) grid_layout = QW.QGridLayout() grid_layout.addLayout(self.button_layout, 0, 0) grid_layout.addWidget(self.logwidget, 0, 1) self.setLayout(grid_layout) def log(self, message): """Log message""" self.logwidget.appendPlainText(message) def add_spacing(self, spacing: int) -> None: """Add spacing to button box""" self.button_layout.addSpacing(spacing) def add_label(self, text: str) -> None: """Add label to button box""" self.button_layout.addWidget(QW.QLabel(text)) def add_widget(self, obj: QW.QWidget, spacing_before: int = 0) -> None: """Add widget (QWidget) to button box""" if spacing_before > 0: self.add_spacing(spacing_before) self.button_layout.addWidget(obj) def add_button(self, title, slot, spacing_before=0, icon=None): """Add button""" btn = QW.QPushButton(title) if icon is not None: btn.setIcon(get_std_icon(icon)) btn.clicked.connect(lambda _checked=False: slot()) self.add_widget(btn, spacing_before=spacing_before) return btn def add_stretch(self): """Add stretch to button box""" self.button_layout.addStretch() class AbstractClientWindowMeta(type(QW.QMainWindow), abc.ABCMeta): """Mixed metaclass to avoid conflicts""" class AbstractClientWindow(QW.QMainWindow, metaclass=AbstractClientWindowMeta): """Abstract client window, to embed DataLab or connect to it""" PURPOSE = None INIT_BUTTON_LABEL = None SIG_TITLES = ("Oscilloscope", "Digitizer", "Radiometer", "Voltmeter", "Sensor") IMA_TITLES = ( "Camera", "Streak Camera", "Image Scanner", "Laser Beam Profiler", "Gated Imaging Camera", ) def __init__(self): super().__init__() win32_fix_title_bar_background(self) self.setWindowTitle(_("Host application")) self.setWindowIcon(get_std_icon("ComputerIcon")) self.cdl = None # CDLMainWindow instance self.host = HostWidget(self) self.setCentralWidget(self.host) self.setup_window() self.host.add_stretch() self.index_sigtitle = -1 self.index_imatitle = -1 @property def sigtitle(self): """Return current signal title index""" self.index_sigtitle = idx = (self.index_sigtitle + 1) % len(self.SIG_TITLES) return self.SIG_TITLES[idx] @property def imatitle(self): """Return current image title index""" self.index_imatitle = idx = (self.index_imatitle + 1) % len(self.IMA_TITLES) return self.IMA_TITLES[idx] def setup_window(self): """Setup window""" self.host.add_label(self.PURPOSE) add_btn = self.host.add_button add_btn(self.INIT_BUTTON_LABEL, self.init_cdl, 10, "DialogApplyButton") add_btn(_("Raise window"), self.raise_cdl, 0, "FileDialogToParent") self.add_additional_buttons() add_btn(_("Add signal objects"), self.add_signals, 10, "CommandLink") add_btn(_("Add image objects"), self.add_images, 0, "CommandLink") add_btn(_("Remove all objects"), self.remove_all, 5, "MessageBoxWarning") add_btn(_("Close DataLab"), self.close_cdl, 10, "DialogCloseButton") def add_additional_buttons(self): """Add additional buttons""" @abc.abstractmethod def init_cdl(self): """Open DataLab test""" def raise_cdl(self): """Raise DataLab window""" if self.cdl is not None: self.cdl.raise_window() self.host.log("=> Raised DataLab window") @abc.abstractmethod def close_cdl(self): """Close DataLab window""" @abc.abstractmethod def add_signals(self): """Add signals to DataLab""" @abc.abstractmethod def add_images(self): """Add images to DataLab""" @abc.abstractmethod def remove_all(self): """Remove all objects from DataLab""" DataLabSimpleClient-0.10.1/cdlclient/tests/remoteclient_unit.py000066400000000000000000000045751463263342600246240ustar00rootroot00000000000000# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file. """ DataLab Remote client unit test """ # pylint: disable=invalid-name # Allows short reference names like x, y, ... # pylint: disable=duplicate-code # guitest: skip from __future__ import annotations import os.path as osp import tempfile import time from collections.abc import Generator from contextlib import contextmanager import numpy as np from guidata.env import execenv from plotpy.builder import make from cdlclient.remote import SimpleRemoteProxy @contextmanager def temporary_directory() -> Generator[str, None, None]: """Create a temporary directory and clean-up afterwards""" tmp = tempfile.TemporaryDirectory() # pylint: disable=consider-using-with try: yield tmp.name finally: try: tmp.cleanup() except (PermissionError, RecursionError): pass def multiple_commands(remote: SimpleRemoteProxy): """Execute multiple XML-RPC commands""" with temporary_directory() as tmpdir: x = np.linspace(-10, 10, 1000) y = np.sin(x) remote.add_signal("tutu", x, y) z = np.random.rand(200, 200) remote.add_image("toto", z) rect = make.annotated_rectangle(100, 100, 200, 200, title="Test") area = rect.get_rect() remote.add_annotations_from_items([rect]) uuid = remote.get_sel_object_uuids()[0] items = remote.get_object_shapes() assert len(items) == 1 and items[0].get_rect() == area execenv.print("OK") remote.add_label_with_title(f"Image uuid: {uuid}") remote.select_groups([1]) remote.select_objects([uuid]) remote.delete_metadata() fname = osp.join(tmpdir, osp.basename("remote_test.h5")) remote.save_to_h5_file(fname) remote.reset_all() remote.open_h5_files([fname], True, False) remote.import_h5_file(fname, True) remote.set_current_panel("signal") assert remote.get_current_panel() == "signal" remote.calc("log10") remote.calc("fft") time.sleep(2) # Avoid permission error when trying to clean-up temporary files def test(): """Remote client test""" remote = SimpleRemoteProxy() execenv.print("Executing multiple commands...", end="") multiple_commands(remote) execenv.print("OK") if __name__ == "__main__": test() DataLabSimpleClient-0.10.1/cdlclient/widgets/000077500000000000000000000000001463263342600210125ustar00rootroot00000000000000DataLabSimpleClient-0.10.1/cdlclient/widgets/__init__.py000066400000000000000000000006371463263342600231310ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ DataLab Simple Client Widgets ----------------------------- This module requires `guidata`_ and `qtpy`_ packages. .. _guidata: https://pypi.org/project/guidata/ .. _qtpy: https://pypi.org/project/QtPy/ """ # pylint: disable=unused-import from cdlclient.widgets.connection import ConnectionDialog # noqa: F401 from cdlclient.widgets.objectdialog import GetObjectDialog # noqa: F401 DataLabSimpleClient-0.10.1/cdlclient/widgets/connection.py000066400000000000000000000104771463263342600235340ustar00rootroot00000000000000# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file. """ Module providing a connection dialog box for the DataLab proxy client """ from __future__ import annotations import time from collections.abc import Callable from guidata.qthelpers import ( get_std_icon, qt_app_context, win32_fix_title_bar_background, ) from qtpy import QtCore as QC from qtpy import QtGui as QG from qtpy import QtWidgets as QW from cdlclient.config import _ from cdlclient.qthelpers import svgtext_to_icon from cdlclient.widgets import datalab_banner, svg_icons class ConnectionThread(QC.QThread): """DataLab Connection thread""" SIG_CONNECTION_OK = QC.Signal() SIG_CONNECTION_KO = QC.Signal() def __init__(self, connect_callback: Callable, parent: QC.QObject = None) -> None: super().__init__(parent) self.connect_callback = connect_callback def run(self) -> None: """Run thread""" try: self.connect_callback() self.SIG_CONNECTION_OK.emit() except ConnectionRefusedError: self.SIG_CONNECTION_KO.emit() class ConnectionDialog(QW.QDialog): """DataLab Connection dialog Args: connect_callback: Callback function to connect to DataLab server parent: Parent widget. Defaults to None. """ def __init__(self, connect_callback: Callable, parent: QW.QWidget = None) -> None: super().__init__(parent) win32_fix_title_bar_background(self) self.setWindowTitle(_("Connection to DataLab")) self.setWindowIcon(svgtext_to_icon(svg_icons.DATALAB)) self.resize(300, 50) self.host_label = QW.QLabel() pixmap = QG.QPixmap() pixmap.loadFromData(QC.QByteArray.fromBase64(datalab_banner.DATA)) self.host_label.setPixmap(pixmap) self.host_label.setAlignment(QC.Qt.AlignCenter) self.progress_bar = QW.QProgressBar() self.progress_bar.setRange(0, 0) status = QW.QWidget() status_layout = QW.QHBoxLayout() status.setLayout(status_layout) self.status_label = QW.QLabel(_("Waiting for connection...")) self.status_icon = QW.QLabel() status_layout.addWidget(self.status_icon) status_layout.addWidget(self.status_label) status_layout.setContentsMargins(0, 0, 0, 0) status_layout.addStretch() layout = QW.QVBoxLayout() layout.addWidget(self.host_label) layout.addWidget(self.progress_bar) layout.addWidget(status) self.setLayout(layout) self.thread = ConnectionThread(connect_callback) self.thread.SIG_CONNECTION_OK.connect(self.__on_connection_successful) self.thread.SIG_CONNECTION_KO.connect(self.__on_connection_failed) button_box = QW.QDialogButtonBox(QW.QDialogButtonBox.Cancel) button_box.rejected.connect(self.reject) layout.addWidget(button_box) def __set_status_icon(self, name: str) -> None: """Set status icon with standard Qt icon name""" self.status_icon.setPixmap(QG.QPixmap(get_std_icon(name).pixmap(24))) def __connect_to_server(self) -> None: """Connect to server""" self.progress_bar.setRange(0, 0) self.__set_status_icon("BrowserReload") self.status_label.setText(_("Connecting to server...")) self.thread.start() def __on_connection_successful(self) -> None: """Connection successful""" self.progress_bar.setRange(0, 1) self.progress_bar.setValue(1) self.__set_status_icon("DialogApplyButton") self.status_label.setText(_("Connection successful!")) QC.QTimer.singleShot(1000, self.accept) def __on_connection_failed(self) -> None: """Connection failed""" self.progress_bar.setRange(0, 1) self.progress_bar.setValue(1) self.__set_status_icon("MessageBoxCritical") self.status_label.setText(_("Connection failed.")) QC.QTimer.singleShot(2000, self.reject) def exec(self) -> int: """Execute dialog""" self.__connect_to_server() return super().exec() if __name__ == "__main__": def fake_connect(): """Fake connection""" time.sleep(5) # raise ConnectionRefusedError("Connection refused") with qt_app_context(): dlg = ConnectionDialog(fake_connect) dlg.exec() DataLabSimpleClient-0.10.1/cdlclient/widgets/datalab_banner.py000066400000000000000000000257461463263342600243170ustar00rootroot00000000000000# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file. # pylint: skip-file DATA = b"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" DataLabSimpleClient-0.10.1/cdlclient/widgets/objectdialog.py000066400000000000000000000217101463263342600240130ustar00rootroot00000000000000# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file. """ Object dialog ------------- This module provides a dialog box to select an object (signal or image) from a list. .. autoclass:: GetObjectDialog :members: """ # pylint: disable=invalid-name # Allows short reference names like x, y, ... from __future__ import annotations import os from collections.abc import Iterator from typing import TYPE_CHECKING from qtpy import QtCore as QC from qtpy import QtGui as QG from qtpy import QtWidgets as QW from cdlclient.config import _ from cdlclient.qthelpers import block_signals, svgtext_to_icon from cdlclient.widgets import datalab_banner, svg_icons if TYPE_CHECKING: # pragma: no cover from cdlclient import SimpleRemoteProxy class SimpleObjectTree(QW.QTreeWidget): """Base object handling panel list widget, object (sig/ima) lists""" SIG_ITEM_DOUBLECLICKED = QC.Signal(str) SIG_CONTEXT_MENU = QC.Signal(QC.QPoint) def __init__(self, parent: QW.QWidget) -> None: self.__obj_uuids: list[list[str]] = [] self.__obj_titles: list[list[str]] = [] self.__grp_titles: list[str] = [] self.__panel_name: str = "" super().__init__(parent) self.setHeaderHidden(True) self.setColumnCount(1) self.setAlternatingRowColors(True) self.itemDoubleClicked.connect(self.item_double_clicked) def __str__(self) -> str: """Return string representation""" textlist = [] for tl_index in range(self.topLevelItemCount()): tl_item = self.topLevelItem(tl_index) textlist.append(tl_item.text(0)) for index in range(tl_item.childCount()): textlist.append(" " + tl_item.child(index).text(0)) return os.linesep.join(textlist) def initialize(self, proxy: SimpleRemoteProxy) -> None: """Initialize tree with objects, using proxy""" grp_titles, obj_uuids, obj_titles = proxy.get_group_titles_with_object_infos() self.__grp_titles = grp_titles self.__obj_uuids = obj_uuids self.__obj_titles = obj_titles self.__panel_name = proxy.get_current_panel() self.populate_tree() def iter_items( self, item: QW.QTreeWidgetItem | None = None ) -> Iterator[QW.QTreeWidgetItem]: """Recursively iterate over all items""" if item is None: for index in range(self.topLevelItemCount()): yield from self.iter_items(self.topLevelItem(index)) else: yield item for index in range(item.childCount()): yield from self.iter_items(item.child(index)) def get_item_from_id(self, item_id) -> QW.QTreeWidgetItem: """Return QTreeWidgetItem from id (stored in item's data)""" for item in self.iter_items(): if item.data(0, QC.Qt.UserRole) == item_id: return item return None def get_current_item_id(self, object_only: bool = False) -> str | None: """Return current item id""" item = self.currentItem() if item is not None and (not object_only or item.parent() is not None): return item.data(0, QC.Qt.UserRole) return None def set_current_item_id(self, uuid: str, extend: bool = False) -> None: """Set current item by id""" item = self.get_item_from_id(uuid) if extend: self.setCurrentItem(item, 0, QC.QItemSelectionModel.Select) else: self.setCurrentItem(item) @staticmethod def __update_item( item: QW.QTreeWidgetItem, number: int, prefix: str, title: str, uuid: str ) -> None: """Update item""" item.setText(0, f"{prefix}{number:03d}: {title}") item.setData(0, QC.Qt.UserRole, uuid) def populate_tree(self) -> None: """Populate tree with objects""" uuid = self.get_current_item_id() with block_signals(widget=self, enable=True): self.clear() for grp_idx, (grp_title, obj_uuids, obj_titles) in enumerate( zip(self.__grp_titles, self.__obj_uuids, self.__obj_titles) ): self.add_group_item(grp_idx + 1, grp_title, obj_uuids, obj_titles) if uuid is not None: self.set_current_item_id(uuid) def __add_to_group_item( self, number: int, obj_uuid: str, obj_title: str, group_item: QW.QTreeWidgetItem ) -> None: """Add object to group item""" item = QW.QTreeWidgetItem() prefix = self.__panel_name[0] svgtext = svg_icons.SIGNAL if prefix == "s" else svg_icons.IMAGE item.setIcon(0, svgtext_to_icon(svgtext)) self.__update_item(item, number, prefix, obj_title, obj_uuid) group_item.addChild(item) def add_group_item( self, number: int, title: str, obj_uuids: list[str], obj_titles: list[str] ) -> None: """Add group item""" group_item = QW.QTreeWidgetItem() group_item.setIcon(0, svgtext_to_icon(svg_icons.GROUP)) self.__update_item(group_item, number, "g", title, "") self.addTopLevelItem(group_item) group_item.setExpanded(True) for obj_idx, (obj_uuid, obj_title) in enumerate(zip(obj_uuids, obj_titles)): self.__add_to_group_item(obj_idx + 1, obj_uuid, obj_title, group_item) def item_double_clicked(self, item: QW.QTreeWidgetItem) -> None: """Item was double-clicked: open a pop-up plot dialog""" if item.parent() is not None: oid = item.data(0, QC.Qt.UserRole) self.SIG_ITEM_DOUBLECLICKED.emit(oid) def contextMenuEvent(self, event: QG.QContextMenuEvent) -> None: # pylint: disable=C0103 """Override Qt method""" self.SIG_CONTEXT_MENU.emit(event.globalPos()) class GetObjectDialog(QW.QDialog): """Get object dialog box Args: parent: Parent widget proxy: Remote proxy panel: Panel to retrieve objects from ('signal', 'image' or None for both) title: Dialog title """ def __init__( self, parent: QW.QWidget, proxy: SimpleRemoteProxy, panel: str | None = None, title: str | None = None, ) -> None: super().__init__(parent) assert panel in (None, "signal", "image") self.__proxy = proxy self.__current_object_uuid: str | None = None self.setWindowTitle(_("Select object") if title is None else title) self.setWindowIcon(svgtext_to_icon(svg_icons.DATALAB)) vlayout = QW.QVBoxLayout() self.setLayout(vlayout) logo_label = QW.QLabel() pixmap = QG.QPixmap() pixmap.loadFromData(QC.QByteArray.fromBase64(datalab_banner.DATA)) logo_label.setPixmap(pixmap) logo_label.setAlignment(QC.Qt.AlignCenter) panelgroup = None if panel is None: panelgroup = QW.QWidget() panellayout = QW.QHBoxLayout() panellayout.setContentsMargins(0, 0, 0, 0) # panellayout.setAlignment(QC.Qt.AlignCenter) panelgroup.setLayout(panellayout) panelcombo = QW.QComboBox() panelcombo.addItem(svgtext_to_icon(svg_icons.SIGNAL), _("Signals")) panelcombo.addItem(svgtext_to_icon(svg_icons.IMAGE), _("Images")) if proxy.get_current_panel() == "image": panelcombo.setCurrentIndex(1) panelcombo.currentIndexChanged.connect(self.__change_panel) panellabel = QW.QLabel(_("Active panel:")) panellayout.addWidget(panellabel) panellayout.addWidget(panelcombo) panellayout.setStretch(1, 1) else: self.__proxy.set_current_panel(panel) self.tree = SimpleObjectTree(parent) self.tree.initialize(proxy) self.tree.SIG_ITEM_DOUBLECLICKED.connect(lambda oid: self.accept()) self.tree.itemSelectionChanged.connect(self.__current_object_changed) vlayout.addWidget(logo_label) vlayout.addSpacing(10) if panelgroup is not None: vlayout.addWidget(panelgroup) vlayout.addWidget(self.tree) bbox = QW.QDialogButtonBox(QW.QDialogButtonBox.Ok | QW.QDialogButtonBox.Cancel) bbox.accepted.connect(self.accept) bbox.rejected.connect(self.reject) self.ok_btn = bbox.button(QW.QDialogButtonBox.Ok) vlayout.addSpacing(10) vlayout.addWidget(bbox) # Update OK button state: self.__current_object_changed() def __change_panel(self, index: int) -> None: """Change panel""" self.__proxy.set_current_panel("signal" if index == 0 else "image") self.tree.initialize(self.__proxy) self.__current_object_changed() def __current_object_changed(self) -> None: """Item selection has changed""" self.__current_object_uuid = self.tree.get_current_item_id() self.ok_btn.setEnabled(bool(self.__current_object_uuid)) def get_current_object_uuid(self) -> str: """Return current object uuid""" return self.__current_object_uuid DataLabSimpleClient-0.10.1/cdlclient/widgets/svg_icons.py000066400000000000000000001436131463263342600233660ustar00rootroot00000000000000# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file. """Module containing SVG icons for the application""" DATALAB = """ image/svg+xml """ SIGNAL = """ """ IMAGE = """ """ GROUP = """ """ DataLabSimpleClient-0.10.1/doc/000077500000000000000000000000001463263342600161505ustar00rootroot00000000000000DataLabSimpleClient-0.10.1/doc/_static/000077500000000000000000000000001463263342600175765ustar00rootroot00000000000000DataLabSimpleClient-0.10.1/doc/_static/DataLab-Frontpage.png000066400000000000000000002170741463263342600235320ustar00rootroot00000000000000PNG  IHDRfr pHYsaaøtEXtSoftwarewww.inkscape.org< IDATx鏝y3}-J[l-pv\4MHj$hWIE&HaImgǑ5.wq'l} [nH-CRuA9s!xv;II~'o&ّ;Ϻ$/%$%ln7|5ɧodMI>hۿ?i޿mѓ7gv{2\d]YF}@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @Y͚m25=LM&I͎JoO3\l:?SG308C04ɌMdzf63Z$I#tvv3wgyYbA֬\dlhWoo"põXNα9qv8.^Υ\TFǦryl:cәMjv4әxaO.ɪfl^4;7-ϦuKda}h_rj3|y2GO 詡>1cʙ<7kzf,Ύ˳sӊܴ"[7,i6Z*Ag308WNgс.^L̰`^W/=;d%Y߷0ݝ.+bjz6#9vj(Ox*>q4u}kj6[6?sK{lZ$}d~oW::Ex-"0Zv'<|9rrFOt7wȝ;tqozf~̩s~@+3?n͇ޘy"c"0'gfcGżtbF.Oef&HOw3{nNgA&@_9yn$37z7Hve}ߢܱ/o{ܱ:=V,MPVgG'ǏŃsyb:owF#tgٳkM>rϦleyvltl*/<}X<~F4'V-wߺ:sC{l\$͎=\W"pvɃ͓/kN5ɖwnȮ-+f=\7"pU'grG׏ɡ7'IV}̭V>%zw6_n AxSfg[̋<\c][0v;&21ud/{wϿֿ,K w4A(mű|x>#cUj$βŽΓ3kl4E/|[ ͎tt4p= @l}lOfrzF4'6[ݹy#o3Kjfwܒ xZ_|nn'?w&ϼt6/+$nfeuӊش<;6ȶ[ x̶22:gGұ y3:>}Uk4]ٸnI/];V[Wg󺥙?늳x|x">wtt4ҿzq~SlŽivt$I. ȳ'9vz(˻^X2/mYZvΖ ˲zM\ 3TG&s|BydyDFƮ|Hve]ߢܴ"ܳ)}]-}S N᧏,GH߲·ܒGƦ'晗̅ LdjU:ظ<[s[ҿfq:V,pgHl;cӹ<>G.#ϜȑCifV,Ws5޳1oeKcx2~/}~UOW3_ջKܚ}ѯj|{Ogsy|:X{iݒtw5$EVN_}PC9rj(SWWu6;gך_zWxtw6j+N]ݥߪFO߿#ݹmՄݧ^ɟyzߙL͎Fї?ҿfUܔExm|219ɩ\HwGo/oK-LOwgUw8?Gs5:[ƵK=nɎ|_WZLNfxt"Ox:Ծ3wuvcۜKKDPwKy{h /eht2cWOh${ߖ|ٽ/q' v;co_{:_sf5=Ȓ=Y|A֯Zd'33<;=G;A'ɧݞ/}l_6Nvv. p,Op:_ʩs#Lum>;h4tqo>yMپiyv幍F#t3ٙy04~fMϴ208 C9zr084IّVg39=əLL{@8~)W15A!Fǧ̾yɣyf=9kIWgG6]_{7eݪ?qUDjtvvaܼni>\wMd|rospFǧjp=6jsXyDW/ن%ݝٽ/_mnM*.:t8^9P۟F#yt7t^8x~NVchOdzuGBPv|ٟ99OW?y{>rϦ7u?vvɩ>|8/|0/]z^x1/ݛV-7ߚzy`^Wß<23 oϖKdUQ:n7>1?obbڲ2+wrU G<1U/^}ro#uww3wX?sU|g_ޝ\ MS8 hZx.=q,'<.οgt6Ny8'NdmWR۟VW6tu5ӿfqݟ_,^_Ƚ{n?)+$Ff|#9u~$Flqo~{綵Y8sP^z@N?ѴiZy׮{tu5qίIrH|]ͬ[o]eKe^Ogdzf6cS84s/X&~͎F֮\+wfݪi6}IPV^ʑsaǢOݑ՟{(񉉜=s6ٳ2>>~U矝`8پ}[zzz^sFFG#[̊% ]a]nJW#$V[\T. x epd-o|ɼ|r־tp= 61>9o<|0/:ə+?*,^Н޳1wG/MG1& ĉ9zXΟ[kT˙me,Z uK2֬X:/~HxT. v_(|ڿ_w[ IDAT+0_TF'382щ Lfpd7ry2cSalܻږy= 7oSӳ9~v8_}p.Gfm[V⮬Yr9r4?>{"N ĥ=]ٵ6|leyiWdтlX_m2ry*#?Qh|T&3>1əLNdzvW/ʧۑ}7 3AG'󽧎kFҿfq>mٳkmy###9pP>9m||3wߺ&eˆÇrLNߊkX͎,]ԛz_v;3\ȥ\ɌMdjz6}Cw_y`. pm乑|9/{޵6жv&''o9tpl䇻FRƲcZ*==ݯF#YbA;<ɜp9[/-k_ˊFYbAVXp sAPܥvjz:Ϝ {3Jv'rLLLfMٸic.xݩ@\5AnRvr)O?;'j=mƫZVr\tiN3339?0  guYݷ*Ͽѣ;79tRNں*w߶&}˯P{%gΜYުޝqw~Qu_]}_n$$x/3CΩ Vv "#>ސwwɒFsHD$H$A@軫*31' T'*Y||>O*"lH8,B!B$PA OHQulCW9 S"c㌎S}_KVczzT*L6Z xeB !B!b$PA]0>ݐiОNb+lfrjGf7f_[\f|b0p&n!B!B,OE!8LI5U7)YF6cppx|cj5fg  MQUUB!Bk  R' $3uly~B~=s,8~Hl>3g@?--R(B!B\ h@3y^gBk.{߱q&&&)K7STbph (==]t o !B!B4,y>o<6t-yqj:}L6/j5 TŚbDnfB!BѰ$PMss(tw-[hY#/2;7]EAT4MGSUTMC54UCQUX&۶-˲, ۱,ZmmZrds9sx^ pp8L P_sQ!B!ur,DI+A;_;Zr oT0pM p\.\.}ki*j Rob;cc65qzXT(*+_e,ZqklL6(n|~~Ϗzxxt].B!B!nK( рѐG^>ZD2L/>S4LnMQJ(B!% ZHKK7XTZ^lkY~RT+ra&nt}~/>4ojdMx|5ij}JcKW)B!BlF( `RBe|U M#DKOw^.*4L0--bMD%CFPE .m۔eҩTd*E"$/PTU؋4qbHXx5Mo w9ߕB!BqI(DN(׷JKާT*34`0Ȗz{ Bzp8Υ?{lw̯fEŅ_zTUxhoo\.ǹ8:|Vײ,R4lVE!B! h0\Ju>v,lyq:wݵ~LӼ0eA:tBX*Q(]\.Q֨jԪUp '*CJUUQ5M0\.LӍmbFYr=e!aA[[[=dahd5unFD"B!B!" &-Q-etM%\>5"03vҙ,#m&''Jkò3eٹW+qX&z* **--ۻ` n?*^Jww--s'H&Zgle``pO!B!$Pe !7-Lc)|\RH6::ԥв+WD_è7>^r\.|^/^vҩ48ӤәK?Ƕtww oj!B!bH(DTj7%ꣻ-캃\D"yb 4b\7{(|֡`(]d2Y,LKK3>WD!B!, h0bu];閐{sEfS }UUP Zj5y ^ׅcszt^$p yhWCƒ85{ٻUk6Ɉ 8pB_Y㷿q ;!BB4ӥWcVg.~bD\ Y~iݸ̦\K2=ٻԃ@]S5riU?KpMӥ &Ma/-Q/1?~!⠀zqBr SΎ$_,e.%B!D@Q6KTWm-%S*6tKxszTfS\u:> W/ ֨֘&?[:|R(B!@Q6u =n2k〃e95ZӶ2.VK O˷؆ǭ*(B!I(D1tE&_ǪYN=P\co@Q Sl{qrMA۶. -۹"Dme׃ǚMjS(*RRFb͗Iʤeb2c=,tNgKLM򝧶iݪB!I(DQ߼@^t`lJ*MPdỺ֭W,JբRJrŢ\QbF6_&W/Vɗ Uҹ\TTLL~acbqb;躶3ÅB!h$Aې(thk32I痯P4M&˕ w7UUq0t{ww;#iALq+5?\ϖ0&B!bskB܆E5@G'.Ε0LM(J%,+5XoFk'p'z=DnisTؙ)pT}FB!Blb( ѠZt4__IԖ`1 㺞F@Q w[3?z |` ~gr GOO?9\e˪B!BMKE!T[OgKӥ|%+Lt^WE!PZ[:BhнO{9~nJA{*B!ۻ ql zWH8"+}~>t:}{]TUCUU4MEu~?hH8LKs3MБZ4Me϶^ӥ_!_1lގ0=![q@!B!n$9;) 5{W+oHfSmE χ=~馩)JKs3Ěx-CixCEE!B!  5ct_sXlS9:i ( 7t-7]ijj9FkK3P4д*+[J%4Mk)r߽|k] [jD'ʓ,ZE,B!FE!\kQΌ$1R#EE׫sśM47hňD¸Ma`\躎fYW˲LNM23X,cvn> q4U)? U%+80#CܳDPQ!B$PiJ[Sl@q"ޘG(Z@4M"0hp8L xpKϿm "Yf3$)$Pbp:}a^Tִ}&_ad2MP5 q\TR4M~`p&FQ~ߢmSTfsdYRsd|Dbdd4bkG!nEQؿ,tԊK5 'vߒ'\SD\dl@*S&)̔HfJ ʕ՚MfQE|Pc4&&3ݴ6wt R̟^7BIvPUlLl\\H*[".̔IfK55JզZm҉.  ŦpMo b5'oh(WI' $2ϔHf$e,{MS1]9>ǽ気P$7|MtB\ $|nz'ř˭JR34b϶nw\¡U=f}|ucss31!bJX/NǙS(;fY,m/9LMU^er6G<'WX9Ofz?2@$F3TEj;t:9cx<$C)'U'(DCnzCkb]BZ>!sc;LxBi׵=D,E[ǒB9,^,.26Y~J4g4Z&ָya}Te2#.1:h )'RLY IDATTv144Z|l펰-Q/͑ɸVrt"Oba/^:{VKP_NhW yk /ו+ӉNz 3jjJX"J259 lvMP.沔J%^FQU0~1ErH>z{M]i8/IL$|>ّ92u^9̥̥=5*D}ܵGm&]Eǁ/obp|$Á}h}ܚ)jkk}"]wky-Q/UmJrF&_!.28cc|SsRJ.Nf8xd=< 5V-:BW;??He(byO]8N"O:W")18S .|ũ 2<2ǭ}w B~b>;P,/~@QqZ5$v*$%&fr:6/81I:/,R瘈xo==}wto'{h {@[Bl"mgh< '_Ε9vzy>}<^/mm8yj)NJrBl߶-[z1 YζmlۦZ2==Srk\L.'LI(8{G;9YUbT'/ gYj\gg8SjPa;L<l vp`?mM>܆r Yǒ/-qݥ_QZ ȭCeR5TTX-TglNex0{ +Tq,'/o枝m6å6kZSղc%EMUY U_pt*[ ;ZfR,8q!Ή q~Sܳ=9++7d >1AgK>~` ~Lg9ةYxEe::ʑt{{ضCXw˃g9{11T^ͱl[oIgk@Y!8ѩ Vd^?<rځ(M-==WP-t0;5yfLNL2S,Vkju y .]a@WyaR؎ï>5Ho{pSLϽ9N\%/_sq9??xcg9P||+mMMsq[sy/9tlb妟t_fԊ6CiGсܵuSC؎L~$1>mqo\M?NՆ׭ySY6/{=$Lqݧ_ǩ/-1;1V6Z" (}]|ctUʗ*v/>uUattvKJ2Mvۍi8md2 fgHRJryCٶM.cnn {!֋~SбQ^x|mnbW{}|b?MgoղL̩Ys`_d` 8B?C#.Tkd>el*#wu#1w;(¸/j9hCs5Y6dCF8ѻyvwnmcePrvxxKN1>TKm;de2Sy.baBlR( 5=|S||brU6/&898ǽ]WM4 GwWC#7eH&) ds9,\B@\Kz LMMK(6 ÃdrsjhoCV%YMba!)]:7Jj16%)13w*-q4/s^dh"[yT>Ƽ˥T,ۦZO].k}:;w32Oߋk݆݂mۡjٜIp~}dSCtO^4 S\* l*Ulll\HpS'.̒̔M9tkׄDӲTSC{;93ë@a;7J4m\y4֖(S3 S,43qzn2|/>ONL*|;q!t"XCU(UN!~q 1\-Q/ရpMo ֿz:n~j4lǹtZʤs4|DH"]$)Q(UW_5_L/Vo@?{/Fi՚M2S"\{h y׺^~M'9C8uU%jk qq6u<~Df9Tkm+THeder |TDH*[^l‘/'MxXċ5 7dc~"A7S4tL6+ERc.]d2cj6t"L"\ sk8ڡ L)< Dž*K)H(&vgI\5x횦T*MژkeGhG4!x.뢣OXY106P4F+T4?{4&(J= DZ`\~‚9Ύ14hѩ "L|quDZ|_vtxݍ#tO^Z2R}9kg]]< q9tu7pz.3^CiBg zhzm[:B4G}vذmgqZ٤se.Ne8w1XSsyR2ʪNƫ5#sZ6opE\qթ<0?qW]>wn }ćZd_QUjM:Wbx"Å5J_~Lf̃[h5mZ,8q>OJ=p44Gtٳ{w5pvдŗ5ݣԗHjm򳫯 UQl̹$3LTl_1ςpߝ\,b@QMqq6_G2Z]^7rN_) ś jsر$eΧ#|rrj۫Bg=|]ܳ m$f=O.@W0?:)2Ҋ}:ORCUi ϩOes\(/{nUUATZ|.^x|Q/|R]PcW.U+*][0>16Ywǩ}']k 嚙Bl( ®&~Ž5mCl.(0=5M&]W˲,GFعcMM.R ϥJ,dzœ,M8f}WK>q_ّĊK5òl[A8*gGկW5 iV~nY_xQ[ AO{?=<:ϭx-)gz\|靸tSl.&Ls+ѐ<'BWuoZoP1淿q'wnmo^?j{KKQo<:PQ$]Sj ;߼3ó$3U},W-`W⁢(17 JQ6`﯍iEsK3.3I8p@_(Zl}8?7_%T^₩)xv/z#]M9Y]P/Qp4ڛx] ty d6/s?~8߇%Ol0,''U<΃{;9Pn5v*(~kqo3:ç'&*n80:yvߢk#[\8_uD~ss6ۂyZ٪*&HANWY|9Anq?2=rۚi]^/m%LN}hnBsh4Clݫt"t޶ YGqf9Ls4:Jz:wlǶrvz;>L G͞m-nba/^tǁxZwcn%4?}$cK_r~~p = W6pWHMk㽣/~,6l"SLWvvkgه#0/:nB~w^\lN\{im#-,/w]\KSm箝mtF+[x]w#7obx"beͲ958K#3y-B5@Q[2udONL/UbDZ(?l \Uՠ( ]RiŕI7EQ|DzBS4mzqWq3(BgX؃KWW@=O{ՉL<;^\:>;On瑻3be~ݼ0!I8`b4Z,,?yK:[WY)6Njqjh9UoǷ׶/JǭBà O,7Wɉ Ao}m [ހ[n^ԗul ' 9Ό̭8\ A9ۂlSKkOB54QR'/̮V Mx(U{=DvWQ|^/PV:;:ø\C8E$GG-[zho!'ĵr:Mi@ %"zc0f {p}]G;.]kj( Dnyq8EUlN7-Z[rDl,qxUmi -A^xl+?<&ߦiww 6ts OL;^4\JM+\֑alukѩBwkwQ\U/?=?>dOa7rmӎPˡ( x~to^ckwb:8x+^Ɠi!7w]4 =`'3eF&+)UڜCqWQD߲Ov|twvG tŶj*\ Μ=O6Eu^& }z8ԛADCtL"SqRqjh ^]5׭= wr@3Aכ(Ӄc'|y>hǁW߿5tsV IDATqK8eQ,qOIضMRazf꧿ q3ضCsCx'xRtD'Hpq*Û ٙUm n}w{+LKWyv qζ<'!MaOF .(Os1݀1?zb{{EvHeK|vff.bY66ƛ3Zb@KoM&.hsӮ}Ǒ剝,oHЛ&l:ƚ*PݟNZIڝl陞mh4wHU$ x U?cْhK]2)U~?WMUo]C)4kpo?@Q7xƧev>]AcEnT@QQpŷÓ9̻l|^/mTyP^^csr<`۶O{3>~gcbbQl[m4K>2i_*J$?&h 5lf)!_>֊>6ĒY]E}L8MX8~|nٶdz.{燸]͞n柿]n!ӟ~(_x7˟ە̽{`Kt<?aee32^d.)X(+WEQ/=ǎ>15߸ !^Ǐrf.ɓ8"{M_o~?aHJ_,d*3Ms͂JHT~VL5j*|x#篍UK 34e!7u :REQVEY&wqp~j]z˖XtŻ]TЩ(C<|mD"<b0Ѷm8׮3g$t;l)lmNe`_X"> ]+* 2wq`ϝl2]̂ʰiGZXJA}#E\Γ)J{ըi#z٢ƻyD3{o=omS_{ʓh.YWkvm]6|ׁH {phEQ֗ (*PYMݛY/eےw Ι&f8' {.pQCZ ]9|=$& p2׮188D<RJ==$IUlIB<.wbL" RʘɵÌt]\W꠲̷xD3{[T?dY63 ;?\p/QǛxP^-q:t<ĩDž>$e Ydh<'WF/||N?Gqm9c_Q6 (* TG|D{~eexnt$X9k&{9--ed*ͱ1]+bnn7 lccܵߢl6(jB6g¯YJ?Õ)` C_ !p;uGm7;dKg-tQEdiWÓǚh m۠M"aOhJMX3<+*;ؾ+&QEEA ]:ȿSRw>046⋵痢g!ZN;(/_|4㌌ryΝћcBmh,Fի$?(5"۶iو5n31}gkD[%Ϸ[TKeme&c1.ݘ,*KjE( uIRBˡs|-.5{pl k!wmL eG:8u~gMD>վiU(Deф`_[?]U:xt?z]Cjjiij& i@#OPWW8EJmdY&'8' ͮ}8bzzX(ˡkE,[b6b km[rM&fX|9ю*l}'jD( yxhuULlDӼsvuMn*;R'FAٺx>#g&j \qǏ6&m)4M\󏵱_n4! pb-U徂?:G(JT@QQvWrdOu3o|_bZKSc#= {tt.,)>^M4#q5/]ffffMJe-H .Ɩ˒k?ff>]pm8ؾEЋ´HE+kCX" {Z9QM(s5A{C6P 29>3YLVnG5{[+;w9e'nC{) wi\e9t\=+&؁W@ >tTVJyÞ׸nZ8~!Bo -m&8{3>4Cd -Abfzffþ"(.Mt b&QF&&iBgv ߍ5AyycM4V9jGhZBi,o sgje*Vn+:F2Ylj]LW9rY?1!n˵+`nn!FGǘ%No mی  ?EYJ|BbAH)1-I,3T81tHÁJ Tni[fi!'<?@i碽*?#>I 4=7`|;3+wA8fOKe^&g}8MAV u FW;< vc@Q6~RHS'{XTr$ qXr.\.HRz0N300 +VX*p#C#Țn~Dԇwl9R:\GwT{eIfinF`_[%U^G~iFyL)7Upӡs`?L C7[]KsmPohBЮJZ =Oϧ5KQP[GZ\Zum"㝳|AU^'&&!amޅss 2>1ه(Ut@QŚlKr{tC+,KH)4# Bg-u&I*BNv! 5P=q9!SGJ5GSMЎ&BP_.L\um&k19db&E) (*䃊_nj*|Ruu;rNMu5~J5a8ffg'n!);\δW /5ӡY-%z _K̻7 .[CTyp[yml\ 4w,E9b.atrru!GqT+!srxw%z ?77Ti !vh9: 5F ϪዊJWQ5%2/|TTd-N^}B ZZ LO6RvT,*Kixƚd(ڶ$1/\d}m*BXK\*1ej6yNji߷F<էa1ws ǎ%RJ5NtdpSu״llȊplNgY6m aByN)b,s+x=k*}Cg_[ŖP 2?q|&QpeFJTi Cg_kG fFYC(Zj,ЄGud:lJWeG(/Qx;g4e~#؄|>G& "L$IPT6t$.v=5ɺ,y! *\Fh꣸l[205*vX.bZN!eMž.!RJb,jQ,2_m$+B~9꩚ t'|+J*g[EQ6gyo>/ӳ)~u%2;t<7hmmް;.V^x1= 5Gd2TT6T:cZEwNFz-%tS5qZQǸS蚠&V}֋ E*vP!Js',s{ˠ!\ahUK2^U%w۸:ရw6#TY튢{UeCe}|=W֖/wu!-Qu=nA}}'>G B mL&Ƣͱ1:^#*W2=&,㤲pO1L`Fv+2TTy u$D|=N\N] IExiHHg W}(vnXQ4A}e>][yiӷp; ^.d à2ed_cBDhjlP(ugIb4cxdAgrtn{\}Ӣ(ʶ%cS.|e'ȗ%R9d<AeCW=n: 4MRu l2E\::!r)!j#?M)T04ʂnWJ2s2p*(jiι*ġ(E=EYTKO4~uzGV\Yt kWzv׭7\N$3+כCQ`6w/ReW"EQ 4Asm>7=4bfN&kqoK/vi#p"rQUYI]]- ue_d9`dtٹ9rNLNӋ餦zÇ(;06(*C1s>x )$uFp9tVſrL&W5M- .h\ !d|iS ̺4NTEm$\#[JU(LE)PrUfWLK]/tWbW{;d^R=Pj( $I,gbr>&''1#\.\N'랍\1fvT xpIƴjq9T@.M,[+"CQׅ BO9!PeH) m{Y0 0 NYY5 C+ɠz[8KqR1 ],%&p; ?6E0Uem(EB~/"]3+Nvb@ؾg!NGJ&.:Hдj˲Hg2?@ooC1,b`pۍ߿kEt"TD_$)A]P7+1t7Bg5]a[(u oe-I)4 ]q:8\>/YVѴ|2]`j]!D_(FREEQSq9u/;yL `q|&X#2YGG1͕* !uHݻvԈ북+  h~{js&appC1 T)whhro\`r9 ۖ*Kqv/XҖ`Z6%ބؒq,˺uCӉqSriz`]DN&(݊Mδo L:*YCU(ʆRWɊܷNr]ˣw~~ We|>|ӲY>{߷?ǍnB??e\mK.v3=_\Gmķ&}$"JSM1t$SE5E2[M6k.aNcےd&Nxl&L\.G6EJtp8mLRq)Cp8MKʚ*)!aSsδ0-{ iYE2kEZTeݗ(M5N \q? -iʘt]_6^V [@夔fIfgs IDATb)I|=YL@7 *(] |n?{A-əα[$O?ܼ>]שcLNLRSSC{[+eea|~?meI\i{&uzm9.^% !-mLQ›55ܺ/X"#ȖrOY{zq T{tH 6EBr6Ųz]u۽8ʯ%,&nsԶm$v C]/$[JUpm6g36 ph${($S9U~)!l#8w?(J)QEEQք@{%newsSKf9y˖cG EUUU(1?7$cLNN-^`74ӱ{ZxĴlXC>LmĿfSl tϹJŮeKFlTR3w#c,P801x3=:Odsђ na 0@#2(z .Mer\PAE򁧜i1<* hx*هdMXh<:;*(I=EY3nKW1<ct2mQ2YlnzI)fGa|lIFkr9{ݴ4z7%$g E=A2]8U_&[8K^u-%gxV -%cQb u^&T ?ƒYFj")!2- .躾c^s"sHFkt欒}ݚgųQ`N!df>l`;WT@QQ5kJ?c>i(%Ko*bR 﫡ҿC4M#s ROϥfmыm-h`d2J֙<(PƜL dK/#i=odnCRJXidY2 Dh4<333qL\u:* 2&XTͷH)wh*ͩxUA lO%,P{]TEE)%*(ʺф~'aNzy~ bbԀ۽u&xr '&H9uٶdz.ɻ+B6gWSakMpIR(@oOz2t l[2Kso +tu%D\H$gjjII$ض$SUUQp9u*< f1-$c-S־$ccqF7%wxPIK]PT(SEEQ#9W܅.}aƦ|K\dnIeM8v(mx= mQ>=s76PNLqy[%Ok5Rmvdr.&25bp,${eR;ik\Hx fRB<{hv]ަiJFLMM1>>$sssdY@H$BMM H`05yg~ B8h./65yt/tnǟxϮO͕nyGl[I0x3|,!/{+NQ6 (*ޖ kïuܱƖ?{LNG{{^*ڶCCC$>eZ 1n'[,[ qꮭtc$Vf| ~6|S#|nv5\Of!,)U|F'G(tPœdb299sssR)nBTTT|\.4MC'~?ضqb:[ D>8R% cQƦ%}ۖ\鞦odO TosEhj((2ho(v9/31\Ʋ%cSq^;Gδo&C4~wTjs/5MF\KI8yoW>cJǏ6R_Xh!Cg{lj_ĒO-BmĿc }tqhʃT=m\ @v%`6̕qRJL$aY eBt]raabpiP&UUpit2:ut;w ?@ zx<8Nb`QY~]Meݫ62_A;b|boucQ]=m[?:ύifjBt4t0YQJ (* ]*ןz,hR23gv/_wd:ijoc@Zs:TgnKSS^UfKQ0:睳|ta#UWǵv>>PKYpΞ)M12,A*cr(cӉ Zjt]C]o'pp}UJd-zGfΚlpOf⻯t]9mX",T6&y^Mqm>фalLij*6@3g9wuLqv*gx.n R!.﫡,F+q{9e파q۶, ˶Jpk=np8p:*p ^'m wK#} L&=?a)Ti,fx<g, A{c_: b׆X*vr6t [JƦU0qi*8ֈUq8W{7$wH),,TD"A61>tىc5~WSktNݺRr{?CYSٜE,?}l1S)%tLzqdO-*;QQ6 (*%5oOyK3޹QSD)MAeeǏs#;֌M'W:w]v-{GG ?y:鬹&ǤlmR3V39o>̕daVrsb-;X_Zy]6箍{;&}}8;e_>xGݕ5.ޘV*md0>>333fӜ>>TeƋ/#/<δl^~3c;DݲlFc|v}>57MW:OwHX"w_ZTQ{mFnPecѤR,&12c.f>!ȿY]8:~Iu9 z*Bn$/pXSӻH*.wO_Tr>LM^/}n7ڕ2?D,Y\/mcwS9EPel1ٜxYG11`&&5ə67˒X}.!.u Cuk躆p; ~2/HrUWQMׂ nx_t=S֯ 9/Kd/Deee:4⿺ݻvQW[뽣|y>{pv iIF.f)GT~ǤlmRiJƣa'!]7TD[ {%\)"hKvsbUޒ )ah,{9ml[ϖ!_.F 4-w \.۩oًM)%i.-Jۍa8 X 8zk!o/9:/jcp,JYx9sC4Vcsii@z!X40KOn﷯sc;N<//a&_8;~7lWOrtO5Oo<}M\`z.]T[]eUI E8*([@2z4篍;d5j pqPN2_%uFJ:k58K^ô$zn! ::v#% ,n q\X@E˵{3M}S|xaXQ˒O'xˇ8 S5&SWy]3VW {x=RelZy\Z.uMT*mrg1m^/ۖdsg:o˞͡iiΧxM'x~k3GG) ?ߙh ʘ|ٽ:RGد2)az>1^~>"~A'<ʯN;<ه|<|vosĪY-u {8Q=m/f.}t!pra,ҲPulGo^ˣ|VÅs?gnbp,gW%7"졭.L.#-a- ~5>< {dbXz!4>N=]GovppW%u*h/ w ~ wTCM܋YlPTML壋#~][?W&k1>`|PtP&VSUVte/ ]ԡz\;g/,[?:?IδGhPU]e[d2&&'Wn*iinXqHsCa7fhbs*cAd'jܷmE+w:a^3WnSfNO\mT# :7 Lfh,E':7hח.wO򣷮VY_j9w8UGY>4,঩6.AťA 3%N'-f!. \ʘq*cSz}RJӸo?7[Ego-CcQSC4ˣ. `fu]y!~v#5Kwl\x{ƅӲy~^'.Ξ 4]זD2Ǚ+7;]\+>_}x {ɦx`tL/=NcM|?*3&Z4|>uQS]k-4=kx㾂uLMi,Ϟl*5R óz>gp,zϟ>o~+TkiX+tޛMgJ'U_50:?zF *K043OLͥxa.tsB }o$pm!xO_H/^:XJ>p, o(/ Y荹נ^<ã3ȧRq?>~=A8TB:^7Ůiۏ>c`t2,t8Ȋm6b,|Ѓ˩v47Rʘ\hQ89ygO:K~(ہ (*ʙ6箎|37 nKI4w)jvTqtO5 5AB~>ޟz/qׯ3:ZҴT ٜŷC}UFtMDcq4M#J{[~o,&f?}F,58Efcit5AXRn#!5Oq+\"bM*+8ϵk'5-{۟ {p"lRJl)I?Ļg]f˩cvQ?emijZk`tUj#>:DEJ+F\nt1:p!t:/[Ji 7&$T|~\?]1|B]{5M44ٍk,3%BEh.9ulk/ۅ-%EhZ?'&ޯ:y@QNCcWCG(IAQUz~E)aCQ9ul[_J d?φq9uv5?ȁ.P}U*BsM%e*N&+W'/d!Hfd2By#q'0MojI`6_ϟl!2Elea qz\>'{ 5*Bnj#Nn 7M>u s]$?~:xPpbKݠ)ϮL{cE|klV1䩀l9fv>7͍^z*ma>XX;U^ A( OS47痽.gtMdڈ¡ #K5̬,o!mfi?m+̕joQho(Ю67FҕVfej4Oi6f $- 2s(" G2Zo~f;vum_zUBCW_m‘~_ 4_ftG C3?scN% ~xv!(,:8Y3E]*|(P嵐ڟVWbp۾Di~r\ -RL&9uqs,JH$ixmhRl%0 fgÔؿ66LO; ]7uP$Kƫؾv4R _G|n;{6' /q企c8Di~w]D2C)0t| 0$S'{:¥GSpE׼d\ rw lX Uzy s[p$mgzTzЍ,·gG%LNxvll87l_qS_4LTyj+ygGnF$ZKmx &+7_ͶϽ$]UlW^ls'L x2Sƒ;\RyE\ q#93D0۰`D3LݺY++ow dEP?|zӳIZ eƦAM1,BPVtFA3Iff ól^_ɮMlPM)n$p9Uvt }^?'zF"{1 ܷD* c99c\ 7ѹ:o]d6SndcKV\ IBϙ=,h"C(`j&T``(1Ʀ OKܾ,Kнg]4#($PsOn"~goTL$3\2321c{Zx`G#~cU:r@ű~N36x؟ǥsS ZJ 蚝㲳v2\\]0LLǸ2MlY_SPEsBJYKg4.O>w h^O~zJyTlEy1݄Xh̐uB\ƥ 4X,ĦR~;׏rex`(sHGv7\N<$WԱhp:nT+#Q|#'k>ʣq(W2 =ot1<l WR\Fl9K$3\{|ӗƉ'6G\vSqT;3J?^p;DJPYN]qw԰mc5וvCdYbGG ,qu<;ɔҜ8N2\o8P]i ƂQ~N/xgRs톬dN)M7d4RTZ#H2$:TTZ#KdĒDi"DhH831f℣%Bv{nơkΖr>hcEn09#;8Mw{5]mTI oɺ#NNpgC'<4s;ʎ>x'6V#I` 0Ppw3o+SLLe:*h)Ǧ*H?%fcIg8;G8tbp↋^>Ɠ[i1|IʦWu='l;͉B%4M#( ocrzz<m_d<eh<*ht8SV ڬp88g/ѹQf:۸S؞&.pxu]'H`퍥[ڏNsorѨȜP:jl)g]]*rvgn D Wą1N\cp|Pm2jSndJveMq kAQ Xfi%p= d4#;5ok`SkM5~^u#I6Vrv2fr!"Ώх;7GSrpMwkki~a-l%ky@yAF TV8L4bqhFٌ"9`e1CkؼT}tFo_JUpMf~2elꍦRkxȲ4/MR}&Ȇs53üwzha]oPzG\%p/U\񑵂ˡӉ,O2̆(Cu~^zLտLzgG-֗PUrNHRhP[Lo6ݮS00bSCLL有BMJ(48ƐeGv5Lf8sibQ_:so>83ž-u謡KC\Xr)$SٹT+3>9ısLLnOrlolfJN5e&Lw %0 EK:9ȁms!iUTypj6+'uPp78=/N R鬳rx|Oh6]] _=ᓃnb;3JZP$l~(p:ػ>ɾyJ1Y^Ua&!N e5$ EI)|NT=`b:߾G]F7RpQsPsΉ۴CqB$ӡ.Or /}^ɒDunOvRWT.:w߻ڇrST{c.otiLIpvU.rd)vL7~/>ʞun`}S)~'.IIaZ)$*nG$2DS:>ı"ɢW C affx5vPS85eF7]„f^Mb`,|KSg4V;,uľV:F{S>cՊ@ M(,3M#.ˀL GG 93c{ZXeVTd]Ԕ{/.!S8GN1NVLS\j62-toR5~.LuGK_|FW[%wN`5ytW؞vtVt.7߽5ߡ) j6B)lhFn$IJjPW{**˖] c 76C3|NToˇq2bۆ2*^wʡ>.C;8~vLT*u]N{ t.sWy+LLnYX\)67؞ul﨡҇ә$Dԍ@p!E`Q ׁ (=/ F 31G<7Pԗ6J|~rώ j0F(dp|G[Κ5[N*T\6@mJ͵!' $HR_Jyj?-zӗƉn/n9p6Ugk=jXWNY@8/n "3$YQ)"*lan$!cuٶ2M~;53# ל"K~oK;:jhr*$)RMYzA*Ȭ\@ ͵?=ѳ# v/IPuo[={Գe}9~L:LT*ڇ&nֶ%lh.s"ů$nJW[{ֳMm=Q\eF$BsMh &Jp aG<xYbmP&?rjt)ݮ_n;0M0gd"B0gzJNʻNmq T(.PS.@]˞p1q$ ;;7\cG9;+S"7W z9/urvme[G5sn/,{E IDATz}se2CS [v3KP[67=pr-Bmljdw=-8TeU0慰B[d2"dˁUl\2Z|RZ+سj" A{37l8L:}M4V{l)IzNEW+K%~'JټmTE{!( +bVAO.,\ 1>)>vvwQWE)DYccLFn\7qoU a?Lܿj݊m'vslxilRdEɆ*8T={kW^en*JT{h Pp!RnmNP8$IB"[Zϱa?}C3LDFo**nꫳy\j^\G16K/]U0n#Pm7}N,Yas4.W/=k(Gώ28fl*Llƨ6ESSe]]+Qņ J$ \_R$IUR5aܨټ1^-6+w0gF9uqqԊEI=v*˲ R}[p:lHfnB *26VӃ5Jp><7BO/>h/NmMyo0޴:ؿ‘Cwsudur&^x)(|NJ|Nҥ0 bxÁ́]_gt*<^p:S^⢽=ؾG%1"rd:Zʉ' $tp;mYAm]j.gsmQ] b1n7ں qΏqehP$l4~2ڒTkrvtְ"%@4HHt",;n01?>aGqص_D7;7sH$I"399I:fxpdtW4 zs^׍>ۆk.RwTNd2I$2K4%=qt؈"E񜚚" ,_ښzv΍^`d2J,F 2m k?qkz+~WYUkKj`S[7VQse23iYnE*@pw E`0OW9t|H| .v$ *n>&>H; ~`.K7x0OGpuV|( >+wk=O_ӗ&VU8R|7={/WUsNudI&024,a8DXU; S5 fdÜ0faә(I&ppJ6d h׊p-X}S(Rr0shm6uYoetRid*[02Zv*1V{oJ)k-\ǜdH$9l7+֞i)J#g[=;Bӽcc]9Oo哏nqJ$3\:ŷ^;]&*,K5j. WWW=9ui}ؔ;Lpb U_#ljzqhu1s 0μnY*˦k^q- 8&nLM9a2̹#*2 nskc]`7Y1V+1~߼e箉k2&F[ fRUӉr8s0O ]bcQ$j=toHѩ*}:l4ɋ"Ifx'-|.*J\r^S zOhsOvbWe^ʪVE=;B$&G;p;ջʽ%;(6l7V5['ۅȿXC":%upEkF5M#JL&s5]7F"QCc^h gf Árt:o=!E s2F`Z |r*lqƂ[ʱWpRSERqڳTf:wTZhV,!Oӄ" R{׭Li\&I$y t+TXWǩu{p5Xk[Jd2:jyNdY#WH/Ҝ'IKdx TyD`wn;ŬՊuY.B&4t:M*MMQ.u|Ƿ7SXXǺ8}^j}^8{Mr)8N3',:&AQ c!AFX'&EQjdˆJzLor4f T㧺Ky ǎaa<Jk$R≬GI8ӳ ‘$HlpH,Ž2Lt(zhNjJ$7/pbHNTo&4bl)bt`uUUX#%; /TDܬ!Ӌ27ڹy?_l4u!6 $In|Zt1L@AQ (#iڒm68M.<|.^zv3}nM'Ose}\12Nd. |n;~'{[sRU桱Ocێ"HR$Ks"9ofIz=3ѳ K(Ԕ{|.JwީaIgh\<~K ER?G7c{ҳU'*@ j|JoGMTUMpxob r1"uƼ5_/ъhd&MR%(Ϋ(x^JJJp\BTBP`b\ĥ8]<>IRxUo\WC^zv _~v3M5~$I"錌GƫgѫMEIett@燬 #"IȊLߙs"c~]1Xl>tF'Mr(q cS.$Z͓[dCO7>CEҜӗJgo<ƫGW߁dlg>`I5)V59to(-x<1AaDq\ߦhs$)YHdL&d2Wd^n7%%%x<QL XE(ӡbhQ|dYvMʡ>_g6vMXZT|v'kA)L凧nKSd 6&cW\e4PUȮꆑ-KrGώ;0}SkY(qF~ "Id}|gye^v6f֫ inacǦ<{j@P־ٚ06?V*XZsYÓ@QfDSDL$ڦ K580L[r,T:?YFsyNW3ϰD`g@p^ U,TӚߊlɁL{t:oK] 5^TB0op} *nr"Zp^eSQꦩOkC 7R]CmJʕ'{9~aE άut`r:ϏLk/`]]Fgk%_| SGeh0Mα~d >d'x&zēCFnS8qUG 0|qPBn%ui %ʩuj /bu A X>kB VѺhd}Ñ[L7Sd2R)h. L!6&0XW-W0+!H( ^}pɴ~Lj~'a74o;0id(N0tMr %>'.< ؞W84RΖ<MrѢ#5t Ί_z N 9[o_$).b(1Sېe߼t=+E4Qî^]a$'qƜpG랇'7"2i@jVR.4CA!Pd2K"`mb5c䧳ȲnVǼ5O}ҢzMRq\.n.2Aw:P8sbۯo]TU~|^U>Hkc)nUSxcLh-6]UrrPY桥rj4V*s+RZ!kTZ xRRNI]0 Hْ)xj?Ul2a≥,~3:cSQqkl"X͜HYO[*ʹ[ř|׍&bnu:8N'N˕{p8p8~Pb cN$DDFq("܁b)Nc8ZJr's.`kc֘aB\}Iw1#5\ X^CQ Xf̉BF"S_Wfj&m &VLRq:l8T6oQPP]Ud2DQ4vÁfe^ŶՌLD8;\2Xڤ3:?;G:-NR^zv aLNЊē^|ڀ`oѯ\ē^ %>'_KPm©x/R(`܅NYfk]w# b 㦠 r! -_*v j"EX_ յboՅcFn2xp9W"E`ɿTsUEaW%iIr'.۩R_奩6@Ǻrj*|lhˡ"IVqXn%0NPU5 ( Ue.; qAdp|`x"o)u׏\&4=[y4TBe^;rƟ'px_6~-r誊/CE@SmEsRhuP-T8hM鐟a)Mh }@hu PpXӔXLQP1ν,FY)Tm}}7w+=BbŠBK-NA\ȳOsxөu5L|kXnw;wDzxޜA < k X~U ;Onm (઴5u,#vPJȅJiopo`8N21\uoFldJ_?޽dhAЦlh*?^WW_f<]۟ɳn%`kBpK$o!`~r圼 [ۖa L&k"]L`a '>g擻XWu~PNW} Zs~kJŨLwqZ~?^UU}fkDѬiZN@4Ýc̷H$r hZ^TU%rĸV =(qN]篿y7߿lﱡ/=u@?hLEddYr|@V\(/yw 0.ZN$`WW-<)|2oyDד%R7aSK[;^ U>~;ģ!yEkeaNMW5^kIm6nǃι5M˝;nӹhub3:pA_0[RRBiip+ 5N$ïE"<.{[_EM7ձaF aR`\]UdN Vkhmׅ\mӺfo<sz~vsz|yy-@pLk NW!'Z]U*sSU桲MmJ/UJVs1 M7Sc8wy WOfd.;CǺrJ|.R #.\ r p(mmN6_2쪂nNDx|s\.l̎LU>| yªUzyvçq$IfggZKŭL{w_Lx\L(\KﲊVDžu_BS c6'Tע(Hk fub}D9&M.Cω>Ͽo :܄/X)S:}MJ~?h5\R~~ρmgo]v.YPm2,aSddI"ēi4d$(a&g׉5^7q]]mtƏ NsGώr(C^Um'YWW]U0 ¾|*v2}3e#-Ƨmhtn*X}dB.)_ X˘.DS<%BXM~9wL?=KhM+uD]yt8j^z臨K*dfft::tSg[!p!1Q 1'f0Agɲ]kV):t[)AkB!ZۡcCZXcy/Zw!azޘb"ba$ItDE%&@p9"4zraWk<[im($8y7?\1N͛<+3ڨ."IH<-yGB3 ޼.v5o?|8?{Ī95+K [7Pfffְ< 狀I%1ɼ1Cj y|`foR!~̜ ,TE XI œ5Y%Y\zo.c1꼿1W*䝄$I\.~?^WA @prz Icm9$j䓏ndgg ><.;x=|gr:F2`}c)_6׊Ǖ 0 ݀IFsb#R<SQQ$ip$y_eUeF7}tOszNΌD$%:LmPʲ-KkFxWk+bƾ؈ua܈}e;VaAY%Ԉ$xp89 άj_<94 4D6UNK/+\ֻO_/zAl8_Kha6+??vDkwh8e'%yϿuK|Z~U½;5Dsl)n o=1Z g˖v\{ #ʎM=Vf27[["@ o%zD/F=F( JҺ+ъ`vfXB.7y^BLJgzyU]R7|$@c[=]Џ޽pJ嬎)s:PR+k!Vmv{ G-uhu粙4L5u ') tVSNcUU6ΡN[^ ]()J[ [|) ,M HnWfSZMZMV+n{TX T,UT433jR|>l6.A;oZj6jϢK 岪ժfffT*T,m3:PƢOm萵*hoĭM{ZX]J4JG;Ʉǁd2>X@x$ ef:qcK F5 ɻD@ח[zf]j*'5SjlA'+:TL9zGAwx"; FcG?ZRHHłj1j~Iwj#96'fH$tvS+.4trj)`L& @fSFCfSARD"|^rYJe] gC=̆a{.bQrYryvC`,`@?˅7;FloB>Dmn?MNWO$ 'FF7˅KAq*>(/P饪U-e5Ll' $D+.]j[V_?rV3圎.ъUj}uzܖ^^Ξ]D"R1R͕w2'T-} y:{bN'Vq ȾnUH6ZudPNhJphha]~؟t+ mXcqtRX &ݮ&=V֋q7/L&aMNO^W}ŷ/rZUHڲeC`1Z %1Z?Vpaq.NDZpp~cOmt#uČ?1j)`DGiA__KWkrW!+0[ O-đRIOHH/  j{sG5_+ВSZw֕wx<⡢Νl9_fb^|E8 G<J>=1͔ CFcƓGjxxNHRzj6ͪ+AE"0" 9_ϞYY91`FW] M0ҕu}ۗͤtziFfTRӧ_V$ֻ?bSÑ^{@VM"Nz ͔rgq};L>knxp lov֠McƢR08YuNUQp8\?OdTleUG r31cxN=68%M6%گ1z;*%IA:hçvXhˬ=_ͦF־&L 岂 P`+l\p#P tR:~\+n,QoZh</'} ðq?WLcՂ -ƖYj!ݶ pc"<,AOLFlV|>|~*{^Jn %{ZV;ɨ`1zj\.^~A܋@T IRVRVgOj4j ;ꭞZA_N_@An^n^phr%ĉJzfReRwf>|kg*aXV-e4WkTṂ z.]¥;z޿Q׵MZm?gJ&_Y?91rWBw.Ǻx[_~OۗWtg-`m)Hv_0\V`X6mAfAs.zی0ѵ і$iIJe W}? -Tczڱ5u:iu:ݿ=FQp8T -XPq8*Ʉm'zCe8Iz^؝^j}5Zk|tku C݁z񇽨&"K$yNL$**3**2*a%b)QQQO{Bw ϝӹSsr.轫kvA.PPއ՞LJsΝ_);5ʅR)oA`?;K'S z`Mllߚ;P%PEEVq?V]D.x.[Vhv:*DۖnWD" -ze-jl6/ BXh}{=6z>F+D`:E$T)eU)eu#cMt"1N*I`<( ͕njzG锧RقN,V8_ܴwbH\S ֻq|X333j4`()Wwt13iIDATHYxnIq=wvA^bgNF􍋺xuma/.{S-s?qRmf8;K.–1}AN" ;>I ŭ+qXiȪS67Vc.~P7j jjʊr\;ZT*hmmMT*)χfggU,UN ^ਘS/W:7^{붮hk߹cz܂^xΞUV_{ GcuyE7uv끷wߺ=13'fGo_~;}Uv$N,@6Էv* Xb4@}L*Bx0*|>jvVuWfCV:lk1zd2Ŷf.}cz6 \|#P--\o}~w.菾.uhU:XBIBfLg 7W/Ǘ>D߿pS_'[O>{T_]ΒH*#8 bMʹpl/eGye7 ӐseBVG$2--`Ѫ}zZux<mr9yw*`0|8>op-X ;cq???"lQO<{TRVռg՚._tRs՜Z/haJN՜ y_lJIx2Q7jo6ƻ 7ܒ+7zҊ^9sthEvR)O O-x|YC Ax~[4q; }?}gb˖Ѷ#Ljh 0 E`km`Zz`0+l,)`:`~J)}Pv.Tn.~PH̉Y-t}۶vD?:De;[[_T©`Zfbj^*~v T, m"tTP_7 (H$_Ԃ޾`V cݸҕ5Uz#*rVT౓ lpa?WNXhYpg˅*z]l#*1 Zi=,$<O"PTRT i'Hz= gfffoFjZt:* d2S\ښS"Չ`y^B?:{rV_Kw:{TkvufSKJzy;Gur hx Ƭ}M~H$¥F+Dh'D$ W{^*;'Z5d|89ز噙5Mj5z~xӶiXRQXTQP2¾lVrYb1߬-WTRr"t*ق>1/ꥧ7.]=`)t'|ZOlHfUu!$!:ؚRaKF'.[?+WUӶvfvaOͪGQxzj6*J* ekCr۽H<`?yHE;\7pUڵI GcuCy^Bbf_=1sT;zf n1+Fy.u:p*KhLF\NVKA""0"첤L^i:Z37۷uNK PB~:|Yz>vznHj.~@,%˞EC;z= p@.E/ޯBx*H"qRUϟ]7~pU{}MWwz*IRh"i0k'9r~QKJ;xxXY8huEȖZ%_$%ZN[9[m7OvhnIUaķÖ0_x\.L&BfN^^ Ʀ=w]Z}`т\.Ni?FqF{,=9O=}뺱ҝzhj)Zwvlg ~B;=d0=/ɵ p/*[a47;n}f˼с(tyڶpPx%j4ja T(T.fJ<Ey<%<$Y?3O߼=^j[@H;$*ܩ9 c}p,N%=}O|1 :X 0^=,L&J$޷odUteʒGm9ؤt:wWa՘|BD Upo{GMS\VTP /M3UjT.533:s LHj}}w{j7V]~M^WI/'??'h2lji2(JuZ {EB.V}YiRb˾;znZiam}GPfSkkkvNyfggUTdvy G"# uښUɟ7/lk*L9gwy]^ׯ/mlI3:sp?BVTY$) ް~} F(Go+reW>HhUvXpSAf; }_{smRQ\{@noFzW7;zeyn6iO?wDOѧ_ґ,D+UYHo aL~pBVմʾh`U N A  㢟=?Fn` u:A ,f2yEr9E`_G_ rޔA,݉LJ\Z|F傯s8R3gy+r*DCh> !x`nZ_N ̦MAGiS_}{E60¹cWN ki.`:N{#P(P((),S(#޸۫ZA_HXP*)N*T,-g5_ka.'f8_ޟT*xR [fģAh4 SO=Ȓi7,Xl4jzkčR) UUy.1LxVW3P*驐K+ImAnW8ƗQGUF O{4$9 xES@Q}y?ZAFynϟQqUUi8_~ͼ`;̶}y| YUU7^J¿xS,r\﹡;zϹʽWӷ[(<9`10P3@uc;t~s(&_>RwLyyy%upᚁW״.y *MAЎa}/%{}8ޞrdɲ+MӊT6wXu}^܂տd^2?)7A[<+ҎlR&>L0rf׿Տj]qI2:wWS)"sBڽE .{3, UVE ' v{w@)f~%^.Xv  y_KR^yaÅ%2yBo\̆/ߩzZV nf0Ҭ=ė:K oW|l}_̳>Mt@n $?Y ɒݔYjI ٟsiwW% s8[%QV^xs_͸ek)UUmUU+Eyjο|[LccXb#|()}1>aOz?UUi ڕEB,z ݖUѡ޼X"} TU]P#ߡV5QuF`

UU\ô(|OnuD?G_z3q끞բWMQYYى_/R50w{d|[.cpx"9q.(`0=T3{8zޞ.TUr TUU7?.<~X R- `LdW{&xb08m^|tw7ȶ!06^QǎOfw޳tyǎxR+K܌LWwiB<sTU=zY m%X?A:y3dyS^^Vkw+&܇?K":̛*O[ġ:AvnV[^!'r֠o( g5Oo2J|x UUXQW+WDffk ޳/̏6Yx;@(@Jڑ>IV^GN}4yE/>%CoŷzƝG$z)w[l`1pCfk.[Nޱ).)aÇEnXw+؛σ7ʹ裪jE|EMn :2 pu8~R A}Y+23Æ-K =PUTzͲW4HՔWxR'%^էBQ@ Tk7}vz%,<_딀p J">HJ)f$ITKk'IqV} KEL߼JJJ[~p)VY_ҵKN1Сc@(??+UUm6+=nt xs}\"չFL&&|{KUՙE dMa\h6QZZʖ_嗭yg``BE~ۀ!UU愯(J8{8;n+) \s 8nnnハ7x0rN:Eѩ"Nq) Oin'Np ؉d"0 dl3 @; HoDhʤiKXh(>> G%p{ ukLHppW;v,N:b!Qr5quud2IݫEyMU.lgETp5ZX o 6Ҳ2Q\RLII)X, FI0fWWpwwpWWpssh4<ꌚEDGE>ǏNl*e@[~P+4h ꀦQQQɚ(+/Yfv @ s֫j<__CwK_-? oCItI(g6_h4 vY,T%d2׹sC;'t:}4K~~6~< - ߵM:Tr >r%DtT$&ɊݬA8fm^Re:\xao5sUmQfK=y إ(2##"֫<v9/'M,ԋ>}@@?77AA4G_RY3ӓ9@btq{3tv4 mQ=귘Pj 8qEr|}|nm̝4(@?=]zKK[fFбuRU)Ii sd\F𯨬5gC'-N[~$DKRawZ}Zᒋ& N~#Elc4(cq9UhsWU8%ƿs/p+n";imN69"_`_ċ \Z2UV+8 Uτ\̆KT{qg ˫qAcp5ȦIVlwKt]Ij[RRR;i4mRvS zS͊C>{ 'ڤ|qNg\iW!!u ꋛb*2w$ڢuf5?7WT݇bAoTF` >SjZlH4 v'-FEYy`J<.$4dPC1DGEӱ#aa g/B46-|$ w琞s =kddB7HddV VZG|U?}p@瓑 ;'W6/|G^tAK{nʀoul͍;v㊕|ɧ,_i/u=giPU !o ]{8QT<ZmYˆPTt &.`JN[Fw`Yy.v6رcEQ6{NM;@OVe?/DEF|+xY3v ڍl;iJJ8"s>aNa?5(0/M*h4=)n޽z"رx^Q䒴+xCk۷Zm㰛+ӯo\D$:'s-7>|+Z؟.N.3;J# pMİoVySDx900h4镞Q%I"!^fvd2qx>WdTZ,X,Ufz˲i[eY694)d-yl SthqOc螔ȈWew|V^ÎhWc$$yjC3^ J!'צ9v_2vP'yA""$Rmt9r}Ȉ,o4`]p 4 YUA=z@LmY<NƝ98<>l2 !6yxx$ZVi]X&!?=ZiVrrqNm8bNn#Gs@F!k +nEE%{ofmf-!{[7'Szn둣ƝG2V}Q#ٴF#C o>X͖ I:$ήyhV!Q zK&'ή-9IӌKuwwrO21ϑ'2"|,[5Mszeh¯3j@L؊ޟ^@i!"{K}}}:F^L&^ՇCf7)**b~L|}|,/4e"ҵc¿˲yuCbCMA}yH_bXBdd0`eVS¿ʲihи-{ve]Mwp #"<7??ӧkϏQ#ӭ[jNKgde9'f#;LFcWY4tS¯G324N8Yygc<ߞM-6CQiʦl_߫2x}⊕ٹkֲ+-;N\\lg!IӴ-{끦iY?7 IC:>rY'_%7W`` *2L, 2cF$44nMQQARJJJ(--#:*r,˚i;[Z NMӬ,a5;?YCgnBl&c4q蜐hfoV\Myݚ((,lAAɲ,/4-%=~4M4mqB||Ɉѣ=]ĺmTKxӮN3O$.fs{ͦ 䫪ZSw޳T[f٨*cyqq@Xv}`D`֧[0 <|k]aUU7rn9;<F} ř= d~`S?Ô]癷?7?|fbt7GXPdž7zd9b6y˫1vp,2Wq9p'.mFx)D Xa [r($iGNS<]tvi6r+~^nDw :ԛP/:faXVU5ud!3WPQiFύCc=iEl63hb;#7:|t~w=ew %4s?p#K&(J20TqE`:Յ$at k@zw  Iw54b۾ə~B=veT R:?{g>/0&KOdqX,ULAv7Wx#xq1w_;fPovϟw;ėa4H> 1 M¾PGA vp^eUlۗͦixyлK(K+{x~Κsv|5}iߞͰ<̷ʲ,wts2˷l]-_wߓvxxExq:6|g.!:AQ>A~1R <Z@+ն,G`Ǚn⋥{1 co$X=>(GBat,U6Hw7+`馾7'ʲ{gnͶ:n@گu$?9keJDGo[x_K8U\1آ(㟶Z >5ZH;r]D+-(9V-4 ~ٓ WYEQZMk@pef 7'=Zti% V*K .IbpV+|Ml,O8z8_oxLe@p|\;8Qb.ՁSR^im@Xa\6a`dYެiZbBZdYtCuוJ+eBb\ }ҹ?+d\(?^2۲RsoV'63&nY+eY^imԧ Oׇo>ǵwkbe sN2w$ ~U4dR\z'9ŌFqW9JEuA+ҀᣅeY^Ӵ IDAT ({7_4ܜ^F2vH,[vXv:GO?˞i[r0îJ4 #eYNӴe.`U(}*{=iK&1 NDA[J*()s.sQoOfLW~`ڭy7VqgWxpHVUu_3 o/Zs0f?sČc-(PVQj6֚bof#lqzXF67I=vc3K3xZ|-}k7]nz#U.B>(:Yk)߃o/Ҏdw;ظ3S^n.F݈ "6—(?b#|n eL5q"u9SI#|ݎZϋ a~9zy޳$AyUUf&M@Q O]ԯ8_̔b^}g7%߃ Bτ`b2J9߯9HFΩsIdfZϋܿ'3Mg,@ =7ĸ\`LJhՃۚȲtݰӎkb4Hׅ =m\e`,\kz$WEa4J:"ŚY Đޑ]C1Su K񡺫E=xqtp7ɒ=fBVcڂ76%jE~WV^ {>a/\πyr ϨqjK&[9ϏėW1mw.rh?>y<7UU}^aLW&Ȳd5$sCEIN>xp,}dY%Mc*ʪM#--^{eBnA CG b"|Y1RecɺLC\]:شAy22z@L?Y7hVS+M(Wd]~'$HPoB=0Hn&V%dd VnʠRELʐ8n7_1M4Ѽk1$F+jgxyq|d_)צfq@'M @QUVۓS_Yf:(q۵]y>xyݼJo}7vOZ7zyxxk ,yv(ޑm.^ݓ+vsN2oG&}8}I"q($G&y1>fmd=Ey<8fsD ʐΎ,k'He`t(9Yj,2 r><]/ GP~7d&?='ںu-6;Ɨ?.WڑS:Ntp5M[ܨL JpƟ RQG&J*2 k] j *,VVm`#HR8]7;TZmazw*B0(>UZ IcYZ[v'|8 f{Nh48uye;_b4JO OPn}iT4ptpM^0 wks4[ar0LʺmY\a=-{HBiZi¯FӴbM˲a=) l|RX_xNuاY-q~Njwך>+n|t%^?[i?֝C~5iʲ wݐ{dtI%Mߑg0H%&:~U4.5> \AxEQI y1 ۯb@S@wUU3wou<* `ďl}y> N|} TU=I!C=@ \$v< ,졆t yu(^WSqi~LJ+.3]vTAץ# -Xq?\]ɉj%ϭF'8_OTUrj;(+UUm|ILOw37 V--}L6_ymh>RwQRְVsWERU 7 UU˱GQl ^tw5%6m/{ ?!?Id;P(J ~Ե|ݯ? dUGk8GLC&kݛ"v)5jϵx>5gMQq{3WdL.VS@{AO?qG/^nX?9S|=]9`Y 8JQUJUWnKxyp0ޞ.3OtpLFXBNrTUM$qO*1E] _oz[vp7-5v ԌfIy'Gc7zݝ'R㌺c6M)(|YLV' eNp_to*^l2u; I-My^^ϖt-c63&<{olu$8|C/RjױWRR3bD|2nz$NÏ eDtՅ._PeV۴ xۯ\ V`Fij5"yhEk$LSR3Uވ{(?2#J*ؼ({ϸb 捏7^Q!^< Qܥjح$t3Bx肎B's@L5VnKЌѭnNkCU^.׏qШ2v&i懛x狭Aucnp' i'0IUՋ[,|̘{Ɯ@=@P8B,9IQ5|'CUU x:EQ)w{J+>^tp$Z@SUGKi}RY0]+ü>m:877;{vp7&o!`GkvUH{=*㥔w#c7'd514صWZgx8r{0M?isn`&M4a 6`- 4|ƈVyfs+.UZ ߜqV7o TBЭ#䲛%x$æ7srcjjnʉ t$cHI<,dGc2Φ˸KG`՟]7tQ=.W')Ygua[0sbu5evz_]b ::*q:V2's!j RR3?a–7ICBEmc!6(lf PufNXn܌>6G_>9pZ%alLM u:oCm̵]Y;.? ANfLԩ<  ISVaJjKHA[BH_ %`BcVr!SSf~6 dݻ13JGt)s86[u]\S=i _QMcGn~NTRX}]R_Ou!矣C KʜKo](UW|WKU关6"D>{4J O:o⨦ `N g:y25;{9VBrtHa ݶz;gNl/)A0s bt]=DBf}bNpQk[T7SR3 K3 rQuI)tNot;йj5 6/mkoYWL3ܞ+=ժjݩy_9x/K޼YJr/Z* c:xQfNzԌ B;f.ɔ=%OGMO:)I!KyzQ[ !hqc1]rv:h}E}^ۙVxTA7,FY쬧Ζ !=W׵< -6amf<]/Wt례e:u7~/!JxIQugo_}Y+#)XjN[قMiQ}:|oh!'g}* i!IH @)3ljj@Y^!t#4q)s2 z7lh}B@r)O@{6S󟘗| 1OOtFzE3u}ޭis3 MG~zp>oՌQ 75Η$^*db5ɬ1Q}̿͟gx8ՙ1sRԔ:͜N@o}onx0bDF$KLC "HXkub㰅:9)놖z:o>LNwrt鶆x8ꈎجI`w%t _3UGkbtzP#ٕ~ǟz+-3' C?S}$I1if!uFݙ9)'6kgjj'33;??󡎿 :WUY+f?V\OubCQt.t;MmNSOh==y,z\5n5L{=ׅRWTVI_JBS^ʦ'KGm^SpvIV֠Klub;;.&f06:4姀3A+ ƌ5bO_b@*֜~V 聯*:}Nх̠L«d՜HY\JohlrP6q6rK6rWLq g .nu xƤ/U^~cF`SR-I!ή ̐Xa;Иظ5̔Ԍ{\:I\6 ߔԌylׄ^RUy~LdCˎ//xaBbwRXBr|ORlP'͉qfjؼt*w-}?yc[@Lit%hʼ. I2;/<~FjfvL\A@C$u|]Km:)s2Ϗlٙs9lQ>J5l"驷3/Ԭ UUqua=~k¶ϟ:gy2'Q!zd*5@쌛M2:|wױz7x% )%5cy_ O]7p6IB5ͯ=Uy?EB ۂm|n <µ6O3@`.vAMk5חYVLM=|.&LMzj41끰 馦f$`@<(XIaO˥ _oĈ)s2gI׹`2:;ӈON(y~Cʂ7~] u|Y#bPKD@a̯uYkK eÅ=}`($0U~m4f`$|^m]=JxŠrh"NyW7:uX_Qp,evGN LtfX8yZ [!}}RRsZ颣Ofw D zw 5#6uÑRR"'ۄm3pQ،QL9V| ybIVp $,PQg>q9يmA- n( Hxkt!uvq}b1cR덵11j$lW?:<ν: !:bQSKڗN ˫,r؟2= DǞ^Ǽ36s\n^Pe%t>LKx>B}I`o'i!̉纏ch.]/ws'G2>[S/:u(_B=lHgP[ft.}$tϚl`+kKԺXN%N%N;i8]vSN%N;i8]vSN%N;i8]vSN%N;i8]vSN%N;i8]vSN%Z.3lIENDB`DataLabSimpleClient-0.10.1/doc/_static/plotpy-stack-powered.png000066400000000000000000000772701463263342600244160ustar00rootroot00000000000000PNG  IHDRae pHYsyyštEXtSoftwarewww.inkscape.org< IDATxy|_H pHV Ϫ_O֊"'RiW>*TmTXX!\"C$ccfggvg6#f;~ c @0Pihh| c%I`4ጱaBrcPc7 B`$IiEUզxn@  P  @8MNp<9cO|mIVO!D9}=)-`{iڴi[zB@  @ oׯ?w!PrZ3ӦM'#@!AKkcN4x"=ZYY'#@ !Aa|uNd(B2e+}=@ ; BUU"0sr|„ ]}=@ 8B CFCCC|mևO@ A(z|7M z>̈́)S<P A"@ MHcc,0WOD!$>1*4Ms^Eyyz"@  † a>Yɲ Y!?Xc1m8TUXw/|f2O&N+@ H lٲ%;qu(y BsFD"Fbi<GZ!7O21"Q P dOyL$;p'lUUD UUŮh@  @ d?# УbVyo2&X DZ猱kNH  B q>ଞ' zIڍ1#@{{;4wb+++#==@ B ٰaI?%IsWD ?O%rTuqf}gԩ;zr@ ; iaÆ+cJgsjg?TQT<4Qs%K+OX&N,//;@ !@ illT;$H eٲ)!ȡ1c@[[[5]*//*@ P f͚5\q8ix7XsjH*ߏ.yԩNgg@ !+ccHgxd)aKg*h76wuua߾}鶫tv@ 0#@ HIsssn[[۟u_IlȻf3XsTv7/34 Cgg'[6z-diB@ R5krx^\0㙉|͢ndss?o1'?VPP#GvպrKcccN@ & NHB.S\$%K ;ayv6ss^DӱcEB?0bdeyA7l3; @ "eT 8aÆc͢m]9pl@dl(HƁZSık:o0mcu1r\ػw/{4B)SH_ ׃TTt|mJwWnL̘ikk[kN`ɓ4]ow @`FB@MSSMv'ZlOz6AY!Cz,UIOus;Ԅyйr%ic`7v%Npu[[YӧO=( /koo.Ey&$~AMMwcg{fEQڄ\N 444|>ߟA Ʊs`X'=ԗ^Af̰5hhcRØ$׵j~;$~?A{-huFWE{{{Yv P sc3a>>eT ~-܎O9SUy-ʌ4vB{߃z=If"bmD)…`'>_Ȳe_?FCk׮Mt@ 0!RFAΦM=68SvtuW\|aoMق(,I0I5vMT)x!"ts3cKGl)jE& k)e"DRڼyK'N=DӴex FY;|~C`AU,ߏq(Q{}HyڴiΜ9S3|W!3pQWLiaJ; 1JQH%; )MMMYtJs?<-%|EVN|~?|>_w]4A$FWG"ᰫEkkAJJRGD*qyL1^&FOWrα)őH.?|A#X,JF",xU\ ~kSJ𷬬?vm{r%K䴶 bYN2@ (+DB` !S܌M%ȦM :/!((*BNn}GkK ,lj"2{6JȕsM, ?[۷szX8C!oll ' )OΛ7'& u_wQJ$;үx>B1k8!d2* B6o<2X%3!RU[nRZ q'cѮ `|y9 IJ#򓟀q3sqd&~ ߶" nᥥ5~JnjA޽|N؃wY:˵KUGS7o qe#;'q9yyڹj,\tttyJa0VRJoP(JbxVi#*` essߝ?־P_B!PAFb4Mnܹ1B z>$ J$I A˔TM Q 1jA3EEg oknN>~:5ᣒvq 55u/><#W݊ >pG(P_8)A)K(! @0Xvk'SC1cC뾟Ξ= {I9r zr>d11 G5ׯ[ tx,@q;=06]N3q-ca/B,IқSNݔʐR:4BTT+  7NS\K$ + lo\6c`gYŝ"C~5lz7uqr"5=;z4&s/5 \y99o>D) nll<J|>߅vi]6oFdRdr%k jwqۦo˲,f2 窬СCF  _ B(9ϡG/x59[`1{vWG)!38=bYh/DuBQ?ejDRZ}{y~ ti(UҥKZZZFBMqaĆ ݤ )NoѮX ;~ 1HIU>Xp?gHq1:fYK.9"!%J镌{1lٲ?isu&ŠxFfy>P#bc|{M{c͸xt<3Q?`?^w ĉ @S fJFBxF0 7Tiȡiς /}Ij[v-e '\TsȐ!&?pgEQB. B\4=A(0B#_&Fܹ 8~(OL02M9P @ **"E;Eq-0jP B7ch$AYQJ ~b:!`xILeYƈѣVBh=s9NbcO?m" &ONL-(N<8$OLCaa:NƘ+q~t7A/=(pW'(>{;] x"iș ~]9s/wX!!30|H˩imԫ; 6΢FiQGNJn.pzp:!9yy:lqѲH09@2Xǝvf-e o\de0\j2Əeh65??m\ʬ G555UN<Փ(USSsB [zgVN = @} Ù p]eKsRFܒb (((*B^$UUU31Q/,΄ ,1t3j"ۜ(?*J0}:0/ }nPfŭ3Nwv{c"-εtj~xfstѮ7p G@]J~SczB Bh!W'HnxȊaًSUVX!744$iiEc$k`(Z2w/(ݾ['x⦛n앙,PJ(PHɋ{1HWӴ]Z|'-[onn/IHMrNMӾ6OOI%COb]BJJsE9lWh;ݼI̅B),j% *AGeeejS[ %<>lHJ0c`+LL1cm&z}!AƎg|Yԥ 4m*:x²"Ǐw5 '[iVV~\L[qo',X,]; п` L) BjB?5M[m ) Dc ]O_6 a@0F5E؉wk5j]lx!!fѢEEp(0O---)/|>q1c`7?WbF+o0^Ve9cUUN`29i~^ J&7N#| O"_^(ʣ=<` TUUoj{sM΅ (bY\w4WP/:1gxd}>a6S{.tQt : :(L2j~\ )l7QXG<=@vNs_;] z5z _X¨Qztg0'G7QE@87wьQGnN4^Z,]UU{H\Es:hP>4YoCvBp9Wh ;&cPWi p R]mDpL/FUUE)Uqq1ýx$t@aa[[[…A,J)Y`Aozهr~8Cxr&cCxp* IDATbX+)?蚱`3X-@aO}}xUU_F! >$/aS21t((7|/Cϻ5S',?Obnٲe~pPfڵ`N}K$xmANS}KKbBa3l..'Mң#G ؘH>FȟSSBu<X$͍O()qgB_@8,1J zܭ߸fBHMMDA3s[ZZVSJ{jF7(Jf*^"p݊+"?xs˸m-^R5*";&xLR: ]$bRZ^7BŔdWn=rx?VY/$|PJ,a&I<ܵkѰ yyyk20,ˮzV٥FƷ.HAGVMs"+KG8i.S 8~.'OCYN>YQ0tAfYڧ:}Ʒ٥Rp#t>eS^AqEEz<=nDZCƍrՁ֭Š;1❼<]mݪ8q }@uMm[ f΅SA lI80?n~};/d͈7Gu!XFѣ= ج,FXڢ\͚x(WE_UUtLPJ #'O0V۪C)}t$nsߎ*L7ҜNoO999EØ(M#]}$% 4MqAs}eYYُf͚eoe4vjEQBݢb߃G렔Ny1 DHE(kjjnauBy=AӴK$IzRG)!_g] P=0'JKKwjY[[{i?|K I㎂+ci0rs*ta _WWh -F/ SJwΛ7#wPJi>^8 e-ZTDatF)}x]]]|V(]\SSsc\6IΈM^^4$Fpj&$2 ~q.x-{LlۖM ̘Ze7ٽum^62;9̨*3xf㛲2]٥:@lĩyN.#GA7=ztFJ !䏙{(c9S^nɿR+-aB`ptKKKoPb\,4v_Q4fBocOz8ՔR%XloI b.]_٩RJ?Ş>VB>&4|"H=c,Mp |陓[[[{juuCu'J|f{n! cпOwόb=XUXX8IGaZ颔BlP[[{f( 83\+kQJ/QeKAEQ"j錱;c\ BMz/Mq555165;uuu߮b} >MV@o2i]stRz2t1`wIƣ'հp7E)y%;q~Au}T[[{ؽs+,XpI@WTK.b=0+++뱜eH7V/:4q1$5Ssf YnunQQ\G2߷ {"y@'!>_ӽz &w=4 ֖n99k-m̘ 7mnM6 G?p+l\n%~RF'[%< .1^ɪ*s !vb}+!g1CEMM͹PifBj0V.\ Bfx"$F.W{ه4 :ME頔.3n&!MIެ^,[ $b\7:kĤBhИ /7!EBȫӦM{&s+EQxumm4M8iA쮠^;y <"oZ54|?L=e)o oĎ$B#2ۜVԂrc,7> Btfp"cXa2eAӴlλۇ̙3U3RƘBwVRRU ;o޼]c _0k]cn$ zy'xRz(l8<^ 纺fÚYVVy777ލٌh4rɒ%6SJwu2{V1 jYowH7, 1cYC%nެ0c`ǎKKYytLc'h$EUkkC; )0>ސ>'kG kKAH$2 +V׭[wi߆a؎%ƅN`|;]W|ƳCa\n5t1%˧jLEQ^z( cw:e oz~XTT$~8=;wRܟhAſiiiL 2 $" 5SJ_${'o@F:b̹EEE{)E?2ƞVǟ՟B. B tk!a诿9ze5X5\{^zZЭe' q;P 6.x1V,*Џmmm:Y 5㹠oS+۾y߶on@pGL5FЦfKGTd1i||zdrKe~'}~(X&:jse$IR}|!fey"4>![[[V,0? dKTiRϱL:8Nh{՛999z]T2.[o۵GUWW{]hI)=z:HR8+IMr!cc`-HBO]\(mɵVWW yxaJeCSVjbҿB-**zpeR⌇2!J0x;rR,*)*!`0w-n2EMMsu EAhc(4>t2|ix*/7B~ _9L߉#cߡ>'E-]Pn.PY |ht6r$:P?_1vvB؈KFСDPC~? DbMӠi\:LTi5\za Pa7iڏa{8l͙ e, (nJ%E苩nQeY`EQ>~sp.o |.d]B)}[! ́PwRo}lB ٿ” \5+V:]9Wx u6ec |pr^7 cg <"UҥKf0t?ehب[C)<B{`0 ]]]ݫq4c>劢,ļm$ +1AbF1Fm$ܷd#·=$.\X->xEQ۽`EB!_c|~/i 0& BțoZXMK:^s$j.£O[&aw A @[,K]Ǡ~xӰS.K-/|(}tΝ-.^/;c@,^ I僆26C `gxGوFo916Xx1BOEoZxBnp[Ґz[4sa.^^ 4ZP\fyFU]EQ }q@D1 Ґq(pq;,u ébb )/ 2Odf,*>=WE#NByyyZ߽{wҞ;---AV.Fqn8@Rr*a]eW?@#|_Q;1oWQR6Уw]c,ȲIF"ף] ϟ5پm>t}b1/Qk33Nm_@2׵&-qd5ʱg{` 8zNe h8P> 4|l>9&Iq2Cm=C="S8@m-bK+(ߖMn﷎ů|ʆobP"5}~Q3@B'ݍ e4 ;1{`0B&Nl,zu,$c.H?|K ~#:gɒ%9EQ&H?a&WWW@G$6}1wâJR #ڣ]2|r@)(ˡ|Gr&@ϕOVf'{֕NY/JY544TDfx~㫪:zݘM c/:pbRAhѢ"@{EQ3nW0uʲ}7)>uuuSJd^ @`ic̍b~0w=pa,}!`ڵW \\QUWW7VUU@ VUU9sJ)}F@EQbҷaM9M ƗU'Eyn|⃕vţLW2<$ltKAÁ247F~S&Z=ظj]Ŗ-٦$}ڞ=ȿ^)ۑ^BoMzm'`M+;=G⃶5u*v fj3Hhp(ODjkk. =6!17כ 2ㅅ?N'}hAqz:B~eu&]y:mAWUUrV( H=8TVMMͷ?hNOo5'F˓  U9- ʧLs^$؉IЍ4~='zTjz9gFc7s쫓b3ncugj ϊY%px>o>N7eŊ4zNU9/!7r)!D"7:IDy/6w|IVVV%mOixQW2.u'u媒9ձ;ڕ$!ל.:t Р9<rv`jIDϧG.8hoζs\g[v )իvU E@76h+m+4w ßXYin!!d0 %I hvfQv y?78^ [LEQƂvli&ݬDQ/H_ =c/(`=_[[{j`=Lż~ִQCmmIk* nꍝ,;z%I~7b7V|K8f[&SןifdY8hX'fԐ|drXjڲ~t8=H4[oQ׏ BK`7wUUF:Y$;vȌ]JVQJl)@7sb_/\.*augEnA5q8 #g3Хy]rχmc7ǮFSP~n |K G@ǁi7 5s&r~sH$[F^}mW^ *Z6feD zW[W1ٝM9v6n!fEδBjcKSK߿+{%!yƘrJоlq<~a6hKEQb5550rғnokkk oja|Rڴߓ\CosJrALW ?MJs0C, L+''\/?㮕fVX!_~:tgQ_TUUNseܦڽRI剚X k$IM.kR y$)C$;v>e0OWWI╯ݙ: <g1^ !uS\ F'Cm]Ÿӯ'V9()::ɓMփ$aĨQΥv˗#|9|'g2mdУ9ī oup2 IDATu^:dlj6ēbƴ]cNٹƐ[ϐ>K`Қ>C,4m-HUA ௒$TWWApXoRJK݈MxT[ԸC)@91vuod~5㡻=x )۠Τ6|~dMӞT8̄iN*A?BF ^;eYިF7B,Oc@m[A0#cz\VJ)h45!D.X`$WQJB-0C%I:K^}ps(O u-pɾL: !4E-0B^=ZK1c̓ {^1o1Й3g~J0ECc'A\ 4n&c.GB0aX]r4e0RmwvW|áY̥EEf$)e)T?E:+TQYn研w1Ly1 fRa t PZ0zo99b:W tyw*>xt,vJ00ƺbiH̉9,˷X6(NDYs!z9 !N?6RʼF 0p)?p]`?U7CUU}n]4, F; B-<<٩2Wb< \5v5,_BaJT7x`Pdѡ;bn~Ѓ C&%1mS޾鏯iZ:z̍K=dPۄ1^H0 p1v-J4ŋuvvi =[2ݶ?bj^䵽!0n" *Ō xwEq,jR4Օ$I۳f͊RJZVTWWו3#4s(V$oj:*>'vXIEQR(_6o$)ߋ1֓|횦%'_AOC"tn\ook,#$b1ȲsӼ;V.x':`AYХжp wux\VСzj/ص+!8rl߼ٶ-,'Fe`ma!@6.  87G/3'> }nBiT2`j?p$5$0w=){t@w`?=h7FY?QUu!hnAQJ+H!pb"!\} 2t[MFˬ?u|B%ks&z0*VB>HTĎC?wy, BQ-m e҉ФM]ihj%2 49N@Ό1rV a]MyUQK41mʾ ORaM1a.aBca$\Ww!cVX!#1ܓ< eY^͙LZ`yu04s)+`E/sJB EF13TK<"nج^0XZ⨰:Xk5;eeapccǰ# m9Ə?Xi6vI4%zv)Z| SB=$)Nox=~R݊4^$ B0Ʈ Nຝ72Yi ꢞ1i=m%+#r`T5m ڹVT۶%DƏG֭Ea^A ;ژ,w7]1N8ƱsuћQ'3^-=g{Xwq1t(6ޢ~L(oXbzCC}[Zx1v& 4I`y^] DSJOd g%!d@'(i!cqŨ7¦G$I[X'N 7lk1e!lԊMDߋ7ٚ?^kv5qAB4>s9be˖[WCg AhGo?8p(ތ#a#FtYjbX̿^tvض#15R'^_pÇQ.;;.`0͟~>%v}ngCK[`c'šK1Ig4M۫qY,$CEjjj2ܶAV3o4JTp0ϙ9sJ. \qwfH$;x4hkc^Qm=d(wqjL %@gNB XaшOGt瘍]^Q!al2FkD;'Kc,9uB>XVn!O 2z%'s*lc>N !υ5=q*++e;C(tQB蚉?횎v5]f3 \tn\uv=@>wE nIE,4 9y$(7ξTӵ!;/ԎPoKҮǤKAVU+Ȳԋgc^ ReGmN5FB7ؒ)wv|ocyyy7(!@^H}C_eGuvvM z'e `ҡGBWCUڦ5L2e7qG~;WMʴ0oKe(ӛA39HL[Hg1b4wu41 g$t+~=$@ SވhciG ַ6))- c[}~R[p9{_}Au~c1詥6ȲQs,nҦMz!mSZzɭMo"P r 8xDQ($MR989*GTrQ)(-P44&ٙ1&iO6μNy-BYyѻi(_EUeeVa/ߛ+8 J)'?Aa+% A5 ̛16쁊G"XJ[23vhӅM!~`cC FZMӎfrA4"¸:|^סH$՝""\ r^;J)eX|0j/ /Bx"U{Oqq7&*9QQBp%M֙;wnw&}Bz(IԴ >pٝRi@ _7O?N۶m*_rHieO#sMC)`3&{O㻟C.S `.pσe w֣1s-Q~3Sky+9JKՅޮ. ŭhQΟW0+jk܌`g:;Kd'|>tٙ )Rx jZHB Eow " VJib>\oY"dS6 {MMM)E*eFJu]VuM7Mϧs.1`- oٸZ2cCp e:9.̊2u$` X< ogQq4M*瓌Gicݧi/=(b:XNi!fy/χ[%9`A;p,O 7h^ }FIHd!v-ޡpUkJGpV0sg,9C‰i cAh@(&4 faҥX\SKbҥ9_t XX&9=H@͊Ej.1$$FX^[!ޘYJ]׃+NdTJ3cLӼHqH1v10vON߂E㞖`ۆܼV gp%{AxRXٙ! Nez)ue3 F/._Ǚ/[)&%ic!7L l#`_JYh c̝JY)5*׼g1wצ577glB\v^ȷO2];3S50jv qIiAi*Ńm> nee)e)}%D6t].MiV)jGK}j,Z`0σ0}$cld\y oᆹddߋgTT? }EL/x0H<0zwD~?nnnNB566~.HC,~|aܜ-B 6hvB; L@g8#`x<%OvVPUɷ8vNOT :ŪX2|5Rivn"STUCti{|^Sl7μ(ChƤ3W>P9%.$ Cq_\(=/RO !VCo2YMd1*Oh][I6dBuܼ"pΟb](qLGYXXd/0Sv- !++uZ4:D"=?ۜ>XpkCCÇ<otR0 h7XB/?pcsVWoxݞO1 5a8qnڴ]S.5 %w,QN'\)b!Nc?bح^.!m>ؼ0mB9LuAt˙a9.y(yYʥ]!2mhP4bx#U*KfETwyh6߼`BmS,_]+e {/<f"`Rfw E 6f)_0Ir[ D"<3T{Bzd)J/&r hhh!muA^aeFb8 遀1ƾ8Ts-k &T__rv^f(e_ϩ^a)b6oi4-ku}|s~Cl޲#g h4~o1vUyy!xJ1޽P#*Rcw׭ĔB7#@q/,M`ñ-j[QQ TTX=ᎠOFBCjxG= \^KUk{+KP/Q!ram޼Y/ jA,((/w3ټ&4vV Rp0@}!"2z`r9PΚ5qrqbRJ)eIIɧNFgF4 _"ga #g'tৰKb7Bhor>q2H)(^}d:Kc3َպ.Xwi+9k8Y2)hvMH)%۰?w ,䅠Ǟ=k5M;s޳w G2ƾK4>;h4sQ9Q5DMcLduiӦv9D} g͚UnКwʩ/{̆ HIÙ!"% f@Ix{w0i#X]ӓV )tH\QՏڪpN)Cf{ϧ%fSBd{rr`_!;gօN1ܭ ρixOT2lζذW ;TnB7OEs"c+ĠpQ=It\YzrCtmc BOe9[/_80_Pvx^m%UwyՖ뮻ί@ 0@1#(}˧2 Mv~ʢp걝xo_:= ~JkP^yÁNyhPy5}:p≩&c69ByfHII=iA¼r)//Ukkk+rΕK[? 1T:V0ւ*r |~$ Tc%|`A _ohhx:J4\-兦iL>clRj(Դ4!*DӴytx|ͧihp455}4?",Q4nnn^ocg "jf7*;dTAMihtϟLlBw ָCX4$ 缜G"'wB Ow)=?}zvSc޷ذ*Cu2kh8hDWbE&Ɠįwp0ؽanTcW+G7666*ݑ&BTC_UTRH\sҿ4\)B|q1tMӅ~ A~I cTWWF6x<*hhlliOPe  zN$Bb',ML UuʣDjk>u= W01<hܟ;VX \yۉ8`eFpW{JTKGKg>$!IDAT |2lUrrA2%}GvEv'a6w˗/ϪL䟒W xm]wݥ766~ Vj0u*v1uLkkmBn)7)N/"Vxk`}dqX I~d[}K,ORgXjcd 9O !~ @I4ņF!}jԜ9sv$K X`5?vz4D,bQ|ZqhRyWBP$+#['Rʇc/>K%HfU _4K/qf ݙ/뱉S  4MSU#XBERʋ@1 KMz*ٙ=$ye\6Ƀ`<9TFׁhs}T`T>iF⏛q!ě^%8 bj[E5Xb.R^`Z[[BNX ^_@| Inll<5Ld B|8A-T4dہ1VLSg+tJ%V@%vWۖ |B4nQEfR(Z!֔A7oTYR/eZ7^;|ׇ NyRD^),,ro`0Y#91Fl[p=RʜM"މD"W-Nb[nyS獺W06EXur  !|=0^ *-yj?RW"(Ƌlum~ 0@>lt;CFY]˜ z㥔ذшr Lpޣ!L,HQ8%0xG6n&fH]o/VTTR9P3+/rǚMΉM1v"ȭPqV`>puשz8&)/>u;I|AU(<ַB!Ada֭BfxOq]\T;x>5&K(W٨s܋8vmlg>/ uݲp V&g*E׉84X7pE,Xb5azUh\gSS9篎@vn O\9vs?> u^u_QOQ  bNkjjZ#ZGGG Zw <C\ۏZя,o[`+fSd hsi - sfUH)CkRP__qB?sο,AoM6zR7S:rx@4444!B*_ohh (Kllpp+t*R@H<klU7KӴ38|u/ ÿB:{MF 1>pSϷ25%d;I9Vׁ Qmf3g;90{Dݑ"m^炖VUUuuu;#/i^.F#EqX 2Yͳǁ=r ( ;[w>pBb/|Sb}s>W 'ǘpο ̂۶mےB曧Xe?~|}})yZjU'%ԩ{r¨#gHⶼRD/[>}8bKkx{vdb (/8ZKQ:+ω8fM"c۵sXlbp^;4͠ÚTyRĸ ?s9>|`q,7/W*1MKѱjy?9H$Y";%lt1vfmmRwp ׿3}#p!sί𯵵X08 az5,#xcPLj6a{9牢XT%B4m4͟6t3ƶ3ƞXla%'˘KosћP6W.M92ۢ«ԫxLG3/`#Spk ;D^Jؽy` (D"tj 0Ʈ 2g܄#i^ GPu1~9o޼;t9p-[np~s(aɰ?1>+cq¾m& `7lefEg5M{4?s5sp.X0}uxRJUPgoll<4O8B6wgv*#=}%_n޼yaR+n <9x a݇0O>ki^:4*Fqhcǎ:)eSx«jeS$pɇwAm$߽۳:H02t+zgpLPc<^V+;lė.) Mݻ>P]]膁!R.0 X7o\z$y!`>Ik9 VX +[J)wxw#"8r,o3z]wݥ,6Msr!cTJYP@ ,^Xc2^F;Ux~c'o .[Rʿx~2~Kc`y Z +K{CJJaaaKG'"!¶mfeFz`:{|T7}X ǭ竄$LiǯY_?PED3#/$`gbT/#8f`ڽX{{{HmA!*=BoQQ‰D!$C?ɶ_O_D9% S/fzĠA8x !t]YLLޫ{WThl@g;`䛗$3t1v\"Α#nfP˽|R+|l .F]}}}) 8hA/n9$bbB!ABTTTRfש.&s~Zfc '[AL2}'>u2 iwb ~PQ"qԊ1=H$ t|)eӺu[ҝ q¶^|( $C 42x'٭,Y0i:ds]>u~VyUwg{on@@R{c'zyߛwV CΏ clB?( $C˗ ӦnsLp}%tA>HwK]e-5pGz6Pp,fw-o7 { = B80zeMD{{{SP#PgY=A1=ATVVm߾ ?gsg I]nϓO`ƽK/Դ-VTλQؔCw]baQ'VLS0`DP  wkj OF7mmmmAd$AʢqbDB8DI$)((,eN9v/B 3_"֯هn{LY-%+g2ieP`A5%b222b51RPPpk>CāQi8Aڴ|1~Ͽo n L}(th0wI!T4l_rsL2Kchkq}RWT5+xpNRQH)/YnfKdd֭o"OsNALd qt]4ͳ,z~a{XzPJ6?5c3~l^-m3q3J$>|e } )$c#&l\xP(ATVV@(O'^B6LdS,Mn_X>gw0:>U:p?06hooPS%r9g &-_PO6 &[pdX6"~~b2~ރ^ʴl`ؠPӀ Ύc|#F28@`KLmm͚ HSSqP #z8 @oooTxZ&S9cv`/S>q^'>0~JctNy_Gn*}O Ii*V̛7y A  [nDJy!PY%^lg* ( ظD7fX,p9f(,!^|&%TNDOO4Q  BXM|/|P@H(555{L<p1^Wf8_ W60S螋__J ]3q)8޼RD $.8;Wx!^cc֭: VѬ;Z Ds>ALLv 4o~* IJ=;F/B*i6 $ O>3ϼ8=Wf3[?pΡKL@rCMMͳc1A!D}8% &(d;AD?ܾ}Ry;% 0:m+Q!HrY^.߶@P℣qܺa-4:;;Udp A8(Ru C۱cmRʋUn ٺJLiZz-2d@i -G(v], Y>W'=3iZK$BuuwUq"xE䜟1q !A~ȪOر#j4ʹMMArhϞ=X)% Àa~LDc:3my=g ^${saaA($ "WP eZZZC2[T"unw?K01'-Hcd6YJߏ03񚚚{ & B B}OOEa3PPPT 2tAzx'J)188>]H)?L>AA  MKKf_D<}~*cP)%;@H$kkk AA^P!Ay1a!G{꒽|ٲX^{#6}=}k,dǞ={N`  |AB rkii"cl 11tzns~f+5uD"A=N4Mz  LP@HDعsZ4`y.5L/hiAD"!񱖅:H$r%K  sB)RkDru\gp4ϔ9XD"a 2}-% AW_}ui8>OZWizRW&j&FFFH$FpiWWVVAA~P@HD>a;vdIoC]G=gt~uOkʿDAA^P@HDimmw3{>RJQSS3XB  qB v1<<|%/(ͭ?ٰaC^O  T NKKt2ƾPJ:x޹nݺ AAD  7mu"NaoLӼ AA1!ؾ}2)?1>xO5M)A1ѡ }\\ y>Ax~Q]]xO  "(1yWW1bX='mR'cj`eeAAD( $bc̟?MӎR@42e=EY@  ( $bRsEJKKTH)H) lc az1.| ]^zHAA>-+IENDB`DataLabSimpleClient-0.10.1/doc/api.rst000066400000000000000000000013131463263342600174510ustar00rootroot00000000000000Public API ========== Remote client ^^^^^^^^^^^^^ .. autoclass:: cdlclient.SimpleRemoteProxy :members: Base proxy ^^^^^^^^^^ .. autoclass:: cdlclient.SimpleBaseProxy :members: Simple model ^^^^^^^^^^^^ DataLab Simple Client provides a simple model for accessing the DataLab objects. .. warning:: This model is not complete and does not cover all the DataLab features. .. autoclass:: cdlclient.simplemodel.SignalObj :members: .. autoclass:: cdlclient.simplemodel.ImageObj :members: Connection dialog ^^^^^^^^^^^^^^^^^ .. autoclass:: cdlclient.widgets.ConnectionDialog :members: Get object dialog ^^^^^^^^^^^^^^^^^ .. autoclass:: cdlclient.widgets.GetObjectDialog :members: DataLabSimpleClient-0.10.1/doc/conf.py000066400000000000000000000026361463263342600174560ustar00rootroot00000000000000# -*- coding: utf-8 -*- # pylint: skip-file import os import sys sys.path.insert(0, os.path.abspath("..")) import cdlclient # noqa: E402 os.environ["CDL_DOC"] = "1" # -- Project information ----------------------------------------------------- project = "DataLab Simple Client" author = "" copyright = "2023, Codra - Pierre Raybaut" html_logo = "_static/DataLab-title.png" latex_logo = "_static/DataLab-Frontpage.png" release = cdlclient.__version__ # -- General configuration --------------------------------------------------- extensions = ["sphinx.ext.intersphinx", "sphinx.ext.napoleon", "sphinx.ext.mathjax"] templates_path = ["_templates"] exclude_patterns = [] # -- Options for HTML output ------------------------------------------------- html_theme = "pydata_sphinx_theme" html_theme_options = {"show_toc_level": 2} html_static_path = ["_static"] # -- Options for sphinx-intl package ----------------------------------------- locale_dirs = ["locale/"] # path is example but recommended. gettext_compact = False # optional. # -- Options for autodoc extension ------------------------------------------- autodoc_default_options = { "members": True, "member-order": "bysource", } # -- Options for intersphinx extension --------------------------------------- intersphinx_mapping = { "python": ("https://docs.python.org/3", None), "guidata": ("https://guidata.readthedocs.io/en/latest/", None), } DataLabSimpleClient-0.10.1/doc/images/000077500000000000000000000000001463263342600174155ustar00rootroot00000000000000DataLabSimpleClient-0.10.1/doc/images/DataLab-banner.png000066400000000000000000000411051463263342600226570ustar00rootroot00000000000000PNG  IHDRlkS pHYsRRDtEXtSoftwarewww.inkscape.org< IDATxw|SU{I^(콧 [P(BQy {[J[J۴i&iGhiI)zOͽ<6<$Q @@@@@4M@@@@(lDz@k2\qT؁^0R7%f u(,TiÀ@H(& h-K;/TjGI/#!kI1md872| & p$vDNbmVBϏsc;L_(q΢+왕f7wyzCeXxmr$+Qɋ I L{URM/w'Vƽ|Jq.;'pնa[n^F%JTOG/H8n&vHgN=)le]yt=.$͔Aϣ8r#E짻bP#Iv-h `2I䧳(כ:L"Oǀn^8}-}w_)aW^7]F|ZQw<6apx)W|HkG@@@(ŽjrJ:o?14F>*^ %kr YJѐX8y-?nIߓI$ g􀿇h9[3HG(gkz}.>b]'ꅅ/y vncQ 9z[;i9@!b+}п(d·N.C?ND4w-a?nS25gP)d,z Mh>U/ŧ4}h04ÀDȂ҂" ΥoCٍ8J 6]8y~e-1񳱘22Ӳ~ԁ8g*lwޔgo C4 wpUHNNzfu&H0h`yxlKOފq 8}5Lwnؕ⒩.\lvg^Dܞa6cXiTh;["<A{Ù3S3?BtLNnp!f=eeef#"< AA* \ ZJt.8 7oZY^ǥ+Wq3:| ΁(`@@@gA. mQ otАBCG^l!uNa`?pBHQ/4,U3TR8 @(a,<} U<<`G3.#af1Ut_E<ҪS(*5>b#fG@;iTr׿YZ6'▜YNU~yT'xoWRRF![!dBɤ, SsɤpU8εܷ~~ݵ:ޏ,t!S/qt2$|ǡY,wp;M9 bX L]41)2Mv8n/P~U/2.))Q#6]!mFkd8/2؈ESF&<AY J"J@aeH̓]"\G_2:Ǯ[i{Kp _N?;6*nn223mB] oox+AQa0 55 \E.lR1n g0B}܆ΐ}~z/d-W;UXPTY5$ L.heB57B֘:dVhdl=Y!^n~u2pU.R]f  d2 j"]`@Kb A"1k)KKRW#:[a?`w[Z_\ H$,򕫨YLD"APhX,FRr srEefeATLa o U/D0r/{ =WxkiVTz^ɑŧsUZLǦMExGEITrXu %gJoĜTQ}9@n-`yPc(* 6`\>`t E]iQ5 -Ծ*P(I,fC0iYkwpSy3RϮb_啜ޤR\vrMJḖ c us>Wcse)*QNA9R,be=:(T )7M)|'1`Jv_ꕳTVx:Rh}ee,͢bmЍ|yLb$GUN*2F@/w'BWA\M wN IƔteHH/r q9[nr@"狎# aِ_PTgdzedf!gѰ,NSU=ٿ߻'JTW|1@2pQk,,{5/_}N]`Z,uO5Z#r q-.Oh A <,wYl6fɷ|gՁ_!O ڠ Fgyx((?}044Ka檴KwϩD~}~yEZ&S$zLH!&aךPdj:Ud_9 2UeTE[5UjF䪴u@X!B00!).w%扚'gYv85 ;Df]%:e~8t!%x8IK.z&%HQ{41-7oR-0 NnjǷl;O^7UR 2*' -=z}DNbT*`~-X03j苟:v'\ :mF36-v8i ]8O(=;)dS6쎖Xbd uޒp̊= LH+; [mlbxw;4xfž YP2kN^f5aSl8+vVVȀ^ΝP?$4;Pإ2iټݿzõ}"ǧvl+P9nJ^Ta{E._xVgp` iL6K-0~U XC&LXQ+ȩ9jT?F_mzz+'4K<\i L,w'rJܜM5"UTZAM-Ci' r VtV֘|c|[J~{<8|@|>D .nVYezjjv)kh-+)=}=/ PEix62&N=LBD {u4*بj=Z|=Bػ"RLrҪ{BZe,=*kFfz~ۆ1~UktFgo`kV @"a3흨IhLztm^Kc[,SBCe 2ߪIeR mj7 ~ZIK\&ft~6[<"POVH$?aP{cCN@J2}V-c?s1p8Kr3ۯBT\.Hs:pS0MeM= +t_)3[hxa?JZs90(x%8Ax}rZ5Z#A0L&ѵKd:xBC`X^^ZL +M\^q'.1aOJ֧j[a~ҁNi,}XP.tZl2GSPpt[[:֓;=g4{٣EY=UـWtOJ/{\ShK s_QY}< ;˫icPAc8v$Μ=PU?L9-̭汻B4!4>hkA:M+ ; q-;6Lf3cu ydW/^s:ʄr1,u3{_%x:*ɘ Ҋ(|r Qq5_f eG#/Fg[ҮL>н۰K{]e|el9p[F쓏5 G á8~ix36[]Eba E]=6V%2iotL4UZlxO(ɬ.޽0qxxx[{?DjZT.vp%i!Z8Ʌ2]9g7ӹƊ =Dcʀ }5"esVgKqƾRКLoeM)vd3iX؈ >z^ozi7ye:Cgw P֚ Fr!t/eOU@D %wzf1pYFGqY6]뤭6 Y- >vwim/әL=?t!A^QD"55 7oEbqU^`* ^l,&V2\5 ҿD~2s&zA<:=!HJJ4FAWMRX\1Cwj>&].x֑y;GP Gǵz}`N.1HrdG.=CA.5v(G/kحWr|TG= C>{F(L>8z`pvm6`;Ej6}yQl4YƗP\V޽z<<1aDv^NcϾPlҕtopMk) [oRjk/TۨhT2w8C_9SIASH)Z7tKQ >sF 9Ӻ';mg}ϣ%H$5C;Hqd/M*pJE&6v}@W6~Q*l g 4\s!-օt:}!\.׮_AjZz7L8}fwmsU>5]-Ei-;5WWbwF}BKcRȸ}0tUCJG\b8/[?/׿B\%܋pbB~զHɂ"c0IqwN|wBb&XY\\۱wj٩BJ]lģka0jeAQ[_ͫe?jYLj-v+!&HsmcrrϥT4J\|]9@ʎ \lڋ\̌g?nu@ޭ۫΋dBƕPa0$Ų@P {IH+:Q:sk6iO"`A9YFNyY&Vg"ΤSE¤QXih+SrRȭIotc~ *,ZFM{|hO (򓝣Us'H5dˎb¸qٽH$.]{ /JUT.Ю"IaZ7ӍA=$ G::PWуfk%lwFA&A4VV3AdktcO3*SMXydᗖ 3ش'FBPC0jpDBa߻q'.:K)`.URX~#>U&Կķ,}w$m !9bilbbu{B#-G>Ơe5 =k+Ll5ևlu1UZ؎h1* fךi XLl9t^q7r* f,]F3^qcFLF"'7ol7߸ P8c%7>UPE/OfS3t\%:TV cwJ#/L\$- z%`<7\K Yք{ &^~Gb2;FLfQNkWZaEib>4*šq]Aݽ۶l"r >tFF8um͆Sg΢ /JSo{oʜ5a`@`c}׸ {+&  S[dgiD16BJVYA6x64:ST[E%RZnl MUrn$++m5DY;1*f]wdYa¥oHX%nݼ/:!Xp[9UVVБPK?0b?D%S廊hT \@0eN.SsCU>s#=4y)҉+ 0N,lS+)P^\wN s rN\f<父51Ƚ恨6qّw]j _}2|{Ç<˫y~ ITՆK5=1$L!|ȥ%JQ-73sKp5Pkӯ \ά4nvn3LRL㠰-.bN^M]'dI%iFA/=jN.{gۡZ $*>c-Ra`>{c4|Chdas)x `0; cFc6e)O!=tޯo .T )2$s5v\>6bST,v gm^ z["ųcE7FB~g2 1 ˇf4ң@"d9ȥ_Cbf8JRsl]蜔ؙ㒳ϟ5<W7*VFk,)0 ۩SoqB!lvw 8s=/.@LHxbxnF1x/,^\_xAo32 K8s#s_JL1krBafA~/L͗RsJtU$Y3wʈZ竷^sOH+}֫>tn\D+މ%?9̒\qdϧp b$eYRU ;x-KR/_iO]͠N.$RԵ$WS">Õ"Op?\]pCa3y{)[y{nh 'OGRѣ{7f~{+6]IvM>JaaksJ.JZGy TV">SY'&IEDDCf;l$PwzL&Tyu /N,@IY~w+Ou~M0ŭә"LIR?P!:&ȳWnאI2xꢠX;ŴU8 xЕl  M,a1 DA/.ه/֝VUdaʂݸz'};g+%xŦ5/4|9g`|4+}2k[2.ICf*uy˷q$ݥQ ?{ofTbͺK^]RqP7)?Xw޽Sw>|wkwސNTVkMd K-^kNBIRSl>$# G3C|jɏq+}}<6f7u U[CJ[e5cuCI^o3:,D(H)Ŵw_ ¸!p\ فw,-7`{ױniO(2K?|{Gy$ B&a{CSC|\ơ%;ͫ:Ŋ9ˏ\{hlUH쩋1|ttm^}՘5G߈f?Oj{+_9ā;op1;ȫF9r1}Gjz)YJ6k! ݏyxn=^0,jϩ$N>}'^_ٵ3K~y~8q%-md"sJ ;&~S!AQ$}jLfNehV- °Zꍜ< +1Xt+_\YgH'=l +zv`2ݕ'';?V[p-?,a_o䗞{x>όz\8߻y.ˣ\jT:`Iry6jS!@C WWZ^n\W+5ZߓR7/Fg7<.'@ɿye0[sv&p9es˞~qj]ԣ0癮RJ;d ->|D-O`W't?ON&fC=+JtL+S-W}Ե&.|7 )~6X]޶HJ 8pǔL6e!]}Bۮae N,ķvygǽ;0 @r8p ඊ'ip=W:WiѨ~}kC /sN\IC9\6=F#glZ-6lLn w̯N^2mTد> Gv{%z7Xs7ãcSh3 .~zD A"S($@ MbXt&eFFp{lփݳb+}{-8kw'6в/ģ{oȾ֜WRwr3>bҰ9J^MϤP)Y$S5*2Kۏ+2JjF-sj=Dϥdixj"ӏ>csl0,quݷ7L.ѨZRaj4YX:z\l$qW!R󲹃.{GyU8K+VlgSh1!AxsjWl|Z0n^z524 iH2(2?3CR_s6;Pǯou~5u MŤX`Ш(,&?Sr/G8ʁXFM6.aT8F66)ngG n_<]xK5vyz&[oh8e c *pW*mk+>J]=+V0iPH{I̓RkOg]ҽpBs)ª!0fLtB (&5ǯֵ[[%LrhN.\nn7 7 ~/@3ydϣxF6bFU&{)?ɇìybNKռ.K{Nd&M:ؕ4g%_|.Cz痥vz+>!Zva\M4]E~oQX>wN\Iesӵ琖jM$ʓ1oz\24sBq`}æiKFo(*-70KMd asBCү{-2A+'"Jq8虙SrR3 K+8r#D|f0،bʢ7D&!\r@KcIZa`NR1\oYL&AcT y,CW od=LUag!~jrWMOPZ5t*Z#W5R,+Ig>Sֈl5RJU+yO-BKIA}0uD(6^@n\V5^ClrvH++9}`׿]_sY| z<&$|݅uC r.lG盘1& _gvYL*{bp#u"鳱}!ahaXch0)~+7_/Dc݈,t s++/ùYw&/M2?0wQԬS| l;K19 02> ?w_9DX8Za@ ͻpz}qb*,_3BF">/rVFA*eެv;ȱex=Nb dg[ B&BvTcJf<\9T.NݝM`0'LXmScV-xfT8GNa92l> ѯ\L^͆Nr+,!HKӧ7 hAp !K_ݴiM/7>$B6xl:6K+T`61kr$ӊb%$5%ۃQI!t@8l ax//|" >Wyf0kr$fSW3|F`bዽܸdG{Q3 Nţö| L1 ;K@@Y&hlQ ʮ39NK4[q!:ϥ7/|t b a67=bLEz G٧`$T޻b,|\Cq#!UMI"d|5i C螚&e5*yi&iExmR$f]E%Ҋ4lRIB2{gӁ)zC%n%`as:uf`Ƙ4*yQp-6`|;OثS4%[ Ue[JF^vļgaX/ `NP/(BZcIשT*zh݅G iiUUdMЈ"Ɍ:6يq-.SGbuNb]["doG%׷C)SLb! FgjHiO fXߊa8jzs0=\1q{ozּ꽡<,~"Ԭ^hT22\6dp(pN^/v]lt ƼQ}OF M{2%Y<\wIDAT "O6oz\>.F7=D|"d=ŤCda! h%K=`0W!N܅/s  %}9gyJiyQKG%Fɱn:6pKu=8R_=_R+_a~2_2Rgpm& 7C(yc ʕ0UZ&+|"|.ώWa)S|r !v  [5n>{#*LDB"-B`1<P-'@F`=B 1! xL 6c (0+IENDB`DataLabSimpleClient-0.10.1/doc/images/shots/000077500000000000000000000000001463263342600205555ustar00rootroot00000000000000DataLabSimpleClient-0.10.1/doc/images/shots/connect_dialog.png000066400000000000000000000304411463263342600242350ustar00rootroot00000000000000PNG  IHDR.|sRGBgAMA a pHYsod0IDATx^|U/'$!@H!!z^D@am+Upm(+ࢨ.euEEH% 齼=K^KyE̝;L3Q0 cBHኍUaZ60SaL.aL.aL.aL.aL& WeGx"jΪ`Inõ'?숂9f ƘؽRa4)G[n[%^yl0F C^A)bi> 2|<:7cQQQxfzHo<&0Cbb"Rlem˂/22RYZGuAu3XޮHIMŕWN`28|)QBP|pwp+hϗ2u}CLCp͜9YYYX~=ۇ5k(k['Oazŋh1& -,-PQ^V "jZ'[wZd8>gv.՞-1?9 8Sᮻ’%K)SO>-߿?>txiEQcbpX[`\HgyE+EguF0Ů@Ȫ_414VX3gHk- `LI]lqÑ٘RHI6(9z\ j9GBJL*@Q&ҤӢ}Am6@DY_[TQ~߭n\׵B _J%+^)ZJ01OF7lܤS)k v:$d:VY-ـze#D-#f+~lQ*XmgCBzEy]jկO}զ.#4PmumԮ/WsC_9yFN4flq=m^wB͛Z8:3bp:p!> Be)TX'T, &TEnk6"VGVV=4:Y3h~3uAniiuu ~$Ͻ /؃ )q-aϯ8Ȩ4f#~̆0J0R|{ )g,%q2޽{,\jǴjǼA|0_K6㷤";3xtѯvoJfcՄZqqq|@]CӫM/[@L!6Վ("zKy}4V AV‘`HK 衱[~ج?łsH( R#Z2uS y3c`_눹;& +=<`O*[4Ϡ] B4f^j]\MFӦOJJR"\*7MW :Ogǿ@Z73 =,ZWg=B}%^o􈚩췎2ҥKW_)K-"ZZEu}Lk+Njyy8X;2p5-8BaUlR'J\"++<㮋 ,l0 c<J*}puDyy0dMGeV]ēz]kTTT(k1&?7Х#>xi4l-d'ga*" bc-2As*ڹXB-tlq#C# {5ϽEX4;H,@bj.Rr+b~xǼx1]Eiu$nRJ4,7B;JKk˶Wh޽z"[W.VTPEIZ_ii\0] ,i?x"HΜ=](ctEiĪEaL31()(PWV &6iiJ~߸-[cמbg]wպ}9>v;KAذq3<ںn OAyYt +'xHrɰFqItK`.,)K+KXYZ!((7ƴү_G\\hM9 0Fc\5cOϤ+Wpj2ㅀ]OɪɑRR`ogWҍi5t`DDODH)Z YH,RJ~79lILŏV2Ҫ-MGƏ?"8֌[6",eIm-lW'J\Ubc{G{9\S jy, !ߗX!9! [$\ ?Cˣ00i,"+չf&I5$H.2Xdd538|&YCB H,0[ }~ٟ~byTĴ Zpul,i0wJQVVʜqyx|w|H񏿆aSaxX̼':wl#R_Ijgޛ;q"%HLU W.5|SmC{z W;nC1f7Ńc}aei\<.HbVAzеWJSXT( ,́.0kRW|x +ADvC0lZpԘ0\)?y:etꄰG)tr0tqzOOZ-i_AGSs$]]]̞=[Yl]v^7 1srXdJx0h@eK28.?^杖6z3;%!+6߭u!;z>ۏ]{\JFBqlpq.=?y K*FS( t^ABJN&N흑[X.hreoSSPK./;c[BbX%Zkv&' ΊIˇW[+Z6QAht'bpL*IbvG\EtRgJllvVJ5m?$cXjޞd t b"L~m#UVdI-Qཿc 999|9Q&ܭzG|"V;-@C| :,?s/ڹּ`tVqq|)'odfvBJ+z) .N!/nBrzRoMDN5a!|~k9J jG7g\QuŔ[!&_/.^MOH(ͦ|pC+O#|9} >>Uji ᪝Ao_l8GEAa`~O(U[\5^Z]+aiwiJ&=t]CAڭkQcqiqsņ}Rz 7V 2X"+1{̞a G.m_X=I/,A=hd!=4Z\w ("خ"h "PTRnOGJ)9AO !bܑ#M"s0([*눮.Jkt-{wFٹo@tM~YgxM̄yC-2sKmۊŋ,V $\Ǒs(ߛXm赔X8x\_F# CSra9,{j82dJ\< l oL S7Yϭ.5eI~<sᚓͲ žtm/z|>[4^xݨ }}ݱ b?``o rjp<0&ü}q5|Bä W}{dgŒ9C0oiwA#9ˣl+8{i9RkK@.,, 5DŽ[`!e]0v(9{Җm]aeq8rtI}n!Xupχë~{y(W'5T -@W:38Zb Pl<5/(kU GOWѾ~ne|0'*k[',\ e5I^SQwcXP=w{@vv d-#)P3+/7CNYS?}_үv+,Oy ܞbCټ#n=kdQîPW,gjйZ<'DWc`sCGkO!%Ӱp`cƪˮ@͇.V֕d9MZz8]11P&ąSZ˕8z>UƇvFBxotlZM2{% NVW%>.xxBd|(d~obj/!SFʗ7у}+@Z7Ab@?^Z9?B8%nڂXcBG=" @ }b5rP'z $ov9PcPp{e$(ĉ נ_GиT..ȵ-l 7; W-H:yWԂgS5 rˏġ{`L?NQ#$EE86bߵ 첒jQ$.Hc@0Iy'e|6qE9E2]UaO;צSٯ7PZ:,\z v';[ 6.4pRCy{&˳uwfh6um'j-,.8J*m vek5SZCSQ<(E9((<ѥ94F+Ck ڿM!%Ghp79f n׷F Bq9ع ):t暜ZåZ_YS+c@&ojde2Js**g EoEo Ec'kk"swdo^v.mo@bRRv{S|_̀/ H zdS1sN(#P~JN~iՋ{r}M+VFvӎ_lFGB K .ڈ"+*t66r5ѿ|4\Z0.OΟi*D^4.>1Ŷ,PS{x a-ݹXeo:,،&T8rVs ? VCtʎW[%f7bU?*31j/f =0vhTZ{w"P'!ICVuFePͿ!=q$MIƢ{g7Lu + N Ӕԅ H˸]vkSWW =}þ89'*xu< ˷ <5ߏn痋8 3)dexAx򾾘󏭰t':cIn?x*xib -Ew❯"zʎ4kCߝ2kOL-^s\k B|=cU-G}^hu~<ǍlIz6V'\$"r ϽK2YSrH )Xim;< ^00sbWp`pnT"@"Dl׉=mcOfd 1]L.<  >-˾|!Jf!Zgp57{3H^ ,v㣵FߗxŒ9 IU9ru-(ru메ϓHqב ; 5XvCnx6huCP`}GWn!ll; ;`Bǥ+xknEBwObSPqUVI;:W[G,QfI…^KIٲNTM9Ew_R[BQ. _;֌BA^2vv!N3򁳓-\mdIɪ)\iQ(,ƎfOM,UC7Ӱ#Unt; m+3r&b3T"S8s nx\9!c&ul(n2uq3TgyڝUɚ򏿾m gW?'K½LAHJuxAH hSzgAI܅G&϶vu A#8uMEց y~^.zw 3,z ^pwM\Oµ~AX&ғQ7i4*DKԞZ~>/ӞV5BpV ㍻ݽ'f8d"Z.4l!}&ta%]僂kơH',LX8!%_aQ_ci[#bolߪ*~8%;D= /ݗOpߨ8FꙇщPVV*>t3cCMB@(!'[uil=}ж4 *HYT4!Sw4tNkӘ}LEݽh҆4t[_TZUQjvvL5\iEUO2a*E Cs0Q. A=5l)\ڗpf?,aPW60XcRPP\7Whn(Mhm> b9aZxgQ!~R)~񯿅Gd?V(P? / WX[hy3s+l9տ jCdA;"ˊh╿ y1 S7Z,K'WZ(տ[{{'n40\TB2 1zhg^C|Y]}0h{zay!&Iw :草0 S ̼ {cj  " P!z=H(nk0 = ` W=Ag %\Ac:vY>Y"KRZdTN YNndž 3n`vwИYnt@fF?z2ԕe5)W=W#>(66ejG30%Kmb[H=C |<0N2]E}HRZƥ,TP;=wڻK^L9 KTex:12I:mkT VWRjM'yŷ0d8e $h +v%9 e{wl瀫xՍrdgN%c9G95aꁄ]Eh?sU]_wO5KL|z]h4D daBO00WI3@x43~zwjC\nW@<\ 0,\ Ƨ [&j^cm0>r;ac\4*E~Q~KȔ#\|81^Wa$d`?x0wRO-1 .aL.aL.aL.aL.aL.aL.aLP9YaP")Ҥy ,r] 4& W^^v?0Y VI9 xi?_E8;ܸ6H̩D;N!EXh*3 0Lhpe8 V*s88'¼ړ1*tЫkg̙ |p 8 B%rK*aJ3da$x=foW1 ...2b`IW3S y6_k%ʄVDia~$\XzJڻ;ֲ^me g}(E/E\m`UT"5 J2QAvΗX5ߩ$Ę1+P{v0V; IE)ΰ'pttOL'L("F_](NA{<8Z SlJbur \벳LwP;_YdyipD\c(*V2gKZvvvFnxcsH?G$ߏN/fv.EŸZ21zLJ%6_#fr~& ʉ*g'WS O,YLc)5m1bYj6qB z6G}m:m1ꜻz shŕk7]oXz4n̑"Dfcc#E]v=|0;GÒ#W;n@X&-D:vjykB@DB[7 k\z˩hXs٧b\*2Fzr1y.ǠAqJ,xM~ζ1V^M6}\Z]\e F|!7!J|al6s3̚(n^n0w& *ѷO/1wMST1&-^>xSPV^lUMQIl]=W\2 nnhZ tVfk(q?\IcbŅ)GjWN` k- !Bc=t7D]m7K>k, iuT>(UQV=)$\P]Vs% mTu`MsqX\+e([i k-li((@zqxzzjm&Y;Ґ,hD(e4VRh1,"1܉QS1Vk6y`#av]}C;X԰2WLT^Nn mJqr:Z`ccVJvH+Gf~)J*-"_>g Ռ/.̿ #B<R*b*ZK!FOĬ p ),8qzةQ<3<\ i?o&Σua4t!rDMהϊ5ۑ vkbO2ԤĈh|iND낅*mn+;7|\BdD~b]ǡrA/Ӛ[m0w&Ay\f/ƜyOtV"\$&&e_H@b .~f7]f4hEi@h'"I0&AAt31;Z"̣ llstcM;|}2?GW YH)BQyT*XDÓGQiN Ƣ0MIu4w BC⧝GTby%ePA- TjY L ;+ +++eaKe0h(7¢°jL?^{1}O U >NV*C0 tD>,Z 'Wb k19|)Ňƚߴm'6D\f)J,ف^#/ p7\]]Y1&Li $^$<%%%rJJJq%&m׷,kkMa(c냆ѾL+PAi,X =|]F})Q>i"Eac,0 Ӝp1 crp1 crp1 crp1 crp1 cr4K0\JU昺~28-a9aaŕ4N~2,M{+ 020 cpaUaI\iV#QYl p1w!|||{.1!WO,`MR5؁&],\ c2$b&`WJe)jU4&NqV/Ъ XXU_)UeJ-#YaZ4{)2~r-| KZ) c)^ӤVp!d#b񴶍鹖Øނ4[ Ft/I"r"2-finIp1뇠Mأ" k2!rj-.11g1t pF&لHͬ~|$,֛HaUBabXGWsb1} O-ؠ,._Ya7AP(E  y a1H`$0AX  ,Feo.*+ mjUM} ~8\)a0OGy+ ZUSYY݇QUCОYx{"0E**)ê#?W xǬ$oIAnn.z&MiѸ~~RU:91I`0QDV8{N> ooOt M[@r4id9Y?ɈhlyP9cxׯop õkװfl߾+V/8a O6$i {;)Su]]_GGGhx q*>Ȫ^a#))M^%a F7|nnn]- I"x2SG mLodn- dizu,D^^+ĜC48[4{l|Šh%ŅcogS7Z98Ⴜ[@hdq=]$`Q2 ̞HUHl IB8ͷ5$KM "`Qaz\s^'`r 1Y`8%JUɘz wE[.YxrVK;}:bNt 4qWF .QiƄ|[FV<[{q=D}a۶mѣTэd̍Ij1lؙmYx +?Ù0m>/yHp5!!>Ll8=nЀE>J۲c(NâaIxE^'c!$zWYg t-*2t&W>ʏ[пW畣򟯮 {TGRe8뺥)JѕU*&hWZ8|*dz .O./O58p@^ _Ƿ1\[L@|ȎB^m~2NyAx(Wc%bre,"Hx'hc(.?.%M7!᭐@" Тcrkף-LcPNA=.!i% B_ "HHa^lH` 6v,Ays;hdgg;XUU뎡V)B#<>Ҳ <6w-N! ra#M4?;wФQ`{t硼Rqsso/7TTT֎-"%>ޥ%&vKp-yO߯/|ܤ 9~Um`z.DbmAQH˱fk$ս~;wUG-PR|ACi{2R0F'0.v'GeE98.!)7u._DɞD-iT.w]Vng?;?Xnڵm6mڠuVprv;ees7ndVM\|.^BЖ :& b0<>n~ޢv ^SG72wfŋaGkaC(KF8c0Eڱ~߼Spt>srМ5HJ^P Ϝ= WUE3-aC$.\M\Ԑ=Wk8wXN(_ z<@B94: fg)Z}xՉj1mjۘº]%%/IZ8Us@͛: [ڡzunG[izҰr1\ BɩwWK0.1FPT:<>m5M`ѪvZ';3I8Z-{k+t8R!x]. w H77b# JXiDXF'0]7$lXRkA3/OAܱd|hX4s^}zf?1a½kK- Ňx8p7Dæ L?/m[5_ A|sӱ+m~1AC`/y!|+~8" wlޗ/ ,. M];r~v;[ksL+vwb_ƶI%BK@+ovU|-{w8`zoq gk]Qּӵ_,I}4Zz3<<\^lp7K79C,_V}agDTUܫWOC]v-G@ӫj&g;lޓk7JТ3<(&\vm9u d^G"vovpk~ҶJK(*@NR"{;\tNn[-pHS3v]Nk[ǯY5d*`_Qfg+]L2:s:m [9]ɭ;FAol^u6!]ۖ}WJQ\aϮ*Ey"6ax/v|Ro|g׿Yd2qF ٛ[& ]qKc1fPgl}+ DAAN 4>T[^wts#AEe v[q)LlGо[~I%[sayt7&=żxbBDX&bxz_)EM;0ZA)Srw{Duo31D4cѓ~8ja$\j곗~/IЁxxdO!6dkO##{[IpѭmZ{ Ս ©6O+Y0^׫bx,(+x])?sUDFw6K.nX=GG>U̜WJaqܹS}:"|j<q VQV,FVR?v+5HT#Qa/}|of=x4>ytucB]㛤LQvf):1k$ou'p~DNc9g;f82ho{7X\Lx%f'{0L鉰}ʞ?Xz l{6/OGfu]Ǿ၁]ѻ|4 M f6!3*ٳo1c:'=!~6Ml5wI+7JD[0.m {?uGS|ZT_bY&2S0wE^>ir|:o,DH`7 ϻ5&8X4\^,4a?r}pKgL.&E}=[MYV01aSb> [rE‚ }صl%i.OwMwל+a1t@6Y my ~Cx>~>?P3癋:í!ekfPzww!>!5b) \3qCzRԧy_ՏWhi_2i5u}z{csZ ^*C(k(w.)4sIsqU#ӨOy_*sۈӳc3d\'O!ܥ}X~{uև\Xp _6^TF.,څ ZeLܧ&?%EŒiL{'vJ EsRҮVʆ290OTkj{'\>趭\TmTLY%miߪv$_1%.k<^.M퉬İa-؏};<2i":w('~,֘` D>xׁSprRױ4ew*]q1?үKa8ࡀbpVmz\7櫱.i1夸vSQ-AMљ ,Qϊ"^T=kQ5A U q}Q <([6ǝ [ޱrx,EbƃC{ KMxk.Zf͚b|8T8v¿oFAQ-^qB k>S&o7 ׌+aΊqZg]Mh nn`"w08귴}r:r5ѵ۳5fG.b[11U-F_ۂ/@5.S~ dzޏwaK|Bᎉ&O?O/"b1OqKVg?:*Vt=Zі/cY]\b{EcW;eKcwUJJ38}﯐ޅ1~[~8$1{@0#=j(4 4^K/,~9D,azclESw<8v4:tkz*ϧj{.b.Oz ylƛ YtѢ:.^/ere҈V n>_>-_ZtoFH-f2ސjcd37qV ;ɩBĖ:X箔 GM1_fO75,497wulL9!:8;!$'@Ʃ[h/.g?2Q@ Uk;m&jHrKLS w]4O?q#Y,Kܦ7ҷBڸ9>vغ?Om\`/\/Og17ldvzSk?=Ϯڱ Mxpg;I˖z h fNǾr+όˈ(MJxq?qzSej\}7iE i]Eز?o| kzSVz ;̇gG4sʭL>fg^μ(.- .k t/h4g~u##ޱ^{6PԈrx_|h4u>D9)xK03'= Z:X5^_˼kD_6rV5?yY4Eoꈗу:aabgK].g p9{lqJ!).~e.(bC$_|,:EM @ڡ9ƶ:O^AV&ELSƃ.L@#XJ]7c3sn"+?A>0g`T>b.͗y`7OfjIx'eۅ[iET 睭:l|XhX vZQ9+5+:iS[.QMㅗ))c<1'ti'u<} #zNKa.UH`/ 6R_¶DQ«+ZU -n1Ѿgf8;h?+Y<2|@h[E; &|Ҡ= nMv\`Vi'lf=^cc=U5~'b,P3ĘDJx=0rPƁQh5sY*H˃ggÚ-"U?`oɼӒ cڄPVv[\SuhZ;˰|@)^b7o܀UZj^cucv^S4 ,JP:GMTc5uY3m͇jSAuo*sQlEDӞeC &üpxvk%ą ޣgXoծfIfǃ"mB`ОXᨬ0 +J`x {{EaIV;&|`>bd.?v֬`Z1BKVfb )21b`W|hTG M񹎺­>=[_ʫ*pABwh?l?Qp"Bbw@z :0^/VĽI?l)/6]L5Jx<M԰pH?qWa0w\<̉!^.\EoרAݙ%sSq>1:V hT.&]rϿhb <OO<"fEjբI8s>]X7ΈQuKFOMѐ(?[՜A>ptѬ5ou1WsR$p^X8F De_«3AL?q 竜Үu34DcQSYQ%x3VHu9zaFg3;Ƚ&jy][ӹ|kYY[6]xkZA1|8`S=Y;{ +'Ac|̎`XsJFGwۖ.xo~aX} .^ܷ׉+pɫhuѹJ}ڠ b.8ʠ1w Z rm$.A1 , A AC !ba1H`$0AX  , A aÇ$07\[Ȃ!b`HAX-da1H`$0AX 2 lٲE#رc幚1y0 1uT! AC !bP TIڲ5SEıE?H#`!9ys ĭ}i?^96qcơ*X?y/hrs}b])..-k \Ŋ41OCްgpIe~Hw>F4*j)0'y܌Ɉ̙!PT\6o?}(`+t tpV y%ڽ;i> k0[LQ^cӦK^Ls{݆cU8M/ÓH~ ~3#مn{%8:ꉿ;o!v-h9 ѣG幪Tj%0'~[Ly|alFl)j3t}c [ZH`%ŶV:1𹮀 +( Q܂RG*HW>Qҗn%%pzJA"[HsuLm898J-1dߖxռrçϕҵ!6MX^8mF[7߰u|RN@^7a:Mvyu\.Mz/1d˛o-+A7o0 L̕yNK/_4+ĩT* \cy:RGcq7lرOLF Bm~ F3qV<刺(I"cj4>[\yaȷBNV8OW`rEv&:/r oN@ӦMpBl޼YKdOܞLLzH>{sqs{eQ]7Hƽ5CvThfi&.H$2Hme$KY%fߡ+s'nIpuq\\F:I ;bb2|6 scDSd-.p50] .pzgv|(-Y!Sұ!jҧ.6'~h.,N*/Quwu]n.C?#If2J餞R>Iƈug|tY!6]H`@Cf1_DZ&`}x͎53r ²4A/*0ܕчtC\Q]{=vF`?xT -իЭsga{}Dr1r"P:G@H6f+V`s玜"yʕ+唚i߶-~:1-g:~\ &ycgqh޼{mVԞdDIL++IK g#&N" ЋY$##{ł T"e~q1x OrN9_2 _ۂÆ`h;f$5ɉ `@$&: cៈ|w0d3F).<}ѢEb;ch٢+S;_/Ç!1eڳa:WS#1T;1h(,4x1Ej`A 8fs텈(E&44TK\-k;g]@Ԃfc:Vf_g?HS=oww* a"eڈ +X1Uxo -F?qw_{\s_}%; iј+;\`Ip,*S"EǬQSO=%&SŅө};|M|{<翉?'bx<8 ~YW@C[x|˶]GJ'8.0.*ӦMnSmt:ĭ+>}u5(6K89:ٺpi KFS-^MRN9;fEA@\\ݜQ@fJ7`B L]i=֝+a×KG' ,^xC$n>ؤpąHA0]$H$!"SuP:AFQk(]]c;{];pwH¢1^䙎r54Akpdn؀fdЭS1(1vHfNj(B^ &{ aǿ #Ną3j|’4;r[O"!n(-1b|4&/A , Ajb0߈_7n5H|ښcgk }xDcb0ՐsYAPx{0>dvCp@3?Auj[5z#3(s+Ĕ? yÞ'1/aTaTKru9YpuuXU"P:GM!rUCkUӞqo),*F[kI>tS G:m^u8uѸĖ-6a`Ma! g^,kT.y^))E3*qpt;F3T4pqv-Cᖙ́2qҙ'[ }R[IC1!yc LRLr:^HMWLFT_xqVJ` 7 !5U$1i;cU.ҽE W "̌L}7Pise+|T5+Q%*)*놉gBUORD&.W%bk0 5yDZ!Y|HQ=LdΏRәDϕU"x2![7YllIB(CX ^:[rL^]GJ% XYPu]:UfyTWb%E",Uc6uu4a $ܑQ@ dFVP t#.D;p "=xD@4)Cf* 51yM`%`)k{ sKSn wTRH IP8yv5QX`ԱxϿıbLrUc:6exVJӬMrA6a?ƽCfuL^R3ˠS5(,\yrf5ϗy.AYl˚%G}@QĽŪbcnZ$X)t6$%2|'p݄L+Oc|M|]mAX=V#0mL^U\7 ˔Lu~rl t-]'͈@#`n՞yѲ[ń4cn޾SՍ 1lly99Be ec$0UEt^嵚;H;FL'~8Nr{+W߰UXd1y="RH~4!@s*+&H +IҨTyu/ s\7P(bNZؤFZ$" &ws| `,9&or"'D $l`(C21h'{C:U4w`) |YZjo| +"uq2fnJtvV|W27&0e-3\dRkҪEuƱcb4G][D:gѼn~;}٤'(E}ZS%Z"A@ZjYV#0kL^:הD*I i0LdTΐG$Mg^ UKo~5{%'ҫÔfD!yX$2 e;->C@z/biDv.r)Fj:;> \N!4 GX=&#ڙHvLBWVǢ2E_ z.x2ٲi*;ͅc,..QӢ_ع+t#r[W\7lrf ,SΩ 舐13)PLNdנ C4pm"L[W /cJ3ZYM2+SSyWjx MXc]엧|&c#G0LY>6K`{u*;lM`VXΝ;r_+WSj]6?xѼY3|Y,"澎n@¯IxnXY6u9Oac k3/\aLtgĖ-./L1c -3nu|WOeAr!L|oLt3LвYw޽{`|?~\lg .]^C\:f)Gbf0&{:}MSk6CI~AZ~mYPyjf-mr6>Ğl gnalXyFXLIE`lmm;`Ȑ!*CT*Ņ/ZHlg n͛co}H/AJ?O_-Mj<~^>bl^*asc=}'#`69O7 9-е4XGE_1=j\ |)N:9۩Υ .Y%i9EC^,tp 5fs텈(E&44TK\-k~x!)=/>}q>cʜ7CstJ}!~}̓7,ԝMO_%Aˤtnψ@'aS4)$`}TтIq iIMMV$yomiKy(/.ht(̝3PZ40FeU նX41{3wu-pVYzJM c{-LyA?r z5 C#p?~`nQ$b:Ac%ۖS[tMhm(1q +ōQ,1&#4Ux 4y TczLoաk92m46Ն G8;bɫsϰca"UptpfusҴbV%}E,@]lB<9`(]#/W9193dl="K=Pg܂L)5]}iKg@Bǒ0{a)χ0^=!ݘdg)`ۯw@p$$^^Gb ²53?E21Umj 0EvFv5KDv qT};j%~8QYfCVժqEy>x_4M Ra /{G%S;w<4UY1{.>$0f^Pz {a8}":o+N`P?'Z|( 2,epTo<N!DVL#[x%bthϦvb~0vډ cU.]^xxli&uW,OcgiV?./Qgh)^J*w˂nг/ЀS"Tbx_QY)er<-g׺} A4V(S 8W~Ⱥvc䅽pCmqؿ~į:w?Lދ `5Pr荼Ϡe^HS4 { D^ \w Zc$1rr T%2 sĺ$ˋ{4gls$#Lˎ Dd-#tmX{,̭"9CI[1pSЦ%_S7WM #)) $'7*Lag#&D /W\g$LR"au&*a P[=*Ɯ0 ǂ)*4m8Vr8{-vӡi7lcqX&/w+ , ҹ H'!y)|}*= i:>ҺpF-5eL)}/* d |YXTdlOS}e l0p:.™®M(żqb7p<۸)*.A3*qpt;F3T4puqJq yQEv1W }tUFK߸mKC_.Ä@yqٵ!&3  fߨJ n = XO  C"Rc'Ly_bIi)B|٣pw K; [ec:MS&*d^xcjU' X/ex8Yɍ}Ԧ!iPX,trmdIxK^&iA<$iF4LAJH˨Z\8Y{ǒ,!.Q]oc76H`r {Q cbJD$0AXFI+0[K1 $0AX  ,`6܋m{ T1y+E#;^˱;>qEA3gw7qfx_}2 fP\셌^XUދ `5S\R.;02OLWFb \*dDaNM~zFB,sDݯJiUOޣP>G?C9~3/?~4_"i3~nyΈkx&ރLq 7&a-mz..G6p*}Br;ڃ/?-FJn"z:Kg?A23m?nAH'aXne%t%'Z CìM_ "/0{rG>cYa {e=zV$0D=]!uYjLBG'i FxY 'B_uϠ.Xvtm{ MY6H+Lt4[ P_\R,n(M՞'3o;k1^'G{;չ4څ_*sDZR7cƸ7{荾ߕz2 e=wojkL3ӖKٯ mN3tG&G88hԁݯNƉ7f%!X&5[Le>5ޓҹ dV=l1މ>ŘKi}i:>IҺO۟L1,bRt.:1|YX=8_{:B`&u0E5L_ &]苧®M(żqb=2TP,J[;b*-ri66w|WFVz3ڨϤ&W~KxEGWxo~/MaBE1jp&3  _iC5,s= ˤ>sSn#cN."̊BO V!0rYm 7<'=D)Wp3bI/ʀ58蚝JP}RHx !ҘxC_gDf%4Mjs-),1{Tރ{;XH ꚠ]t>6WѴ_9\ڗX1 Kڿ݄C)._ |0.ܑW֜s|.3}~j "7z祧Hevr%,ɳJ؆'V4%3Wn! Xs'*硉lS<+9]kIG2 kB̀U o?y-[h.8[P;[(>zld箸-ez1Ltx#~0rt^#?'ǼOctiExT"Hq^@/>ZU"vɜחXf=K}Uש4k⎀ jJz{VugEMS4ΣiɓI.`0.a5]nỵw啨cQSx)3*ao={>/ ų̂uRfVnaÈF>jUƃi9K29N7,צK{+&iA<$jF4LAPsg^ W5w/X%ą#-t L@ny2*a \L AE"(HA+H`$0AX 'WTVJ٤+`ֳ(L5Wd#\%!Fۍ"íSc3y/ Laa!n9F^gP2/V)=SO"/c_;uS-1leG5JdM}D\нGs|&1:G2"t@4)9Bg_q3!t Cw[Ehks 6)s؇b{OMKpnb{G$R*S)zX$>Lߨ0uћChiN\g$v)0x: P(b-cMPncNdAK唪zcc6zEiz9,Gc.tۛ'/Le ɉ sfcn4ECl>XJd OydoHB'f=y r/X`MaB;Y^փ`V$0Ep)RPV5&uLm83 +6;+ 9 Zh)Ba+ҝJXRח}jcη ARN:9۩Υ .DsDZ6$}Qʋf{OOeԓyTS{ V#09w >{-vӡi7lS D'K5$Qn3uW"e>5R:7dV=$/EoO%`K; 6ӱg~l2ajUƃi␴h(,t`6E^$3}qcI&`n2jju^?:vcA 0.NAC !ba1H`$0AX  , A AC !ba1j՛ 5A @E!!ba1H`$0AX  , A AC !ba1H`$0AX  , A AN5?'JGyN?[lC;V3Ѯ@S3 ~FX)J{E%tH%a1H`$0AX h,cxʋOEUxC6_M^VMVH`Zeؑ<1[#"L,ܑţ4+@t`a6$!xB`VezLܱ3Zغ=SQ3zmMdꋟx6Wm/Ui1!$0DD=}Pba=^Jqani{XjVxXYIE2q|*L!>G3,1vLWE aSO=*hZӿ2OJo!"&OOO\1]yoJ$0Ke=*l-czডQJc$$H⓶;V_ !jhD,u2F#.݁0szoF~>#ku͗5ܭ빱gc0oAE SH`Z:QR VW j1,T1l 1R=5X0}ǙM kug_PgFuv4 uv4ّ z A A1FhW34H`j;$0A bcgg'A-6"AB.A AC !ba1H`$0AX  ,F={VAfIENDB`DataLabSimpleClient-0.10.1/doc/images/shots/remote_control_test.png000066400000000000000000001051761463263342600253670ustar00rootroot00000000000000PNG  IHDR"}^sRGBgAMA a pHYsodIDATx^p$}xg4lZFʴV!-kJʝ kfOÇFʊ̌v^{Z؆C%$WMY5k[jh-.E,pߙ^:3_˗Y{/_fnnNS^אַO?M_zw;Ǖߢ7w>Cߒs/~>+jߨ$O୯|ڟ ' DժtNL 8"0qD`!K޿^kwy M/&,_؋?@B#GDZ?yđ[#gA&IL)N6r$γiN<gOD8Pݬă1]tR_`DŽT~y5)7y-^~o?OweZ;/QyϏ0)Sk~MTګoEy.d>y-5! q#V5EE5.B4}ʺVSL7BI%=>;r?jR =b8@–֍A1皍~B9Tꀽitҥ $#r̨g177WVzY$o(w#) ct˗ߏ?{=q8UR":QzُҞSөm9Voi+O7(4IxDߕS#'HޚWZ~ɍ v2*<ӿG-,_/ ]}oӻ wGqݶ]ZzܖW] muV{qsz_L/S%NO\~-:@kh'}jfw`dr3Iqo/W=u}|W^%ŏKp/B\ }y{uՍYֳ={g#\ᕀ$>?)^ӯ;}uۯ]ActRJUeMΔU6+G%UI-/_q_Ot3|"w%Ȟg!&y.V9yV*jUUQ==w:ilۦL& . BF{f6Wǀ+<ϫNRyۣJ7n?UWz1tӫ|_1a鵿}$]|m!-ݒ:pH&&gzO?;}u{Ty}T\B>v)m)+Cs|tҗ* OOdz{PG:+Qbww3l 33dzɖJl҃e2߿N¿!Ӣ=U0rܓc D{^ȋ\qim%&uR $ŋtѩ|`4/^IJL_KZ tRXiː^[tWebT%,֣4֑ٔnvJt弗r_t&/{ﴔ1Mߍcg-HIyƎ<|SSisiT{{ǞZ{F%H6+rtA]yXE*T.ZzGoG7M ~H o߿!KnM+:دI0}!>ƌ7p|6zrd9\u 3N4wJG?52|ƅ7O&/ȿ!L{-9 "GOk׹1N}C->ɿx٣_߲-q :.P.ojSVsZ+ gӊ$IQ8|!U֭2[ru\_znx;֕+.bAj]W-2u>IҜ~1ۥ2ߛTpWNn6ُ>b~qIO;+˗~L˿!'Ec8Q{>&7?K"}3?G7~k=e[һ0,,i)DJ".Õtm?PeO2mӒ+ݤ$i6[;JYU%tˑ¤IIK|agDŽ% >TϜ)֩.3p$s']_K~&|O %C^8Y?}QKekVU~F%]^ SbLZZZmR>Z=%r]:ƻH?'s v(Q o kDqhJsu*=dB./Awry*~|Oa9ҽ`c㓺z3Z`[d _{~ݩqK|oXЙ7` U5!דm??~w-xf5jZ+F\{- WJpҠ{vb*RAZ1n./c\^pSH6r,$/w#ݲL<{r:m0_3/VUywGR.jrXF]_{(~=~Ò[>1{3C "pjM^"ՒZL1T"_CwE?пwKwKDߞ[PR']k?2IK-J5ե_;8Q)}ηr- -fxThgTA7T1?zT}8N܍9X8WluRjr?a*<`?:߮T Fa)Ϭb Y]]ۿ|o6퓟䑗AeכG7<"ɒHS wQ`>#?GtCZ+zmp/^<"pvOf0p|?g?QܸqCz_coA ' $H !h~ToA @&L8"'PɄ$np!cUS,Tگq ȗJtFfƖEf}O\ ;jyfI[?8{|'iۍZ>EP37L$"p|jrfyZ^^ U81mlBbe^9yӖSzHޞA/YEKd{zr^$ 2,j\}}w*8f{58\:ݾ}n޼FTrP0KϟG$_]Ba+]YS?d[Ttt-O~q+dYAzW,u21]m5cu‹\*:\o~!JgG0׶2ހ[En!L204Ӭ.iq˕VA'<yT5x?eIs>W?#}Tϗ|F~JT~}8i>g$g+wEUu.]<l+T^"_|FtT:`+[*\qZRp%vhhhY^KZ]);T_9i0Pvq戶rV*aE3<_:Wm{~<IRAJd #VIMq^/%i;#$_K a9]%9F>3!V<t1|o!W 5o")0@ޕћǬZP$KYB!TT-MW/ɗ3WɕQ!G9]cE+-gXUZQA=AM>Qs+fIdP3{IrΩբ}O|~L8.gW}<38 JΓ<ݿʕ!,ɸ @wj,wig'-rqfK0I;7UGѯ+ߧM^)g\m6KUx9drMNh0a=J`|J%xii{Ȥ|~G?-~L#|~<,*ӶȌ}Ja%\-\$7W4"+"y׹7*L‹?һvSO1pvo# |J/'śQW,˼vڄ_ErEN'-;N&ؖ7hrhrbJ3/c~:^m!qҥIHNn[X05k z{!}[NE5/mҦ9+LZ `zYZ.aڵݺeNksHt%!{kOujU:jTdS//Jʄ|/^uVQ*>ْfWr+PG¢"g&up>$./zSr.Ie+-m kUH^~ocU(|~! ,%}]l3$]?e Oq?͡ҫ驗 Eʗh5UOw?wzTrJȨ?Yߣ>"=z+ߠ'^+?:&`]y Ypqp)5K- "U~tTY .4p ҾFzERZ7r|xIuki3hY!/>O?(뭦^eK_"U_#?cOԦE<6<z٠sJߠvoPu%e0yy_?ѧ^?QKL׿N}t; w@%ݠ# zEa% NtKW[Zz-,ZaVZv.iʗd~v%Y [2T8?*Wu]c9ty4v`|kUz_o};d񍯫lHwЛoI+]pP,e\ ae7քUZ" t+Fvtž~V엊j Qջ7h^b ֞U$|TƵldΚYu؃* s,Dw3IB 2"zz÷{?6~zc#:—{Ez]wȍ+Uꦒ|wt~bBO僃 $J*_Z bADc JUC09DFR>5z͇t9:K˿eJFo~!k#?BPi?օFK֮`$`Xܡ®.oJ{X8"#>!{>Dx{ѣzx }t7ZnB+vVx[)wѳCD[> yΫ~Z|٪~nBeH:]c, |^S$(Ds3A&̐957/N;'Lv@d䕼 =@G<7&}=}dL_u'egCvwqsk>jQ b!ZrVg-i\4`9}we, ʹkfE:|Z-!ر;=ҳQUisseTqMf8HEQ& '$*2ٞK\Z`B_Kϫgo.͜U''4Ris >́L:pIE$:ʌ)6!3ex9qu?H7ԫKBzR~y4;;Kd.uwe@Z#1&c\6rnP`sZ"/oqLn{naf3SJ* lm)I'eRI']A|+c<~ >F: o|'oO>ը2eXŰS 4W̗mQQwR_ g$nfv,b|6AKw v[U6wUŷ?_o&pԘ[#]>}ڒ{+%X__ :F@\N?#>*%]M~wk>>ϧ{~ǩYBUIA 2";~ե䩧ҩZ!TxE UKSdḭ2?+8\B*L_v2 L+Oqh5%抭AwhkQf@h9r:߰ȑZ9\vϯ_u`Iγ9.LJ/G9}N.Z}>RoXWU*(| r$@Aw.rDe봼0KM Ht ޖhvz3@&KOKe^fS #H6VQZ:x?Wex# ?_Ng? x:mIZGJVKyKUҽ;'cVl[T.wV$OuY69++h?nP :9سL|~y>_p&9X}@ HC 7[ `c ͖WE1%\,=n4VmUWEX%熺wrn+6W#"Ljp|Te'eqc oXUv"ܯ_rWz[V"r~mϿ~:a-o@܎|.Q`m{ o3␪% %:''W V)qjFc2atW-cnQѹX\Ɍ!Ò{|]jcez˲(1$\8(1 ~B,rt?'|p -"027W[~-"p!C )YT[8"0qD`L,!5D+d /xdg"s~SbC ǫ&ר6TZ_'\հSe2pRi$T\.e&0@ ǪTMo,LB>|CEaO~~8V ix!ӴoaٷɌm3_zח3d{^9҅I#;yxm\rt/][>_o]Tkbw^$QPD9DוZr-H(?7> 8OO<];MF|@ ǨFD3z{ΖS$+WJWǕ0uVe1@!1|ԑc.oےEEdxEr ^wݒbʗ|+A]$/b%(AKw v#4ڒ=ϛR4\nCe~$=_mI\]>y~Y@ѡ "89XU JpX ]v,!+^엞J5Ò|8ʿv@t.W:-+**TQ;@N݃Z쓿/]Z~ HtoLIA/Fv\/nX L)r:\2??IKx6[o g8>CEfOY~=t}a%^\K𤤻~ bt-78|'ez߿AΖX4uvMMZj å2v]62pF?󝄀x, Rك@M WڤMYJ$/̪^iE|\?*atMиozҴOa!P]nqE hhtN"plf%OYޟ/4R?\"?|G?sn8>y\絎闞VuN߳\z|%; qmZ=c"|}ۍSԶ^OUd>5ݕ/k|=2|oHw2i1/fq |߿7ݻwOYutMu2H(?^h\=d\+VQwmXa!$v#Q[<_8^^ŋU]֭[zOo.]<l+@dTEٽB #7I"0-i5}A%s" )xjˢaO{@&L :X5:ЛiwZ ` IC*.Ѕe]S= q Ik>x@.j~vwW&<2q{ KYu_aUqYL3)ɨ4mF6QwK÷sÅ+2v$[cɶ\y`Zd[v=py0J8i*%S,Q 5@;*HM閵I}pY*\)\Iygv/?Φ]t{bg8er%orJc,`5ΐ1-rKEɣЊqA;±Kz5je$b^y*Zn--qj!ߦӔ۽ʣZ[F j%a=3ʱC%}PxhV*.ݘR51#8^B#cd[D|JE",!+/KO%M;:(E+-)4:M8nI9ˡ->@$-0Qn;peU",ʯؑjlPt8$߿RvG*(xZGht#rH8t[ަ~Ny2ccaCYޮ"ڎwƺHJ`V<=wy𐦳U$ ϖM&---➰VYz0=2qnz3ٽ{ҥKz $*3>[qtMBJ4@Y=4DޢY]<>9-d MTl xdnLҥ+IU/}2tsX#*oɩqECư<2Mx8u^y6/[$8-IVݣ>j5^iF43#IK5{>UBCײ',IZǀKOkymiC%-"&y ǨE' ]yLl㒥r{f/򴾿rȵdtKZ*Tܹbngp|,RY,3~|mSk.Qv۳zF,/vXud~ؘk7Y}Y㦺Fus51u˦;x18&5& J6s`u ǡAU!0z_/d"gZDNppz",ð q1\][ 6ѮYz`b!C 062;Ckʿ~mDsFsO/d. ||~'5Ԥ뛴_ӻUz_Q8L2|À0e1R!d(-$`t'u})SX&qL;` cU-8R2($FӴo۷Ɍm3_OTXڻdaZ>Dl;S؅JRn^Rxo<':ﯷ4| 8FUV DkViJ3p*\iWF@wq rA/|!/cr7VٕnErKEI/R٘ #k޴R2vފ$I9+gmEwKY4|L"p|jtHt2$$%N9XUtiQ qZg%> K+qY\, wKq[\9}dd\EPE9[!+'iQJN׭SV@Oi S&8f5YnY .NiH@vYZZMg2ҕ/5?t$F,T*"+)$ߴ2D^vGbL`#O AnNuΣ-OF/-!|H״ ֊c}S9h}jT^hfÓ)~JSE~|jY\ɓq \=u H\T>U6" K~7ɋݽ CA?ZokVz]x7]䯷/_781.S+|770|VO!גmfXpю& cS*Ku/=%HEPczaeoByv%~}.eۍrQud܄THٕJ˦$Eۭ-GZzǚ^e| /8%2sssM}6ݼySo z%895'L+wEUuۥK ^n<%:妽'8l D0F&n{58\:EdڮYIp6ɿDN/tS 2nojҏ.duqfdYZh@OD]MگcH*G^U18YE*Tϐ8fY//Ӳ^ԤY Sh,Lc0 ”`@oNi,,'!UѴ!g6M~{'&3JΘ~>f ' ݣZ]8obǴv{)!bkt…Ʋ֧z>JQJ:/kΖl$ G!UASb8[ar\xe+\t%* [YTTexO(%گ1 HE7\b$?@zD!;}e UWiwwȍ;81fc1G5lnY[40H@(YCV<> *,LL/Zt\+%|| Ckwn܍WiuCoK Bd†Zv֓sV >o-p;b87ء TY ҶF(nKKZRNwR-tM2iiiwoj%>ݳڻNB.+::4: |] x@QKnvIe7h~jE],ܡFx*P57OtG*lp:$Y!yʲQH*C[ڏGU^{\kw|Y}W_T8ݲsTכݓta*K9Uc=&bN "S/b!KoOe\67hn 4ùF3Qum9r;66h\E:ݲ4 pܧ4Dȵ,Z8@ҽ1 S\Y"눥Uz}W9]ƚ @dIe`JG9Zhzu݃5ZQE$ ٥\ S K9DAN1UQSzUF*^WYE(tW.@B-åtK Us%y=oc yU.f&2"IUg|8i|kei:xk5ڗ .S~y󗉪{~lS0 L1:مlvu4 ȷM^lvk\Fr8FYZ g9zB#:-~DXe9>D34'DO>  enVNNn%]ud*4Dissh:]_^חih4 8@#;nU&---֒7(KFOY qЂ|\?Gz=K'/S@[~J%}dfFwXdqd!Q%>UOUBa5@ (K4sXMM0{;X]ou`Ჴ#!גƫ%58ĵ3jzU٦MY'qz}Xf#C||q3tÓ .e缥˫nx >X_pg78TxZ6O|_%} p:`fu)ۦȺEܖ|r=_UJm;!w”|_( o)f!Kϗߟl|nnmc$0j@$~nE{սM*V$pu71^b^'}>!G\}/m~UM%+aeֱ 2 b)E\p%ϕ 2|م/KCM~` u<]uEQ_Zǯ/=YxO9*M*E9j{B|o]忉*RA (K<]9jƛQv!Sk2ę+kp[q6M)nS@ӻtE- ϖ|MZZZ%^.Us7/>t?O(~ptDxfi!L,̼ZD٬N2\Cq՗GC(S^r٥r0AnKV2#QQ孻7餸/AfY盲GA rV$RQVUݲ78 S~Jj%]u80Ѣ[H71!}g FKvj﷝㨉pȱE$/~05=uB"?U{*oءk4O[\ R)֐ ) Ns`Jh5&4 uwki;破CBG0uBۯ{-AY,`V Hv|-';W`j̯nae64/T]UXWiϑnT[Y:~yKW jI226E{ =y^#h"Hd{ys|!]@io룧}Y6M|Ro+ն]OMZ__R{pVuI_ߤ~M?5[*2==9MȹǏz@\ ]tukU:RvVjS dp2 U8:tPgٹ/~^YZPegUrБ]s2K<峼WvN+&3 /wdazxLΓ>?Lg+_tm[5d=6 }o#>L̹0XK9X EpUkD3!Mg-s;]#hJ ܒKEU@fIw ^wo5O {|oP/T*|1HVA|0I @MCv:(Oץ#R-z.jij)R8mչҝ)6\*ԁG铥* 8T GKu~5^F5NN-;d [`lR<~ϷV(H2p Rۧ*ei0K2&m@mH")K@dID2>+C\&,0y34C5Ep@aMgP'TlTǖ4A@q94Q:)>F 5 .4Yonq!KTՒzcVVvN#⊫Oczc + 49F-:O~%rDW(}0l"jSOi6|#W>LCdJ^4d}3~l~.Ǖ){yY9k:YT_}LWuf$Qǵ|cno9K*:?LC|C%UK|={~I1"G130j F7fyƈYWڶ.B`!jfE'L8"0qD`!85|3iSslD3LZu_MVN뱉 #O 2ST㣞V|Z_Ù,ega|d:e&sc@hi9@E)df2Ŵ}l' ӣc"g>\>/$z9_οf!J7:lۡo9q0v s}%DF* 7޵I)JP4Csa W"5߿_o&w^ʕ+z۷͛zUN%|Vi-zmJ X/Rث)|o,ݏL',ݶ]3y=*@[QHT.;-精̿0W^kx ܺuKҥK"rMoHF5pS 3DxIVB""^䨲Dnҭ*AnqF-mݓCCU[ΕA6 8+%DXG5BKa$t19B6yQSTKj}1_Hi6I^C\TǨZpc<$hWb\BAA (|Jc֋NGאeihԎ"2ki[Hb-"CQ]ڢFwQA*if#'81Ⱥ:|պ.˳ȕQ`@DR [$WrjQ6(4^y۲ġ%C@ 5rpQ-'"_ c@<|\ 95Z\\k r*#"3ϫ7g齽0G-@䘼)oy; {o'h gJYϯ*ͩ1 9& чӻߞ!{#*DW ,wsc ނ?-}m"%K-yphѭ>~NX&ԛޔxdRE?!O?E^߬W?MܓzFcOI\:}S>-̣1#< :)J4W9n<xٽ[ z ;Yڐt^68iNp>W zFv nOҝfxѪ5UZiD3U's.lF1gmqfe(]>oJ~R¸ժjuZ2QaMMbiI}g4 3@EGe'!T5_g<=oJw|;n;lF(}+u?&k&}u?!}u ݲ8h@=+UhQMg)+{"Q+hIgcJBjUKmh~gG=ʪ?h \UUCZ<|__ f>\:li9?C==:LX4Vz}C.h__<9S&TNsD 22~Iߙnfփx&έx2ujKG?Ent񹕊)辤VeLx>e>{.!޽jL#w-C?4׹'.s37O~SPeP!HDY+:"GJ"+qXH}nW0%XkFETPsK+ 7o;a>u/Vn;Rcn'Ӏ+\ tcH|jrURwnG[;m m{]rTEUjlƐ Bd-y9>gi_8k}ԝ%M)#o^#J|<<ȡ2V4@s)AmF|}>' c#'OK)Z蝍 uvR-1kYk W.w witZ7̯j6jq{UEh;ڍ7MƈAհ)W!ʵXzWR.xtWt*.CEeeK}3&?0rȒ.6W I7w\ g8|}rd2Bɿf!J7:lۡoXaN1&~f& jڍbsT,K&P4'G9G.ZI-:Zs4',D[=xHEτ-#+vÊ -%c{o<,YZFjem[lZv&U-x*븶Q kw:Om6KUI:#QvWʯIK>l28=c]!)6!9&orev]ouRuNLRw4po#L -+-ܲ@'T#Acky_I(~0|N1~&?X?gh5ZƤ>jQnVe=EMڔ7g9>cU7 5h>copRdכݻG.][pȿW\[n߾M7o[;^V\Tny3q:]! :J&|kDeOcw^> xW.^n{zb@ζ34Thz[ݻە8R?1Ҵ`x v4c D@$lE^T$uݳWT~XNX`&`@j97t'"_&C L8"0qDN ypFM_8p%j f2|?٢'sx lg`HD`j}5:7&2lMNt^diX}4rYQ #2\T.ɾbx*$D`j~gջZTi)eZg M&S}ӎs(be39唩IÄ0NhX-St]jI$M儆M.5U$lkMncʺx&$$1@37rv&r]ѥGjsZ||XEi}rZ G{,</~{E?bݖ"ĻuUq';=yS;"rMoU_R5K $ (K_*N*\!w' ?ŭlӒRQ BmKغ\4Z+ TE-9WZJ yr\k^ M~*\/nUE|.@-o-EK݃ <ڷE9j D~y#pHA ]~nN!SIw*rR YaM~t[Ry|>/$݌t圫at,G&#?$U8>)n/r p-L;IpGK %ւ(6MD*jE瓣#LTLB}N|: B .Nׯ_YnJ@N۞2]*׶ nP~u}HwEP&Tpg6y\qc!)L"05f ի'9 P>Tk@_J(~0|  ye=OƬ>eT7 <L{^cPbHw!iQ0\[|9yL:|}jṾ>[Eoaf[~}}o,_?k_W}>ÑNI}z_\bF,:3p 19D6VhȺ:ާeEk:eг]C\ R55+1^-X*Ε7@$P|JNn ӯQzt Y[K?% ǐp4mo|&jLc;ŁJ8֬cqN2D=GO 54w)OR3nfTW$36UFV#ZAAH{7dMհ#Lޚu|ay!y&#Z[@xm?&=i 'Z^#kK#k[L+Ǘ/+-:*{aL{N"O-}Sۉ^{>`D;T?;TEz~vORI+/byKa՝"WTG٠xmˢߖj eOc7i/i@d2D~&CN~{?v TN<_/\ ,ik$j)PЊ+;EG.e֢א%jyh;>"7Y C+^Q/-8/'_ʏ 6#"c1ѧNዏ{.cza>z)W=sH!y* tShxsx@{CAq VWw|dҝ5 }@ϵ 빻7҈ztY~;rna#EEe0Ow+KtNԝ*N8"cٯ؛/_ڛo?^ůu.Lw8t,WZPG5˼;;ϕغJd U:H Vih;/%*-|ܰO 1O_tw~ zDst&LeE݋nLmݳIEv2Q6t D2+Gο[C xp\ɻߖ{ܓ}')Q~St:BˑtRݳuDEVz?X+E#xLRzi;F) ]>nԪAՊP".ݘJ<p넣9+ _kz'sƼ6' 9H`7>~ZpOw=El![Fjw_;T*f֘h71",ʸ F)nWx~5D_r-E\`H;zUդޘ0&GWu; 3@E1 {ίw> [~S'Trbɛ8p֚ )05|3Y9t=i&mV@*)寓6E1 8l5M|5q<&o.>I?g{Oxj,ARwLL]mk5>Jϛs0`Fc"g>\>gi_-}ݢf9})^G@mz!s(CDhf愴~X 嚧̸mqŷ>ln9dɌܖC[|d^5g{sV#JEU nț__ puPoKs~/:UVQp4m'[CEfOY~=td ȍw~Bra* Ww(H8ڒq%Y] ȟL&llzSN%ju :f2).+Z9 <2MtRtv@6]LTT=+.NG@빬o;4txupQK5|g|38&9X==ӎpt]i9@dyaF>o +xn99lsEfWU}]ZںE-(msePF` \+%DXG5BEIaO$/*|op*>'@i墄mTb?I7ѷf/MQbs6yQsTKj}N•[sE4M?r+Qǵ 1Pc<$hWQ4b\BWۃ P.ڕHI?J!vk釹?>[ueyS뫱5FFzg.X@/οI7@YG1peWZHF@#b.TG0hN\uP *W.#s-PD茪iL8O5# o)c_1 6%c:8Pc({?8I0@j9:~?Q#"0qD`!85u2!L'[Ei|~y.G>$5"Yo赣[Vo.]<l+FhŤJ*ղqSHkF۰["p``r`\Z 7Hg[`"L4*ꀶ27[ſ(o&U@X@ d Ypf_@$+KNa5Wt3mÇ @wl>aDO5 ԷD݉9n;A K #ket,#K$_ʾD茪iL@ 2!2.^^'΋'J t,Y5HAp#u۱ɯQ6(ȜNZX~[1=$D& pI*2v<hgqvtҙ3J47ANRUʯ[!1B 2-!4"qYjb[Kԯ+_9zxZy:QAEwo9X[齏ЭJ̍vVeg~}u"RV"co i7XڙWh5Zr-Wi.}+׈5IPv#4_y :xǎDvvtCJN\Bj#XiPFeU_WJgWUI [` lq@ 2FI!VhWG2dJ7 j3{ Bw-ct{/T ps3Dsk8ӣv%d>) 2@d<|. ZE ޠ/;ԷTJ;t`yCB> &^'֊ W{K,GS4z5 8hZQJ$ ٥U-, VWzc"v5o~s"S+seRcWѯAń>"Q&DƤWkH֬TcE X 58+һ7'.Jc7:(%6| UjԦ:2$Bv E$dDy-8kOz)߯/ҕk.;`E)ܥƭ˜*p9wug_/LvBcaXuzx$uOEP4zS:&iH@dLdž*uݰ,Irlym]TZ}mIBlLį׭Iתptqm&*O5eZ/PGjġHח) ˔G!Md>omFݟc"g>\>yAyew|N"cZC~DjҾ_s[E$=0}J7v hʻ (@f4=ȵ@wqr (݂EWUH phfAe-B*VU=\$e*q2X2]ܢQ[UpsiTv b&t2e܅ W]1iRvq ᚴʷ-qYXz+[~-N! -e_X~_pϗ/+0-ǏB 0no^; ܺuKovҥ$HJV6:PjTzkӞ4 T/a#p̫Uqb([ x".oFA!RW5uEXo )Vщ,<-y$2Ȁru*V>"wHL7+G~,a(Cbz(zo ?ψʟ"c{O8-"C@EXq/ֺS";N!l 츟/ 0@{~_}w.;z#sgo{=kUjd6N]iJ*]|d2N I^OrJlJ` M29Go}Mo@2d67i}}KmSVKeO5ysTLdW+G^[B cTt8LU˴5lzrT$0"0F F~3zW7,TJ2gN)&3 /f{OGD}<$+#238?d>_yS- $[zC'7хo/瀫1\fGK\:8հ9dze~X暹adq`R r8ݰ|^(QEJw\P^׭9[b5P)f ^o^2"0[VZ:չrX}Z0–JVj7nUr^Rw[ǐ$1r=ZJ:!xeۛy[AsyuykT!ݝE$A9˸ ?J%{͋AXW[֛pNtZsJ<]6&F1"0FU5&/j jIo'ee[V8>DR8he$61긶5BwRxݟX{aQ<I%ޖr 7I[T`fm+nccfWr[D2sssdkpV]rEu}6ݼySo zmZq;cS+oJNk7(!|z7H]֭[zK. Ip6ɿg>.GjB۞G. nY^v-%ITWhf&lDݖl+95{TAhҷeщ rbo Lj@:DNDWU_e~~_lD;zW;sg~g~^ kѯʯoo /@'>wjkFz9S-d o?8&!#PȜ,zρ' )_ t]//[[zW;+t…沲@xIu4SVWzD&&lμ>m*e|f?"gƂp

piIU͞JwAKwR/ioo-MnQ'3@dȔoM_7}7 _ݿwu]?l3?չ\YrH١ $-$EUt+0:H-ޢ"W0i/P^a-+9VioJ{\n\"lk~(G9]E+m !K&XH[uU7XLVQu{ƊFXr@%Ϩd9UΣtejϺ&lTB,r.c% TҶ:ޒlV3K0??,s &?0݊ρ?qN~u''5kFmF??yꩧT>1ZYYiAADPHRU]?ͫp@$v7kcM Rۧ=t}y__ڣnOxbbUn&~oaQ^|F^0$q8%sUDZpԌa y]R?sIx, z`H ) -!nݢov}6}|Z峟,}gE *]>-mܡFo T T!.ݘ֐c]VW!J-IǵKql65wѤ7K3^5^"b0)O?u{ܲp9&O4}򓟤$h+bJ&8}??LO?:_;NOqQ%^V:ެs!W´EpY]Z Xt{tFHfڵlB aRq-ys@gŝBTq!#5Yolq"ݲKpm8ufC)54S ty\_U*! Dgsei}ůI#\C塗0$_W { HnlmgHw5i1ɋ`$߿w%Aԗ.][0,WWURʛѓO>?~Lj^yofZ\\9L[qIZn޼DJf+X$y "+|ut2LT "㤦ۛ{T,ykMP+ÁS'b[T.;\Y'YI20=ZyN3,wzjKQL(Z)]bK[e*G6,roh<=-u! FN[j1q?ЏO?S D3}Ȱ+n{4w0v D0Fଈf&XF$;8U!W,:qƝ?D&F(axm#pzM`u$DN6 VS !ΨI.63e2&yMXd^dz_u, \晷UѫUI^PtNt^6K͙ad2f0...UrFF*et;o|^_Ù,ee:si9wq0%`1\*6(/P"Oz^)S$xg۹/ց^YZPeg!1hvvf eXUDZ>IkdwA!9yHoޔFy^T. *((|yoG~|X 魭:#1"m\[aB1GH[EHTDH]ɰN#%ov\ZR}s@'hnUuَoTtpR=N&"p|fghjriIS+˗^Id9`HkUT`_v9R!W]XM~`-Nyu e6sdErHIC-irr~H˄l/ݽ'rHouuUx 0iY/!U5|3 uRr_.׬*y@nT? @,KsҪы}ԩ,.y]#mHz>9[2=H]` A(A"k6K eZda5"fu1r~鏷@- r˺?|'+T<#%erE(ȹGU1sYo.U-k}s<4!V"߳2p> qOp_~їI0r,$5mXR6W r4-'s"ښ^k BBby7iFjI_[z8Cedz*6v,s&E~ eӵ U։c' \Rritz‹Gؖ1jlA~ F|EG&JoOb#t D2sssM}6ݼySo zw&*;ɻvC˜w0x IZD!RuҥKh81; NQD +k[8 @`d Әe2D`Y02 8Ҿ5 L^ Lܹ'i 0Y 0^>W&cD`!Su{ _8mU5(CUIENDB`DataLabSimpleClient-0.10.1/doc/index.rst000066400000000000000000000037041463263342600200150ustar00rootroot00000000000000DataLab Simple Client User Guide ================================ DataLab is an **open-source platform for scientific and technical data processing and visualization** with unique features designed to meet industrial requirements. It is based on Python scientific libraries (such as NumPy, SciPy or scikit-image) and Qt graphical user interfaces (thanks to `PlotPyStack`_). .. seealso:: For more details, see DataLab `Website`_. DataLab Simple Client (``cdlclient`` package) is a Python library providing a remote proxy to a DataLab application (server). It allows to use DataLab features from a remote computer, and/or from a third-party application. It also provides widgets to embed DataLab features in a Qt application (connection dialog, etc.). For this particular use case, the library relies on `QtPy`_. .. figure:: _static/plotpy-stack-powered.png :align: center :width: 300 px DataLab is powered by `PlotPyStack `_, the scientific Python-Qt visualization and graphical user interface stack. .. note:: DataLab was created by `Codra`_/`Pierre Raybaut`_ in 2023. It is developed and maintained by DataLab open-source project. External resources: .. list-table:: :widths: 20, 80 * - `Home`_ - Project home page * - `PyPI`_ - Python Package Index .. toctree:: :maxdepth: 2 :caption: Contents: installation overview api .. _PlotPyStack: https://github.com/PlotPyStack .. _guidata: https://pypi.python.org/pypi/guidata .. _PlotPy: https://pypi.python.org/pypi/PlotPy .. _QtPy: https://pypi.python.org/pypi/QtPy .. _PyPI: https://pypi.python.org/pypi/cdlclient .. _Home: https://github.com/DataLab-Platform/DataLabSimpleClient/ .. _Website: https://datalab-platform.com/ .. _Codra: https://codra.net/ .. _BSD 3-Clause: https://github.com/DataLab-Platform/DataLabSimpleClient/blob/master/LICENSE .. _Pierre Raybaut: https://github.com/PierreRaybaut/ DataLabSimpleClient-0.10.1/doc/installation.rst000066400000000000000000000011201463263342600213750ustar00rootroot00000000000000Installation ============ Dependencies ------------ .. include:: requirements.rst .. note:: Python 3.11 is the reference for production release How to install -------------- Wheel package: ^^^^^^^^^^^^^^ On any operating system, using pip and the Wheel package is the easiest way to install DataLab on an existing Python distribution: .. code-block:: console $ pip install --upgrade cdlclient-1.0.0-py2.py3-none-any.whl Source package: ^^^^^^^^^^^^^^^ Installing DataLab directly from the source package is straigthforward: .. code-block:: console $ python -m build DataLabSimpleClient-0.10.1/doc/overview.rst000066400000000000000000000075241463263342600205600ustar00rootroot00000000000000Overview ======== DataLab may be controlled remotely using the `XML-RPC`_ protocol which is natively supported by Python (and many other languages). Remote controlling allows to access DataLab main features from a separate process. From an IDE ^^^^^^^^^^^ DataLab may be controlled remotely from an IDE (e.g. `Spyder`_ or any other IDE, or even a Jupyter Notebook) that runs a Python script. It allows to connect to a running DataLab instance, adds a signal and an image, and then runs calculations. This feature is exposed by the `cdlclient.SimpleRemoteProxy` class. From a third-party application ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ DataLab may also be controlled remotely from a third-party application, for the same purpose. If the third-party application is written in Python 3, it may directly use :py:class:`cdlclient.SimpleRemoteProxy` as mentioned above. From another language, it is also achievable, but it requires to implement a XML-RPC client in this language using the same methods of proxy server as in the :py:class:`cdlclient.SimpleRemoteProxy` class. Data (signals and images) may also be exchanged between DataLab and the remote client application, in both directions. The remote client application may be run on the same computer as DataLab or on different computer. In the latter case, the remote client application must know the IP address of the computer running DataLab. The remote client application may be run before or after DataLab. In the latter case, the remote client application must try to connect to DataLab until it succeeds. Supported features ^^^^^^^^^^^^^^^^^^ Supported features are the following: - Switch to signal or image panel - Remove all signals and images - Save current session to a HDF5 file - Open HDF5 files into current session - Browse HDF5 file - Open a signal or an image from file - Add a signal - Add an image - Get object list - Run calculation with parameters Some examples are provided to help implementing such a communication between your application and DataLab: - See module: ``cdlclient.tests.remoteclient_app`` - See module: ``cdlclient.tests.remoteclient_unit`` .. figure:: /images/shots/remote_control_test.png Screenshot of remote client application test (``cdlclient.tests.remoteclient_app``) Example ^^^^^^^ Here is an example in Python 3 of a script that connects to a running DataLab instance, adds a signal and an image, and then runs calculations (the cell structure of the script make it convenient to be used in `Spyder`_ IDE): .. literalinclude:: remote_example.py Additional features ^^^^^^^^^^^^^^^^^^^ For simple remote controlling, :py:class:`cdlclient.SimpleRemoteProxy` may be used. For more advanced remote controlling, the `cdl.RemoteCDLProxy` class provided by the DataLab (``cdl``) package may be used. See DataLab documentation for more details about the ``cdl.RemoteCDLProxy`` class (on the section "Remote control"). .. _XML-RPC: https://docs.python.org/3/library/xmlrpc.html .. _Spyder: https://www.spyder-ide.org/ Connection dialog ^^^^^^^^^^^^^^^^^ The DataLab Simple Client package provides a connection dialog that may be used to connect to a running DataLab instance. It is exposed by the :py:class:`cdlclient.widgets.ConnectionDialog` class. .. figure:: /images/shots/connect_dialog.png Screenshot of connection dialog (``cdlclient.widgets.ConnectionDialog``) Example of use: .. literalinclude:: ../cdlclient/tests/connect_dialog.py Get object dialog ^^^^^^^^^^^^^^^^^ The DataLab Simple Client package provides a dialog that may be used to get an object from a running DataLab instance. It is exposed by the :py:class:`cdlclient.widgets.GetObjectDialog` class. .. figure:: /images/shots/get_object_dialog.png Screenshot of get object dialog (``cdlclient.widgets.GetObjectDialog``) Example of use: .. literalinclude:: ../cdlclient/tests/get_object_dialog.pyDataLabSimpleClient-0.10.1/doc/remote_example.py000066400000000000000000000022141463263342600215270ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Example of remote control of DataLab current session, from a Python script running outside DataLab (e.g. in Spyder) Created on Fri May 12 12:28:56 2023 @author: p.raybaut """ # %% Importing necessary modules # NumPy for numerical array computations: import numpy as np # DataLab remote control client: from cdlclient import SimpleRemoteProxy as RemoteProxy # %% Connecting to DataLab current session proxy = RemoteProxy() # %% Executing commands in DataLab (...) z = np.random.rand(20, 20) proxy.add_image("toto", z) # %% Executing commands in DataLab (...) proxy.toggle_auto_refresh(False) # Turning off auto-refresh x = np.array([1.0, 2.0, 3.0]) y = np.array([4.0, 5.0, -1.0]) proxy.add_signal("toto", x, y) # %% Executing commands in DataLab (...) proxy.compute_derivative() proxy.toggle_auto_refresh(True) # Turning on auto-refresh # %% Executing commands in DataLab (...) proxy.set_current_panel("image") # %% Executing a lot of commands without refreshing DataLab z = np.random.rand(400, 400) proxy.add_image("foobar", z) with proxy.context_no_refresh(): for _idx in range(100): proxy.compute_fft() DataLabSimpleClient-0.10.1/doc/requirements.rst000066400000000000000000000030411463263342600214230ustar00rootroot00000000000000The :mod:`cdlclient` package requires the following Python modules: .. list-table:: :header-rows: 1 :align: left * - Name - Version - Summary * - Python - >=3.8, <4 - * - NumPy - >= 1.21 - * - guidata - >= 3.1 - Optional modules for development: .. list-table:: :header-rows: 1 :align: left * - Name - Version - Summary * - black - - The uncompromising code formatter. * - isort - - A Python utility / library to sort Python imports. * - pylint - - python code static checker * - Coverage - - Code coverage measurement for Python Optional modules for building the documentation: .. list-table:: :header-rows: 1 :align: left * - Name - Version - Summary * - sphinx - - Python documentation generator * - sphinx_intl - - Sphinx utility that make it easy to translate and to apply translation. * - pydata-sphinx-theme - - Bootstrap-based Sphinx theme from the PyData community Optional modules for running test suite: .. list-table:: :header-rows: 1 :align: left * - Name - Version - Summary * - PyQt5 - - Python bindings for the Qt cross platform application toolkit * - QtPy - - Provides an abstraction layer on top of the various Qt bindings (PyQt5/6 and PySide2/6). * - plotpy - - Curve and image plotting tools for Python/Qt applicationsDataLabSimpleClient-0.10.1/doc/update_requirements.py000066400000000000000000000012651463263342600226130ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Update requirements.rst file from pyproject.toml or setup.cfg file Warning: this has to be done manually at release time. It is not done automatically by the sphinx 'conf.py' file because it requires an internet connection to fetch the dependencies metadata - this is not always possible (e.g., when building the documentation on a machine without internet connection like the Debian package management infrastructure). """ from guidata.utils.genreqs import gen_module_req_rst # noqa: E402 import cdlclient if __name__ == "__main__": print("Updating requirements.rst file...", end=" ") gen_module_req_rst(cdlclient, ["Python>=3.8"]) print("done.") DataLabSimpleClient-0.10.1/pyproject.toml000066400000000000000000000061111463263342600203160ustar00rootroot00000000000000# DataLab setup configuration file [build-system] requires = ["setuptools"] build-backend = "setuptools.build_meta" [project] name = "cdlclient" authors = [{ name = "Codra", email = "p.raybaut@codra.fr" }] description = "Python library providing a simple remote client to DataLab (CDL) application" readme = "README.md" license = { file = "LICENSE" } classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "Intended Audience :: Education", "Intended Audience :: Science/Research", "Intended Audience :: End Users/Desktop", "License :: OSI Approved :: BSD License", "Operating System :: MacOS :: MacOS X", "Operating System :: Microsoft :: Windows :: Windows 7", "Operating System :: Microsoft :: Windows :: Windows 8", "Operating System :: Microsoft :: Windows :: Windows 10", "Operating System :: Microsoft :: Windows :: Windows 11", "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Topic :: Scientific/Engineering", "Topic :: Scientific/Engineering :: Image Processing", "Topic :: Scientific/Engineering :: Human Machine Interfaces", "Topic :: Scientific/Engineering :: Visualization", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Software Development :: Widget Sets", ] requires-python = ">=3.8, <4" dependencies = ["NumPy >= 1.21", "guidata >= 3.5"] dynamic = ["version"] [project.urls] Homepage = "https://github.com/DataLab-Platform/DataLabSimpleClient/" Documentation = "https://cdlclient.readthedocs.io/en/latest/" [project.gui-scripts] cdlclient-tests = "cdlclient.tests:run" [project.optional-dependencies] dev = ["ruff", "pylint", "Coverage"] doc = ["sphinx", "sphinx_intl", "pydata-sphinx-theme"] test = ["PyQt5", "QtPy", "plotpy"] [tool.setuptools.packages.find] include = ["cdlclient*"] [tool.setuptools.package-data] "*" = ["*.svg", "*.mo", "*.txt", "*.json"] [tool.setuptools.dynamic] version = { attr = "cdlclient.__version__" } [tool.ruff] exclude = [".git", ".vscode", "build", "dist"] line-length = 88 # Same as Black. indent-width = 4 # Same as Black. target-version = "py38" # Assume Python 3.8 [tool.ruff.lint] # all rules can be found here: https://beta.ruff.rs/docs/rules/ select = ["E", "F", "W", "I"] ignore = [ "E203", # space before : (needed for how black formats slicing) ] [tool.ruff.format] quote-style = "double" # Like Black, use double quotes for strings. indent-style = "space" # Like Black, indent with spaces, rather than tabs. skip-magic-trailing-comma = false # Like Black, respect magic trailing commas. line-ending = "auto" # Like Black, automatically detect the appropriate line ending. [tool.ruff.lint.per-file-ignores] "doc/*" = ["E402"] "svg_icons.py" = ["E501"] "datalab_banner.py" = ["E501", "W292"] DataLabSimpleClient-0.10.1/resources/000077500000000000000000000000001463263342600174155ustar00rootroot00000000000000DataLabSimpleClient-0.10.1/resources/DataLab-Banner-200.png000066400000000000000000000202241463263342600231150ustar00rootroot00000000000000PNG  IHDR;>^) pHYsyy ,tEXtSoftwarewww.inkscape.org< IDATxgxUǿS2)^!$$I",ʺmQW`WWz3!{m&m2$/"63 r/ss%t:1cftf!J@%lfŃVŷZ޻pzrNYZ{Ti ,ŸfMNss,3FAx@{6ٗ~s#Bz͘$"OĪ\k>a̘1+V{Lj\1a} tO?9Mn eƌRAD-oxՁ  œagNj%1̲ٱpO1cW~ E䵊N x8ѧ4:n@̲>U3j'`ivJ0ixjq!H8x?1s?܏dxϵ[uhpip`ǕZoZZRQ|x3JCy]WX[obf/A\ΦUezڻa'`b o;)9y^Q[Wu=ALn8j¹J1czwf,yHR[F$-& Rُ?̐HD"1Q2O5ZW:ړ2[>Co&c5KQ"E}K:⓿'.pT5cPQv8W<͵SV [Ϟ'@@&$8nF^y<\'[2; 3`iy򗷇3G.}-KoZ(DHhp9H݌ҶiCV/S˹y)%;wyo/ϕ㖖S24*3C]-Z@@Rr|}Qq Q@>K%,TP--jJVj'dZfV p9){&3Fc86Kd`өWKj[ PK$hhl$" *łBb8?,\&3 S+ 74PmX drFWY4;S'.@mrѰ`׮H &;f > ܝԷ#/y.ֳP(˯pڐqw:9ghB`-<6]Ҩ4KIhfV ΅؝7d_ƕ'^pR˙8!^ӽ^-܆E-l;vӯ9 "qh Rw__B1~`"H,0p1i(F,~aS BC&d_@ikՔb fPwύu6# d205&!AAPվ# X0/ CةߨzxH~A @oUH[pM٩1VE3~XemSDH O65:9:(2V`SPeI==tjk\Uڭ٩MT?0D *!呅 ^͜yE}?,Y0ne-;thnclX'S*=xi#Bҍ^(Ljjf Ƀ@$W?ݷ!4@a\],楌HJg 9⽤N" \WTV"0:qM.;Y>% 3R䔪5)m'CS-TZ;9RFs~xe]Xc2R U /asЊsE-܆өU\*>C8C/c*;\R`jf}>/8rlnm,9x9ZsؔV:/+:~3k?[\T-Aٯ}Ad"|scـB Q[OB+F%u=KZo5Z:7#}%F:5t>F; }\y$畋q!ѩAGKJh1scą: sztJ[k{7]U3yT\U̲voZ߭wvIy(믏5ښSw\lR_!9r_{"zXյ۝^K~$">w;i#Ԉ67j?hΊeJh})>s~ wAF:la2G$$wUPBG.vlh}0oE,:UʪS#\78 #B8;/g7oq'Vâ62dNI/]0M]0&pIodDxOC+ |CR¥˴ X,T(V nN2E-N/ܨBs7n(oϵѿ{oeڛ5Z=cpqK UQg<"@6Z]WkFen^nL}`}"Ӻ/G8P~gU.r䫣yQYtl .1oݜXy O6}FGOggᾶ1vޗłH;ٲQ؍O~}# qF;ǡa<KGGEfcVR"qT2Z~9'RV^Ʀ;bI%@֧\#V4ZbۦG{Npd0ykXt.!uԷm\+cX׶6kjN4Xyk4wm;]X932Ib$6K.2 _aO߀]6ǀ&߫y}*wo;OSrW`So}m fpiA&D&5Dd4M okz!F9$L{( H!N{ iǙ6%*(/`0¼gy٤u~t n0+&?&US)$mmKUtTd0ܨE$;͙YYKZ_M$Q (8aYlznihCDcYJM#%XN r5j!twbh{&bg[^o8Z?qx3X OoN%[r5aw466X8pTUW#;' #ݲWvIXwKe VD jLLbl`3)FgMtK 3~5D6,k? WYj3Z%)G\E!v߾;‚ofq"BHpOŴo/ VVUbbY&ݻc2[3+_"{4rh4Zk9?'2ݢƒe@c#iB @LAU|ZM\ P,_ګdGĩD b=y{]p͠b:WP(8rjF4nimx_[!sYsI*q0icv@\a,>; L E6#ڈ. ^ϪC*w2tHOWD/7T\d͜զq"J)p{/ n>Na8 9alcyۜ prFd2CM$ֆO/ۅ-a%cj4,#^J5f>q&kuP 3Vee[gQFD( 0=}Oŝ͒ZG^0k8k?{}ƄHݪN)H4CY3OT5v]`e\Qk}*⫛MDsbN 6:w.*Rfsq5z{S<]FPR,n~ِM ?eEJƅ:@jvcCJVIz9ߨJNxÐfUuV<2`MoOF%S+>~SP!lcL"šGȆ ozۃmmh'5wϒmHFxz#,,;` %eߟ) %EqӜG w>U0u\߭Yl '#->i'L7ckF~C{N|vfΉ5ΥU˨h#VS*Qɶ3 *79Y)UK.\cűLt0Qk9Z"6رyzqHW]kh,Efa3<? H\g?w19O回ҿ@eĄx:MƟ_;NgkIv:|4@!Җ%_~'(GLiVc9|VGSVg f78^Ğ H9 (Cx:d9Gs=RqZ*8w|qe[]r *狫$YǮmHFyc|-k:rfݓ=VvԴs\jQW;ˑq 2R*W2kq%mg 岙N?h)&#CJ ՑXR`v_Hd(,Y@*ԯT4+i`%,.=}`Oci 'ߦ?SŤ*%#y0"!%~gW700AlY4ې63\~ |z0kCa)'M;|@9 dϼUb`߲Oo֤W[D6wq^6H=+vioO}~0;bLi.99r(z ̆ey⇎\,R(ՄOE)m H6G3O.hNhuSμ2e6W /p ImcGY]WLnykRrjet[eQ5W?C ~㏓vWi T-3*PK.$Woh}tG[rʭZwzmb[:]wn32t4- ~H Bw`#WjcZ{xU ty ,:EnEĥ(^/>rMT R%ou$7DwGN܎i"%>qaNwap8!;ϙ`bagm8; Z#}y4'Jf˕ڵ"5vBY-n袨Z8ٱV/8O7[6 OuXaI!X^"mXy[gC)ǝ _wtՂ Kc.1_v)־{<'vo7> ꚇ+.iŊWmyqFP>0fHahur&"s Gzd8زO6*j +ocgppц\6C<>-`[l͉)TIw+ϝo{֥7sJ[m+ *$"`3E^^WFm4%7.ܹN%-{SڸpgilF.tqqrYpF.jc[pH۞I";=Gƅ;9 "|ztܖO'*CEI"9emsd6KI4"L?IuzLGai^xd7L$qLlݐcKq YzIP0YJ"OڵKC'zyu݋:{tZz&~OU~9#>(*4D^'YcѺlՁ޶0|T [kHY^}AVkjz"J[㻻"֬7V;O _Κ+0|O,j!UX)dOk t:bAoX}AW_8L[ӉF "NfI/2򛐖׈1"*f)>?7 ]wA 1[^E4cP ]_Uj+Paၪ"& e)}gR*'US:~\شv*j|':03͒^Vv457$mf)o+3`:^]u7H aêH 6WLN }<Uc0'c@Thu:$FC1^qs3df1WYTsyo{/oIpq@&Q\ݎ>$EÒBCYt;zsM,,K 4-œ,n-I5ʌ1h~E}ׅu[·4 ;WFo`D"b{u^+/[t#&# # 4߶t10cf"Ho1EqLiJ~ۧĥ5  xШ(nvH8NCAiJvI+ZKCU܊{PCc@=fdqgԊEɩU M4*>ZG! {8;TJ6;9cњg>O >=L;u%~'b̽BA?=\/u IҀä*Vnɷ1b6v˙%5pckV- xB"I{dG *nE]BBO3# SCpȊM;nul 2>&1 del /s /q *.pyo 1>nul 2>&1 FOR /d /r %%d IN ("__pycache__") DO @IF EXIST "%%d" rd /s /q "%%d" @REM Removing directories related to public repository upload set TEMP=%SCRIPTPATH%\..\..\%LIBNAME%_temp set PUBLIC=%SCRIPTPATH%\..\..\%LIBNAME%_public if exist %TEMP% ( rmdir /s /q %TEMP% ) if exist %PUBLIC% ( rmdir /s /q %PUBLIC% ) @REM Removing files/directories related to Coverage if exist .coverage ( del /q .coverage ) if exist coverage.xml ( del /q coverage.xml ) if exist htmlcov ( rmdir /s /q htmlcov ) del /q .coverage.* 1>nul 2>&1 if exist sitecustomize.py ( del /q sitecustomize.py ) DataLabSimpleClient-0.10.1/scripts/gettext.bat000066400000000000000000000011771463263342600212540ustar00rootroot00000000000000@echo off REM This script was derived from PythonQwt project REM ====================================================== REM Run gettext translation tool REM ====================================================== REM Licensed under the terms of the MIT License REM Copyright (c) 2020 Pierre Raybaut REM (see PythonQwt LICENSE file for more details) REM ====================================================== setlocal call %~dp0utils GetScriptPath SCRIPTPATH call %FUNC% SetPythonPath call %FUNC% UsePython call %FUNC% GetModName MODNAME python -c "from guidata.utils.gettext_helpers import do_%1; do_%1('%MODNAME%')" call %FUNC% EndOfScriptDataLabSimpleClient-0.10.1/scripts/gettext_scan.bat000066400000000000000000000003361463263342600222540ustar00rootroot00000000000000 @echo off call %~dp0utils GetScriptPath SCRIPTPATH call %FUNC% GetLibName LIBNAME call %FUNC% GetModName MODNAME call %FUNC% SetPythonPath call %FUNC% UsePython call %FUNC% GetVersion CDL_VERSION call %~dp0gettext rescanDataLabSimpleClient-0.10.1/scripts/run_coverage.bat000066400000000000000000000017331463263342600222450ustar00rootroot00000000000000@echo off REM This script was derived from PythonQwt project REM ====================================================== REM Run coverage code analysis tool REM ====================================================== REM Licensed under the terms of the MIT License REM Copyright (c) 2020 Pierre Raybaut REM (see PythonQwt LICENSE file for more details) REM ====================================================== setlocal call %~dp0utils GetScriptPath SCRIPTPATH call %FUNC% GetLibName LIBNAME call %FUNC% GetModName MODNAME call %FUNC% SetPythonPath call %FUNC% UsePython if exist sitecustomize.py ( del /q sitecustomize.py ) echo import coverage> sitecustomize.py echo coverage.process_startup()>> sitecustomize.py set COVERAGE_PROCESS_START=%SCRIPTPATH%\..\.coveragerc coverage run -m cdlclient.tests.all_tests %* --timeout 600 @REM coverage report -m coverage combine coverage html start .\htmlcov\index.html if exist sitecustomize.py ( del /q sitecustomize.py ) call %FUNC% EndOfScriptDataLabSimpleClient-0.10.1/scripts/run_pylint.bat000066400000000000000000000012441463263342600217660ustar00rootroot00000000000000@echo off REM This script was derived from PythonQwt project REM ====================================================== REM Run pylint analysis REM ====================================================== REM Licensed under the terms of the MIT License REM Copyright (c) 2020 Pierre Raybaut REM (see PythonQwt LICENSE file for more details) REM ====================================================== setlocal call %~dp0utils GetScriptPath SCRIPTPATH call %FUNC% GetModName MODNAME call %FUNC% SetPythonPath set PYLINT_ARG=%* if "%PYLINT_ARG%"=="" set PYLINT_ARG=--disable=fixme %PYTHON% -m pylint --rcfile=%SCRIPTPATH%\..\.pylintrc %PYLINT_ARG% %MODNAME% call %FUNC% EndOfScriptDataLabSimpleClient-0.10.1/scripts/run_ruff.bat000066400000000000000000000010571463263342600214130ustar00rootroot00000000000000@echo off REM This script was derived from PythonQwt project REM ====================================================== REM Run Ruff analysis REM ====================================================== REM Licensed under the terms of the MIT License REM Copyright (c) 2020 Pierre Raybaut REM (see PythonQwt LICENSE file for more details) REM ====================================================== setlocal call %~dp0utils GetScriptPath SCRIPTPATH call %FUNC% GetModName MODNAME call %FUNC% SetPythonPath call %FUNC% UsePython ruff check call %FUNC% EndOfScriptDataLabSimpleClient-0.10.1/scripts/run_test_launcher.bat000066400000000000000000000011121463263342600233010ustar00rootroot00000000000000@echo off REM This script was derived from PythonQwt project REM ====================================================== REM Test launcher script REM ====================================================== REM Licensed under the terms of the MIT License REM Copyright (c) 2020 Pierre Raybaut REM (see PythonQwt LICENSE file for more details) REM ====================================================== setlocal call %~dp0utils GetScriptPath SCRIPTPATH call %FUNC% SetPythonPath call %FUNC% UsePython call %FUNC% GetModName MODNAME python -m %MODNAME%.tests.__init__ call %FUNC% EndOfScriptDataLabSimpleClient-0.10.1/scripts/run_unittests.bat000066400000000000000000000011141463263342600225050ustar00rootroot00000000000000@echo off REM This script was copied from PythonQwt project REM ====================================================== REM Unattended test script REM ====================================================== REM Licensed under the terms of the MIT License REM Copyright (c) 2020 Pierre Raybaut REM (see PythonQwt LICENSE file for more details) REM ====================================================== setlocal call %~dp0utils GetScriptPath SCRIPTPATH call %FUNC% GetModName MODNAME call %FUNC% SetPythonPath call %FUNC% UsePython python -m %MODNAME%.tests.all_tests call %FUNC% EndOfScriptDataLabSimpleClient-0.10.1/scripts/upgrade_env.bat000066400000000000000000000011261463263342600220610ustar00rootroot00000000000000@echo off REM This script was derived from PythonQwt project REM ====================================================== REM Update environment requirements REM ====================================================== REM Licensed under the terms of the MIT License REM Copyright (c) 2020 Pierre Raybaut REM (see PythonQwt LICENSE file for more details) REM ====================================================== setlocal call %~dp0utils GetScriptPath SCRIPTPATH call %FUNC% UsePython cd %SCRIPTPATH%\.. pip install --upgrade -r dev\requirements.txt pip list > dev\pip_list.txt call %FUNC% EndOfScriptDataLabSimpleClient-0.10.1/scripts/utils.bat000066400000000000000000000055551463263342600207340ustar00rootroot00000000000000@echo off set FUNC=%0 call:%* goto Exit REM ====================================================== REM Utilities for deployment, test and build scripts REM ====================================================== REM Licensed under the terms of the MIT License REM Copyright (c) 2020 Pierre Raybaut REM (see LICENSE file for more details) REM ====================================================== :GetScriptPath set _tmp_=%~dp0 if %_tmp_:~-1%==\ set %1=%_tmp_:~0,-1% EXIT /B 0 :GetLibName pushd %~dp0.. for %%I in (.) do set %1=%%~nxI popd goto:eof :GetModName pushd %~dp0.. for /D %%I in (*) DO ( if exist %%I\__init__.py ( set %1=%%I goto :found_module ) ) :found_module popd goto:eof :GetVersion call:GetModName MODNAME call:SetPythonPath echo import %MODNAME%;print(%MODNAME%.__version__) | python > _tmp_.txt set /p %1=<_tmp_.txt del _tmp_.txt goto:eof :GetVersionWithoutAlphaBeta call:GetModName MODNAME call:SetPythonPath echo import %MODNAME%;ver=%MODNAME%.__version__;print(ver.split("b")[0] if "b" in ver else ver.split("a")[0] if "a" in ver else ver) | python > _tmp_.txt set /p %1=<_tmp_.txt del _tmp_.txt goto:eof :SetPythonPath set ORIGINAL_PYTHONPATH=%PYTHONPATH% cd %~dp0.. for /F "tokens=*" %%A in (.env) do ( set %%A ) set PYTHONPATH=%PYTHONPATH%;%ORIGINAL_PYTHONPATH% goto:eof :UsePython if defined WINPYVER (goto:eof) if not defined PYTHON (goto :nopython) for %%a in ("%PYTHON%") do set "p_dir=%%~dpa" if exist "%p_dir%\activate.bat" (goto :venvpython) for %%a in (%p_dir:~0,-1%) do set "WINPYDIRBASE=%%~dpa" if exist "%WINPYDIRBASE%\scripts\env.bat" (goto :nopython) goto :python :venvpython call "%p_dir%\activate.bat" call :ShowTitle "Using Python Virtual Environment from %p_dir%" goto:eof :python set PATH=%p_dir%;%PATH% call :ShowTitle "Using Python from %p_dir%" goto:eof :nopython if defined WINPYDIRBASE ( call %WINPYDIRBASE%\scripts\env.bat call :ShowTitle "Using WinPython from %WINPYDIRBASE%" ) else ( echo Warning: WINPYDIRBASE environment variable is not defined, switching to system Python echo ******** echo (if nothing happens, that's probably because Python is not installed either: echo please set the WINPYDIRBASE variable to select WinPython directory, or install Python) ) goto:eof :ShowTitle @echo: @echo ========= %~1 ========= @echo: goto:eof :EndOfScript @echo: @echo ********************************************************************************** @echo: if not defined UNATTENDED ( @echo End of script pause ) goto:eof :Exit exit /b