Contrôle à distance#

DataLab peut être contrôlé à distance en utilisant le protocole XML-RPC qui est nativement supporté par Python (et beaucoup d’autres langages). Le contrôle à distance permet d’accéder aux principales fonctionnalités de DataLab à partir d’un processus séparé.

Note

Si vous recherchez une solution alternative légère pour contrôler à distance DataLab (c’est-à-dire sans avoir à installer l’ensemble du package DataLab et ses dépendances sur votre environnement), vous pouvez utiliser le package Sigima qui fournit un client distant simple pour DataLab. Pour l’installer, il suffit d’exécuter : pip install sigima.

Depuis un IDE#

DataLab peut être contrôlé à distance depuis un IDE (par exemple Spyder ou tout autre IDE, ou même un Jupyter Notebook) qui exécute un script Python. Il permet de se connecter à une instance de DataLab en cours d’exécution, d’ajouter un signal et une image, puis d’exécuter des calculs. Cette fonctionnalité est exposée par la classe RemoteProxy fournie dans le module datalab.control.proxy.

Depuis une application tierce#

DataLab peut également être contrôlé à distance depuis une application tierce, dans le même but.

Si l’application tierce est écrite en Python 3, elle peut directement utiliser la classe RemoteProxy comme mentionné ci-dessus. Depuis un autre langage, c’est également réalisable, mais cela nécessite d’implémenter un client XML-RPC dans ce langage en utilisant les mêmes méthodes de serveur proxy que dans la classe RemoteProxy.

Les données (signaux et images) peuvent également être échangées entre DataLab et l’application cliente distante, dans les deux sens.

L’application cliente distante peut être écrite dans n’importe quel langage qui prend en charge XML-RPC. Par exemple, il est possible d’écrire une application cliente distante en Python, Java, C++, C#, etc. L’application cliente distante peut être une application graphique ou une application en ligne de commande.

L’application cliente distante peut être exécutée sur le même ordinateur que DataLab ou sur un ordinateur différent. Dans ce dernier cas, l’application cliente distante doit connaître l’adresse IP de l’ordinateur exécutant DataLab.

L’application cliente distante peut être exécutée avant ou après DataLab. Dans ce dernier cas, l’application cliente distante doit essayer de se connecter à DataLab jusqu’à ce qu’elle réussisse.

Fonctionnalités prises en charge#

Les fonctionnalités prises en charge sont les suivantes :

  • Basculer vers le panneau de signal ou d’image

  • Supprimer tous les signaux et images

  • Enregistrer la session en cours dans un fichier HDF5

  • Ouvrir des fichiers HDF5 dans la session en cours

  • Parcourir un fichier HDF5

  • Ouvrir un signal ou une image à partir d’un fichier

  • Ajouter un signal

  • Ajouter une image

  • Obtenir la liste des objets

  • Exécuter un calcul avec des paramètres

Note

Les objets signal et image sont décrits dans cette section : Modèle de données interne.

Quelques exemples sont fournis pour aider à implémenter une telle communication entre votre application et DataLab :

  • Voir le module : datalab.tests.features.control.remoteclient_app_test

  • Voir le module : datalab.tests.features.control.remoteclient_unit

../../_images/remote_control_test.png

Capture d’écran du test de l’application cliente distante (datalab.tests.features.control.remoteclient_app_test)#

Exemples#

Lorsque vous utilisez Python 3, vous pouvez utiliser directement la classe RemoteProxy comme dans les exemples cités ci-dessus ou ci-dessous.

Voici un exemple en Python 3 d’un script qui se connecte à une instance de DataLab en cours d’exécution, ajoute un signal et une image, puis exécute des calculs (la structure de cellule du script le rend pratique à utiliser dans l’IDE Spyder):

"""
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 sigima.client 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.calc("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.calc("fft")

Voici une réimplémentation de cette classe en Python 2.7 :

# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.

"""
DataLab remote controlling class for Python 2.7
"""

import io
import os
import os.path as osp
import socket
import sys

import ConfigParser as cp
import numpy as np
from guidata.userconfig import get_config_dir
from xmlrpclib import Binary, ServerProxy


def array_to_rpcbinary(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())


def get_datalab_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_dir(), ".DataLab", "DataLab.ini")
    ini = cp.ConfigParser()
    ini.read(fname)
    try:
        return ini.get("main", "rpc_server_port")
    except (cp.NoSectionError, cp.NoOptionError):
        raise ConnectionRefusedError("DataLab has not yet been executed")


class RemoteClient(object):
    """Object representing a proxy/client to DataLab XML-RPC server"""

    def __init__(self):
        self.port = None
        self.serverproxy = None

    def connect(self, port=None):
        """Connect to DataLab XML-RPC server"""
        if port is None:
            port = get_datalab_xmlrpc_port()
        self.port = port
        url = "http://127.0.0.1:" + port
        self.serverproxy = ServerProxy(url, allow_none=True)
        try:
            self.get_version()
        except socket.error:
            raise ConnectionRefusedError("DataLab is currently not running")

    def get_version(self):
        """Return DataLab public version"""
        return self.serverproxy.get_version()

    def close_application(self):
        """Close DataLab application"""
        self.serverproxy.close_application()

    def raise_window(self):
        """Raise DataLab window"""
        self.serverproxy.raise_window()

    def get_current_panel(self):
        """Return current panel"""
        return self.serverproxy.get_current_panel()

    def set_current_panel(self, panel):
        """Switch to panel"""
        self.serverproxy.set_current_panel(panel)

    def reset_all(self):
        """Reset all application data"""
        self.serverproxy.reset_all()

    def toggle_auto_refresh(self, state):
        """Toggle auto refresh state"""
        self.serverproxy.toggle_auto_refresh(state)

    def toggle_show_titles(self, state):
        """Toggle show titles state"""
        self.serverproxy.toggle_show_titles(state)

    def save_to_h5_file(self, filename):
        """Save to a DataLab HDF5 file"""
        self.serverproxy.save_to_h5_file(filename)

    def open_h5_files(self, h5files, import_all, reset_all):
        """Open a DataLab HDF5 file or import from any other HDF5 file"""
        self.serverproxy.open_h5_files(h5files, import_all, reset_all)

    def import_h5_file(self, filename, reset_all):
        """Open DataLab HDF5 browser to Import HDF5 file"""
        self.serverproxy.import_h5_file(filename, reset_all)

    def load_from_files(self, filenames):
        """Open objects from files in current panel (signals/images)"""
        self.serverproxy.load_from_files(filenames)

    def add_signal(
        self,
        title,
        xdata,
        ydata,
        xunit=None,
        yunit=None,
        xlabel=None,
        ylabel=None,
        group_id="",
        set_current=True,
    ):
        """Add signal data to DataLab"""
        xbinary = array_to_rpcbinary(xdata)
        ybinary = array_to_rpcbinary(ydata)
        p = self.serverproxy
        return p.add_signal(
            title, xbinary, ybinary, xunit, yunit, xlabel, ylabel, group_id, set_current
        )

    def add_image(
        self,
        title,
        data,
        xunit=None,
        yunit=None,
        zunit=None,
        xlabel=None,
        ylabel=None,
        zlabel=None,
        group_id="",
        set_current=True,
    ):
        """Add image data to DataLab"""
        zbinary = array_to_rpcbinary(data)
        p = self.serverproxy
        return p.add_image(
            title,
            zbinary,
            xunit,
            yunit,
            zunit,
            xlabel,
            ylabel,
            zlabel,
            group_id,
            set_current,
        )

    def get_object_titles(self, panel=None):
        """Get object (signal/image) list for current panel"""
        return self.serverproxy.get_object_titles(panel)

    def get_object(self, nb_id_title=None, panel=None):
        """Get object (signal/image) by number, id or title"""
        return self.serverproxy.get_object(nb_id_title, panel)

    def get_object_uuids(self, panel=None, group=None):
        """Get object (signal/image) list for current panel"""
        return self.serverproxy.get_object_uuids(panel, group)


def test_remote_client():
    """DataLab Remote Client test"""
    datalab = RemoteClient()
    datalab.connect()
    data = np.array([[3, 4, 5], [7, 8, 0]], dtype=np.uint16)
    datalab.add_image("toto", data)


if __name__ == "__main__":
    test_remote_client()

Boîte de dialogue de connexion#

Le package DataLab fournit également une boîte de dialogue de connexion qui peut être utilisée pour se connecter à une instance de DataLab en cours d’exécution. Elle est exposée par la classe datalab.widgets.connection.ConnectionDialog.

../../_images/connect_dialog.png

Capture d’écran de la boîte de dialogue de connexion (datalab.widgets.connection.ConnectionDialog)#

Exemple d’utilisation :

# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.

"""
DataLab Remote client connection dialog example
"""

# guitest: show,skip

from guidata.qthelpers import qt_app_context
from qtpy import QtWidgets as QW

from datalab.control.proxy import RemoteProxy
from datalab.widgets.connection import ConnectionDialog


def test_dialog():
    """Test connection dialog"""
    proxy = RemoteProxy(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()

API publique#

class datalab.control.remote.RemoteClient[source]#

Objet représentant un proxy/client vers le serveur XML-RPC de DataLab. Cet objet est utilisé pour appeler les fonctions de DataLab à partir d’un script Python.

Exemples

Voici un exemple simple de l’utilisation de RemoteClient dans un script Python ou dans un notebook Jupyter :

>>> from datalab.remote import RemoteClient
>>> proxy = RemoteClient()
>>> proxy.connect()
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"]
<sigima.objects.signal.SignalObj at 0x7f7f1c0b4a90>
>>> proxy[1]
<sigima.objects.signal.SignalObj at 0x7f7f1c0b4a90>
>>> proxy[1].data
array([1., 2., 3.])
set_port(port: str | None = None) None[source]#

Set XML-RPC port to connect to.

Paramètres:

port – XML-RPC port to connect to. If None, the port is automatically retrieved from DataLab configuration.

connect(port: str | None = None, timeout: float | None = None, retries: int | None = None) None[source]#

Try to connect to DataLab XML-RPC server.

Paramètres:
  • port – XML-RPC port to connect to. If not specified, the port is automatically retrieved from DataLab configuration.

  • timeout – Maximum time to wait for connection in seconds. Defaults to 5.0. This is the total maximum wait time, not per retry.

  • retries – Number of retries. Defaults to 10. This parameter is deprecated and will be removed in a future version (kept for backward compatibility).

Lève:
disconnect() None[source]#

Disconnect from DataLab XML-RPC server.

is_connected() bool[source]#

Return True if connected to DataLab XML-RPC server.

get_method_list() list[str][source]#

Return list of available methods.

add_signal(title: str, xdata: ndarray, ydata: ndarray, xunit: str = '', yunit: str = '', xlabel: str = '', ylabel: str = '', group_id: str = '', set_current: bool = True) bool[source]#

Add signal data to DataLab.

Paramètres:
  • title – Signal title

  • xdata – X data

  • ydata – Y data

  • xunit – X unit. Defaults to « « 

  • yunit – Y unit. Defaults to « « 

  • xlabel – X label. Defaults to « « 

  • ylabel – Y label. Defaults to « « 

  • group_id – group id in which to add the signal. Defaults to « « 

  • set_current – if True, set the added signal as current

Renvoie:

True if signal was added successfully, False otherwise

Lève:
add_image(title: str, data: ndarray, xunit: str = '', yunit: str = '', zunit: str = '', xlabel: str = '', ylabel: str = '', zlabel: str = '', group_id: str = '', set_current: bool = True) bool[source]#

Add image data to DataLab.

Paramètres:
  • title – Image title

  • data – Image data

  • xunit – X unit. Defaults to « « 

  • yunit – Y unit. Defaults to « « 

  • zunit – Z unit. Defaults to « « 

  • xlabel – X label. Defaults to « « 

  • ylabel – Y label. Defaults to « « 

  • zlabel – Z label. Defaults to « « 

  • group_id – group id in which to add the image. Defaults to « « 

  • set_current – if True, set the added image as current

Renvoie:

True if image was added successfully, False otherwise

Lève:

ValueError – Invalid data dtype

add_object(obj: SignalObj | ImageObj, group_id: str = '', set_current: bool = True) None[source]#

Add object to DataLab.

Paramètres:
  • obj – Signal or image object

  • group_id – group id in which to add the object. Defaults to « « 

  • set_current – if True, set the added object as current

calc(name: str, param: gds.DataSet | None = None) None[source]#

Call computation feature name

Note

This calls either the processor’s compute_<name> method (if it exists), or the processor’s <name> computation feature (if it is registered, using the run_feature method). It looks for the function in all panels, starting with the current one.

Paramètres:
  • name – Compute function name

  • param – Compute function parameter. Defaults to None.

Lève:

ValueError – unknown function

get_object(nb_id_title: int | str | None = None, panel: str | None = None) SignalObj | ImageObj[source]#

Get object (signal/image) from index.

Paramètres:
  • nb_id_title – Object number, or object id, or object title. Defaults to None (current object).

  • panel – Panel name. Defaults to None (current panel).

Renvoie:

Object

Lève:

KeyError – if object not found

get_object_shapes(nb_id_title: int | str | None = None, panel: str | None = None) list[source]#

Get plot item shapes associated to object (signal/image).

Paramètres:
  • nb_id_title – Object number, or object id, or object title. Defaults to None (current object).

  • panel – Panel name. Defaults to None (current panel).

Renvoie:

List of plot item shapes

add_annotations_from_items(items: list, refresh_plot: bool = True, panel: str | None = None) None[source]#

Add object annotations (annotation plot items).

Paramètres:
  • items – annotation plot items

  • refresh_plot – refresh plot. Defaults to True.

  • panel – panel name (valid values: « signal », « image »). If None, current panel is used.

add_group(title: str, panel: str | None = None, select: bool = False) None#

Add group to DataLab.

Paramètres:
  • title – Group title

  • panel – Panel name (valid values: « signal », « image »). Defaults to None.

  • select – Select the group after creation. Defaults to False.

add_label_with_title(title: str | None = None, panel: str | None = None) None#

Add a label with object title on the associated plot

Paramètres:
  • title – Label title. Defaults to None. If None, the title is the object title.

  • panel – panel name (valid values: « signal », « image »). If None, current panel is used.

close_application() None#

Close DataLab application

context_no_refresh() Generator[None, None, None]#

Return a context manager to temporarily disable auto refresh.

Renvoie:

Context manager

Exemple

>>> with proxy.context_no_refresh():
...     proxy.add_image("image1", data1)
...     proxy.calc("fft")
...     proxy.calc("wiener")
...     proxy.calc("ifft")
...     # Auto refresh is disabled during the above operations
delete_metadata(refresh_plot: bool = True, keep_roi: bool = False) None#

Delete metadata of selected objects

Paramètres:
  • refresh_plot – Refresh plot. Defaults to True.

  • keep_roi – Keep ROI. Defaults to False.

get_current_panel() str#

Return current panel name.

Renvoie:

« signal », « image », « macro »))

Type renvoyé:

Panel name (valid values

get_group_titles_with_object_info() tuple[list[str], list[list[str]], list[list[str]]]#

Return groups titles and lists of inner objects uuids and titles.

Renvoie:

groups titles, lists of inner objects uuids and titles

Type renvoyé:

Tuple

get_object_titles(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.

Paramètres:

panel – panel name (valid values: « signal », « image », « macro »). If None, current data panel is used (i.e. signal or image panel).

Renvoie:

List of object titles

Lève:

ValueError – if panel not found

get_object_uuids(panel: str | None = None, group: int | 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.

Paramètres:
  • panel – panel name (valid values: « signal », « image »). If None, current panel is used.

  • group – Group number, or group id, or group title. Defaults to None (all groups).

Renvoie:

List of object uuids

Lève:

ValueError – if panel not found

classmethod get_public_methods() list[str]#

Return all public methods of the class, except itself.

Renvoie:

List of public methods

get_sel_object_uuids(include_groups: bool = False) list[str]#

Return selected objects uuids.

Paramètres:

include_groups – If True, also return objects from selected groups.

Renvoie:

List of selected objects uuids.

get_version() str#

Return DataLab public version.

Renvoie:

DataLab version

import_h5_file(filename: str, reset_all: bool | None = None) None#

Open DataLab HDF5 browser to Import HDF5 file.

Paramètres:
  • filename – HDF5 file name

  • reset_all – Reset all application data. Defaults to None.

import_macro_from_file(filename: str) None#

Import macro from file

Paramètres:

filename – Filename.

load_from_directory(path: str) None#

Open objects from directory in current panel (signals/images).

Paramètres:

path – directory path

load_from_files(filenames: list[str]) None#

Open objects from files in current panel (signals/images).

Paramètres:

filenames – list of file names

open_h5_files(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.

Paramètres:
  • h5files – List of HDF5 files to open. Defaults to None.

  • import_all – Import all objects from HDF5 files. Defaults to None.

  • reset_all – Reset all application data. Defaults to None.

raise_window() None#

Raise DataLab window

reset_all() None#

Reset all application data

run_macro(number_or_title: int | str | None = None) None#

Run macro.

Paramètres:

number_or_title – Macro number, or macro title. Defaults to None (current macro).

Lève:

ValueError – if macro not found

save_to_h5_file(filename: str) None#

Save to a DataLab HDF5 file.

Paramètres:

filename – HDF5 file name

select_groups(selection: list[int | str] | None = None, panel: str | None = None) None#

Select groups in current panel.

Paramètres:
  • selection – List of group numbers (1 to N), or list of group uuids, or None to select all groups. Defaults to None.

  • panel – panel name (valid values: « signal », « image »). If None, current panel is used. Defaults to None.

select_objects(selection: list[int | str], panel: str | None = None) None#

Select objects in current panel.

Paramètres:
  • 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.

set_current_panel(panel: str) None#

Switch to panel.

Paramètres:

panel – Panel name (valid values: « signal », « image », « macro »))

stop_macro(number_or_title: int | str | None = None) None#

Stop macro.

Paramètres:

number_or_title – Macro number, or macro title. Defaults to None (current macro).

Lève:

ValueError – if macro not found

toggle_auto_refresh(state: bool) None#

Toggle auto refresh state.

Paramètres:

state – Auto refresh state

toggle_show_titles(state: bool) None#

Toggle show titles state.

Paramètres:

state – Show titles state