sigima 0.0.1.dev0__py3-none-any.whl → 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sigima/__init__.py +142 -2
- sigima/client/__init__.py +105 -0
- sigima/client/base.py +780 -0
- sigima/client/remote.py +469 -0
- sigima/client/stub.py +814 -0
- sigima/client/utils.py +90 -0
- sigima/config.py +444 -0
- sigima/data/logo/Sigima.svg +135 -0
- sigima/data/tests/annotations.json +798 -0
- sigima/data/tests/curve_fitting/exponential_fit.txt +511 -0
- sigima/data/tests/curve_fitting/gaussian_fit.txt +100 -0
- sigima/data/tests/curve_fitting/piecewiseexponential_fit.txt +1022 -0
- sigima/data/tests/curve_fitting/polynomial_fit.txt +100 -0
- sigima/data/tests/curve_fitting/twohalfgaussian_fit.txt +1000 -0
- sigima/data/tests/curve_formats/bandwidth.txt +201 -0
- sigima/data/tests/curve_formats/boxcar.npy +0 -0
- sigima/data/tests/curve_formats/datetime.txt +1001 -0
- sigima/data/tests/curve_formats/dynamic_parameters.txt +4000 -0
- sigima/data/tests/curve_formats/fw1e2.txt +301 -0
- sigima/data/tests/curve_formats/fwhm.txt +319 -0
- sigima/data/tests/curve_formats/multiple_curves.csv +29 -0
- sigima/data/tests/curve_formats/noised_saw.mat +0 -0
- sigima/data/tests/curve_formats/oscilloscope.csv +111 -0
- sigima/data/tests/curve_formats/other/other2/recursive2.txt +5 -0
- sigima/data/tests/curve_formats/other/recursive1.txt +5 -0
- sigima/data/tests/curve_formats/paracetamol.npy +0 -0
- sigima/data/tests/curve_formats/paracetamol.txt +1010 -0
- sigima/data/tests/curve_formats/paracetamol_dx_dy.csv +1000 -0
- sigima/data/tests/curve_formats/paracetamol_dy.csv +1001 -0
- sigima/data/tests/curve_formats/pulse1.npy +0 -0
- sigima/data/tests/curve_formats/pulse2.npy +0 -0
- sigima/data/tests/curve_formats/simple.txt +5 -0
- sigima/data/tests/curve_formats/spectrum.mca +2139 -0
- sigima/data/tests/curve_formats/square2.npy +0 -0
- sigima/data/tests/curve_formats/step.npy +0 -0
- sigima/data/tests/fabry-perot1.jpg +0 -0
- sigima/data/tests/fabry-perot2.jpg +0 -0
- sigima/data/tests/flower.npy +0 -0
- sigima/data/tests/image_formats/NF 180338201.scor-data +11003 -0
- sigima/data/tests/image_formats/binary_image.npy +0 -0
- sigima/data/tests/image_formats/binary_image.png +0 -0
- sigima/data/tests/image_formats/centroid_test.npy +0 -0
- sigima/data/tests/image_formats/coordinated_text/complex_image.txt +10011 -0
- sigima/data/tests/image_formats/coordinated_text/complex_ref_image.txt +10010 -0
- sigima/data/tests/image_formats/coordinated_text/image.txt +15 -0
- sigima/data/tests/image_formats/coordinated_text/image2.txt +14 -0
- sigima/data/tests/image_formats/coordinated_text/image_no_unit_no_label.txt +14 -0
- sigima/data/tests/image_formats/coordinated_text/image_with_nan.txt +15 -0
- sigima/data/tests/image_formats/coordinated_text/image_with_unit.txt +14 -0
- sigima/data/tests/image_formats/fiber.csv +480 -0
- sigima/data/tests/image_formats/fiber.jpg +0 -0
- sigima/data/tests/image_formats/fiber.png +0 -0
- sigima/data/tests/image_formats/fiber.txt +480 -0
- sigima/data/tests/image_formats/gaussian_spot_with_noise.npy +0 -0
- sigima/data/tests/image_formats/mr-brain.dcm +0 -0
- sigima/data/tests/image_formats/noised_gaussian.mat +0 -0
- sigima/data/tests/image_formats/sif_reader/nd_lum_image_no_glue.sif +0 -0
- sigima/data/tests/image_formats/sif_reader/raman1.sif +0 -0
- sigima/data/tests/image_formats/tiling.txt +10 -0
- sigima/data/tests/image_formats/uint16.tiff +0 -0
- sigima/data/tests/image_formats/uint8.tiff +0 -0
- sigima/data/tests/laser_beam/TEM00_z_13.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_18.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_23.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_30.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_35.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_40.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_45.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_50.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_55.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_60.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_65.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_70.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_75.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_80.jpg +0 -0
- sigima/enums.py +195 -0
- sigima/io/__init__.py +123 -0
- sigima/io/base.py +311 -0
- sigima/io/common/__init__.py +5 -0
- sigima/io/common/basename.py +164 -0
- sigima/io/common/converters.py +189 -0
- sigima/io/common/objmeta.py +181 -0
- sigima/io/common/textreader.py +58 -0
- sigima/io/convenience.py +157 -0
- sigima/io/enums.py +17 -0
- sigima/io/ftlab.py +395 -0
- sigima/io/image/__init__.py +9 -0
- sigima/io/image/base.py +177 -0
- sigima/io/image/formats.py +1016 -0
- sigima/io/image/funcs.py +414 -0
- sigima/io/signal/__init__.py +9 -0
- sigima/io/signal/base.py +129 -0
- sigima/io/signal/formats.py +290 -0
- sigima/io/signal/funcs.py +723 -0
- sigima/objects/__init__.py +260 -0
- sigima/objects/base.py +937 -0
- sigima/objects/image/__init__.py +88 -0
- sigima/objects/image/creation.py +556 -0
- sigima/objects/image/object.py +524 -0
- sigima/objects/image/roi.py +904 -0
- sigima/objects/scalar/__init__.py +57 -0
- sigima/objects/scalar/common.py +215 -0
- sigima/objects/scalar/geometry.py +502 -0
- sigima/objects/scalar/table.py +784 -0
- sigima/objects/shape.py +290 -0
- sigima/objects/signal/__init__.py +133 -0
- sigima/objects/signal/constants.py +27 -0
- sigima/objects/signal/creation.py +1428 -0
- sigima/objects/signal/object.py +444 -0
- sigima/objects/signal/roi.py +274 -0
- sigima/params.py +405 -0
- sigima/proc/__init__.py +96 -0
- sigima/proc/base.py +381 -0
- sigima/proc/decorator.py +330 -0
- sigima/proc/image/__init__.py +513 -0
- sigima/proc/image/arithmetic.py +335 -0
- sigima/proc/image/base.py +260 -0
- sigima/proc/image/detection.py +519 -0
- sigima/proc/image/edges.py +329 -0
- sigima/proc/image/exposure.py +406 -0
- sigima/proc/image/extraction.py +458 -0
- sigima/proc/image/filtering.py +219 -0
- sigima/proc/image/fourier.py +147 -0
- sigima/proc/image/geometry.py +661 -0
- sigima/proc/image/mathops.py +340 -0
- sigima/proc/image/measurement.py +195 -0
- sigima/proc/image/morphology.py +155 -0
- sigima/proc/image/noise.py +107 -0
- sigima/proc/image/preprocessing.py +182 -0
- sigima/proc/image/restoration.py +235 -0
- sigima/proc/image/threshold.py +217 -0
- sigima/proc/image/transformations.py +393 -0
- sigima/proc/signal/__init__.py +376 -0
- sigima/proc/signal/analysis.py +206 -0
- sigima/proc/signal/arithmetic.py +551 -0
- sigima/proc/signal/base.py +262 -0
- sigima/proc/signal/extraction.py +60 -0
- sigima/proc/signal/features.py +310 -0
- sigima/proc/signal/filtering.py +484 -0
- sigima/proc/signal/fitting.py +276 -0
- sigima/proc/signal/fourier.py +259 -0
- sigima/proc/signal/mathops.py +420 -0
- sigima/proc/signal/processing.py +580 -0
- sigima/proc/signal/stability.py +175 -0
- sigima/proc/title_formatting.py +227 -0
- sigima/proc/validation.py +272 -0
- sigima/tests/__init__.py +7 -0
- sigima/tests/common/__init__.py +0 -0
- sigima/tests/common/arithmeticparam_unit_test.py +26 -0
- sigima/tests/common/basename_unit_test.py +126 -0
- sigima/tests/common/client_unit_test.py +412 -0
- sigima/tests/common/converters_unit_test.py +77 -0
- sigima/tests/common/decorator_unit_test.py +176 -0
- sigima/tests/common/examples_unit_test.py +104 -0
- sigima/tests/common/kernel_normalization_unit_test.py +242 -0
- sigima/tests/common/roi_basic_unit_test.py +73 -0
- sigima/tests/common/roi_geometry_unit_test.py +171 -0
- sigima/tests/common/scalar_builder_unit_test.py +142 -0
- sigima/tests/common/scalar_unit_test.py +991 -0
- sigima/tests/common/shape_unit_test.py +183 -0
- sigima/tests/common/stat_unit_test.py +138 -0
- sigima/tests/common/title_formatting_unit_test.py +338 -0
- sigima/tests/common/tools_coordinates_unit_test.py +60 -0
- sigima/tests/common/transformations_unit_test.py +178 -0
- sigima/tests/common/validation_unit_test.py +205 -0
- sigima/tests/conftest.py +129 -0
- sigima/tests/data.py +998 -0
- sigima/tests/env.py +280 -0
- sigima/tests/guiutils.py +163 -0
- sigima/tests/helpers.py +532 -0
- sigima/tests/image/__init__.py +28 -0
- sigima/tests/image/binning_unit_test.py +128 -0
- sigima/tests/image/blob_detection_unit_test.py +312 -0
- sigima/tests/image/centroid_unit_test.py +170 -0
- sigima/tests/image/check_2d_array_unit_test.py +63 -0
- sigima/tests/image/contour_unit_test.py +172 -0
- sigima/tests/image/convolution_unit_test.py +178 -0
- sigima/tests/image/datatype_unit_test.py +67 -0
- sigima/tests/image/edges_unit_test.py +155 -0
- sigima/tests/image/enclosingcircle_unit_test.py +88 -0
- sigima/tests/image/exposure_unit_test.py +223 -0
- sigima/tests/image/fft2d_unit_test.py +189 -0
- sigima/tests/image/filtering_unit_test.py +166 -0
- sigima/tests/image/geometry_unit_test.py +654 -0
- sigima/tests/image/hough_circle_unit_test.py +147 -0
- sigima/tests/image/imageobj_unit_test.py +737 -0
- sigima/tests/image/morphology_unit_test.py +71 -0
- sigima/tests/image/noise_unit_test.py +57 -0
- sigima/tests/image/offset_correction_unit_test.py +72 -0
- sigima/tests/image/operation_unit_test.py +518 -0
- sigima/tests/image/peak2d_limits_unit_test.py +41 -0
- sigima/tests/image/peak2d_unit_test.py +133 -0
- sigima/tests/image/profile_unit_test.py +159 -0
- sigima/tests/image/projections_unit_test.py +121 -0
- sigima/tests/image/restoration_unit_test.py +141 -0
- sigima/tests/image/roi2dparam_unit_test.py +53 -0
- sigima/tests/image/roi_advanced_unit_test.py +588 -0
- sigima/tests/image/roi_grid_unit_test.py +279 -0
- sigima/tests/image/spectrum2d_unit_test.py +40 -0
- sigima/tests/image/threshold_unit_test.py +91 -0
- sigima/tests/io/__init__.py +0 -0
- sigima/tests/io/addnewformat_unit_test.py +125 -0
- sigima/tests/io/convenience_funcs_unit_test.py +470 -0
- sigima/tests/io/coordinated_text_format_unit_test.py +495 -0
- sigima/tests/io/datetime_csv_unit_test.py +198 -0
- sigima/tests/io/imageio_formats_test.py +41 -0
- sigima/tests/io/ioregistry_unit_test.py +69 -0
- sigima/tests/io/objmeta_unit_test.py +87 -0
- sigima/tests/io/readobj_unit_test.py +130 -0
- sigima/tests/io/readwriteobj_unit_test.py +67 -0
- sigima/tests/signal/__init__.py +0 -0
- sigima/tests/signal/analysis_unit_test.py +135 -0
- sigima/tests/signal/check_1d_arrays_unit_test.py +169 -0
- sigima/tests/signal/convolution_unit_test.py +404 -0
- sigima/tests/signal/datetime_unit_test.py +176 -0
- sigima/tests/signal/fft1d_unit_test.py +303 -0
- sigima/tests/signal/filters_unit_test.py +403 -0
- sigima/tests/signal/fitting_unit_test.py +929 -0
- sigima/tests/signal/fwhm_unit_test.py +111 -0
- sigima/tests/signal/noise_unit_test.py +128 -0
- sigima/tests/signal/offset_correction_unit_test.py +34 -0
- sigima/tests/signal/operation_unit_test.py +489 -0
- sigima/tests/signal/peakdetection_unit_test.py +145 -0
- sigima/tests/signal/processing_unit_test.py +657 -0
- sigima/tests/signal/pulse/__init__.py +112 -0
- sigima/tests/signal/pulse/crossing_times_unit_test.py +123 -0
- sigima/tests/signal/pulse/plateau_detection_unit_test.py +102 -0
- sigima/tests/signal/pulse/pulse_unit_test.py +1824 -0
- sigima/tests/signal/roi_advanced_unit_test.py +392 -0
- sigima/tests/signal/signalobj_unit_test.py +603 -0
- sigima/tests/signal/stability_unit_test.py +431 -0
- sigima/tests/signal/uncertainty_unit_test.py +611 -0
- sigima/tests/vistools.py +1030 -0
- sigima/tools/__init__.py +59 -0
- sigima/tools/checks.py +290 -0
- sigima/tools/coordinates.py +308 -0
- sigima/tools/datatypes.py +26 -0
- sigima/tools/image/__init__.py +97 -0
- sigima/tools/image/detection.py +451 -0
- sigima/tools/image/exposure.py +77 -0
- sigima/tools/image/extraction.py +48 -0
- sigima/tools/image/fourier.py +260 -0
- sigima/tools/image/geometry.py +190 -0
- sigima/tools/image/preprocessing.py +165 -0
- sigima/tools/signal/__init__.py +86 -0
- sigima/tools/signal/dynamic.py +254 -0
- sigima/tools/signal/features.py +135 -0
- sigima/tools/signal/filtering.py +171 -0
- sigima/tools/signal/fitting.py +1171 -0
- sigima/tools/signal/fourier.py +466 -0
- sigima/tools/signal/interpolation.py +70 -0
- sigima/tools/signal/peakdetection.py +126 -0
- sigima/tools/signal/pulse.py +1626 -0
- sigima/tools/signal/scaling.py +50 -0
- sigima/tools/signal/stability.py +258 -0
- sigima/tools/signal/windowing.py +90 -0
- sigima/worker.py +79 -0
- sigima-1.0.0.dist-info/METADATA +233 -0
- sigima-1.0.0.dist-info/RECORD +262 -0
- {sigima-0.0.1.dev0.dist-info → sigima-1.0.0.dist-info}/licenses/LICENSE +29 -29
- sigima-0.0.1.dev0.dist-info/METADATA +0 -60
- sigima-0.0.1.dev0.dist-info/RECORD +0 -6
- {sigima-0.0.1.dev0.dist-info → sigima-1.0.0.dist-info}/WHEEL +0 -0
- {sigima-0.0.1.dev0.dist-info → sigima-1.0.0.dist-info}/top_level.txt +0 -0
sigima/client/stub.py
ADDED
|
@@ -0,0 +1,814 @@
|
|
|
1
|
+
# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Sigima Client Stub/Mock Server (Real Objects)
|
|
5
|
+
---------------------------------------------
|
|
6
|
+
|
|
7
|
+
This module provides a stub XML-RPC server that emulates DataLab's XML-RPC interface
|
|
8
|
+
for testing purposes using real Sigima objects. The stub server allows tests to run
|
|
9
|
+
without requiring a real DataLab instance.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import os
|
|
15
|
+
import threading
|
|
16
|
+
import uuid
|
|
17
|
+
from contextlib import contextmanager
|
|
18
|
+
from socketserver import ThreadingMixIn
|
|
19
|
+
from typing import TYPE_CHECKING
|
|
20
|
+
from xmlrpc.client import Binary
|
|
21
|
+
from xmlrpc.server import SimpleXMLRPCServer
|
|
22
|
+
|
|
23
|
+
from guidata.env import execenv
|
|
24
|
+
|
|
25
|
+
from sigima.client import utils
|
|
26
|
+
from sigima.objects import ImageObj, SignalObj, create_image, create_signal
|
|
27
|
+
|
|
28
|
+
if TYPE_CHECKING:
|
|
29
|
+
from collections.abc import Generator
|
|
30
|
+
|
|
31
|
+
# pylint: disable=invalid-name # Allows short reference names like x, y, ...
|
|
32
|
+
# pylint: disable=duplicate-code
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class MockGroup:
|
|
36
|
+
"""Mock group object."""
|
|
37
|
+
|
|
38
|
+
def __init__(self, title: str, uuid_str: str | None = None):
|
|
39
|
+
self.title = title
|
|
40
|
+
self.uuid = uuid_str or str(uuid.uuid4())
|
|
41
|
+
self.objects: list[str] = [] # List of object UUIDs
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class ThreadingXMLRPCServer(ThreadingMixIn, SimpleXMLRPCServer):
|
|
45
|
+
"""Threading XML-RPC server to handle multiple requests."""
|
|
46
|
+
|
|
47
|
+
daemon_threads = True
|
|
48
|
+
allow_reuse_address = True
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class DataLabStubServer:
|
|
52
|
+
"""Stub XML-RPC server emulating DataLab XML-RPC interface with real objects.
|
|
53
|
+
|
|
54
|
+
This server provides mock implementations of all DataLab XML-RPC methods
|
|
55
|
+
using real SignalObj and ImageObj instances for maximum compatibility.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
port: Port to bind to. If 0, uses a random available port.
|
|
59
|
+
verbose: If True, print verbose debug information.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
def __init__(self, port: int = 0, verbose: bool = True) -> None:
|
|
63
|
+
"""Initialize the stub server.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
port: Port to bind to. If 0, uses a random available port.
|
|
67
|
+
"""
|
|
68
|
+
self.port = port
|
|
69
|
+
self.verbose = verbose
|
|
70
|
+
self.server: ThreadingXMLRPCServer | None = None
|
|
71
|
+
self.server_thread: threading.Thread | None = None
|
|
72
|
+
|
|
73
|
+
# Real Sigima object storage
|
|
74
|
+
self.signals: dict[str, SignalObj] = {} # uuid -> SignalObj
|
|
75
|
+
self.images: dict[str, ImageObj] = {} # uuid -> ImageObj
|
|
76
|
+
self.signal_groups: dict[str, MockGroup] = {} # uuid -> group
|
|
77
|
+
self.image_groups: dict[str, MockGroup] = {} # uuid -> group
|
|
78
|
+
|
|
79
|
+
# Current state
|
|
80
|
+
self.current_panel = "signal" # "signal", "image", or "macro"
|
|
81
|
+
self.selected_objects: list[str] = [] # list of UUIDs
|
|
82
|
+
self.selected_groups: list[str] = [] # list of group UUIDs
|
|
83
|
+
self.auto_refresh = True
|
|
84
|
+
self.show_titles = True
|
|
85
|
+
|
|
86
|
+
# Add default groups
|
|
87
|
+
self._add_default_group("signal")
|
|
88
|
+
self._add_default_group("image")
|
|
89
|
+
|
|
90
|
+
def _add_default_group(self, panel: str) -> None:
|
|
91
|
+
"""Add default group for a panel."""
|
|
92
|
+
group = MockGroup("Group 1")
|
|
93
|
+
if panel == "signal":
|
|
94
|
+
self.signal_groups[group.uuid] = group
|
|
95
|
+
elif panel == "image":
|
|
96
|
+
self.image_groups[group.uuid] = group
|
|
97
|
+
|
|
98
|
+
def start(self) -> int:
|
|
99
|
+
"""Start the XML-RPC server.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
Port number the server is listening on
|
|
103
|
+
"""
|
|
104
|
+
self.server = ThreadingXMLRPCServer(
|
|
105
|
+
("127.0.0.1", self.port), allow_none=True, logRequests=False
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
# Register all methods
|
|
109
|
+
self._register_functions()
|
|
110
|
+
|
|
111
|
+
self.port = self.server.server_address[1]
|
|
112
|
+
|
|
113
|
+
# Start server in a separate thread
|
|
114
|
+
self.server_thread = threading.Thread(
|
|
115
|
+
target=self.server.serve_forever, daemon=True
|
|
116
|
+
)
|
|
117
|
+
self.server_thread.start()
|
|
118
|
+
|
|
119
|
+
execenv.print(f"DataLab stub server started on port {self.port}")
|
|
120
|
+
return self.port
|
|
121
|
+
|
|
122
|
+
def stop(self) -> None:
|
|
123
|
+
"""Stop the XML-RPC server."""
|
|
124
|
+
if self.server:
|
|
125
|
+
self.server.shutdown()
|
|
126
|
+
self.server.server_close()
|
|
127
|
+
if self.server_thread:
|
|
128
|
+
self.server_thread.join(timeout=1.0)
|
|
129
|
+
execenv.print("DataLab stub server stopped")
|
|
130
|
+
|
|
131
|
+
def _register_functions(self) -> None:
|
|
132
|
+
"""Register all XML-RPC functions."""
|
|
133
|
+
# System introspection methods
|
|
134
|
+
self.server.register_introspection_functions()
|
|
135
|
+
|
|
136
|
+
# Basic server methods
|
|
137
|
+
self.server.register_function(self.get_version, "get_version")
|
|
138
|
+
self.server.register_function(self.close_application, "close_application")
|
|
139
|
+
self.server.register_function(self.raise_window, "raise_window")
|
|
140
|
+
|
|
141
|
+
# Panel management
|
|
142
|
+
self.server.register_function(self.get_current_panel, "get_current_panel")
|
|
143
|
+
self.server.register_function(self.set_current_panel, "set_current_panel")
|
|
144
|
+
|
|
145
|
+
# Application control
|
|
146
|
+
self.server.register_function(self.reset_all, "reset_all")
|
|
147
|
+
self.server.register_function(self.toggle_auto_refresh, "toggle_auto_refresh")
|
|
148
|
+
self.server.register_function(self.toggle_show_titles, "toggle_show_titles")
|
|
149
|
+
|
|
150
|
+
# File operations
|
|
151
|
+
self.server.register_function(self.save_to_h5_file, "save_to_h5_file")
|
|
152
|
+
self.server.register_function(self.open_h5_files, "open_h5_files")
|
|
153
|
+
self.server.register_function(self.import_h5_file, "import_h5_file")
|
|
154
|
+
|
|
155
|
+
# Object operations
|
|
156
|
+
self.server.register_function(self.add_signal, "add_signal")
|
|
157
|
+
self.server.register_function(self.add_image, "add_image")
|
|
158
|
+
self.server.register_function(self.get_object_titles, "get_object_titles")
|
|
159
|
+
self.server.register_function(self.get_object_uuids, "get_object_uuids")
|
|
160
|
+
self.server.register_function(self.get_object, "get_object")
|
|
161
|
+
self.server.register_function(self.get_object_shapes, "get_object_shapes")
|
|
162
|
+
self.server.register_function(self.delete_metadata, "delete_metadata")
|
|
163
|
+
|
|
164
|
+
# Selection operations
|
|
165
|
+
self.server.register_function(self.select_objects, "select_objects")
|
|
166
|
+
self.server.register_function(self.select_groups, "select_groups")
|
|
167
|
+
self.server.register_function(self.get_sel_object_uuids, "get_sel_object_uuids")
|
|
168
|
+
self.server.register_function(self.delete_object, "delete_object")
|
|
169
|
+
self.server.register_function(self.duplicate_object, "duplicate_object")
|
|
170
|
+
self.server.register_function(self.copy_metadata, "copy_metadata")
|
|
171
|
+
|
|
172
|
+
# Group operations
|
|
173
|
+
self.server.register_function(self.add_group, "add_group")
|
|
174
|
+
self.server.register_function(self.get_group_titles, "get_group_titles")
|
|
175
|
+
self.server.register_function(
|
|
176
|
+
self.get_group_titles_with_object_info, "get_group_titles_with_object_info"
|
|
177
|
+
)
|
|
178
|
+
self.server.register_function(self.move_up, "move_up")
|
|
179
|
+
self.server.register_function(self.move_down, "move_down")
|
|
180
|
+
self.server.register_function(self.delete_group, "delete_group")
|
|
181
|
+
|
|
182
|
+
# Calculation operations
|
|
183
|
+
self.server.register_function(self.calc, "calc")
|
|
184
|
+
|
|
185
|
+
# Annotation operations
|
|
186
|
+
self.server.register_function(
|
|
187
|
+
self.add_annotations_from_items, "add_annotations_from_items"
|
|
188
|
+
)
|
|
189
|
+
self.server.register_function(self.add_label_with_title, "add_label_with_title")
|
|
190
|
+
|
|
191
|
+
# Basic server methods
|
|
192
|
+
def get_version(self) -> str:
|
|
193
|
+
"""Get DataLab version.
|
|
194
|
+
|
|
195
|
+
Returns a valid PEP 440 version string for testing purposes.
|
|
196
|
+
Since this is a stub server, we return "1.0.0" which is compliant
|
|
197
|
+
with both PEP 440 and the minimum version requirement.
|
|
198
|
+
"""
|
|
199
|
+
return "1.0.0"
|
|
200
|
+
|
|
201
|
+
def close_application(self) -> None:
|
|
202
|
+
"""Close DataLab application."""
|
|
203
|
+
# In stub mode, do nothing
|
|
204
|
+
|
|
205
|
+
def raise_window(self) -> None:
|
|
206
|
+
"""Raise DataLab window."""
|
|
207
|
+
# In stub mode, do nothing
|
|
208
|
+
|
|
209
|
+
# Panel management
|
|
210
|
+
def get_current_panel(self) -> str:
|
|
211
|
+
"""Get current panel name."""
|
|
212
|
+
return self.current_panel
|
|
213
|
+
|
|
214
|
+
def set_current_panel(self, panel: str) -> None:
|
|
215
|
+
"""Set current panel."""
|
|
216
|
+
if panel in ("signal", "image", "macro"):
|
|
217
|
+
self.current_panel = panel
|
|
218
|
+
|
|
219
|
+
# Application control
|
|
220
|
+
def reset_all(self) -> None:
|
|
221
|
+
"""Reset all data."""
|
|
222
|
+
self.signals.clear()
|
|
223
|
+
self.images.clear()
|
|
224
|
+
self.selected_objects.clear()
|
|
225
|
+
self.selected_groups.clear()
|
|
226
|
+
|
|
227
|
+
def toggle_auto_refresh(self, state: bool) -> None:
|
|
228
|
+
"""Toggle auto refresh mode."""
|
|
229
|
+
self.auto_refresh = state
|
|
230
|
+
if self.verbose:
|
|
231
|
+
execenv.print(f"[STUB] Auto-refresh set to: {state}")
|
|
232
|
+
|
|
233
|
+
def toggle_show_titles(self, state: bool) -> None:
|
|
234
|
+
"""Toggle show titles mode."""
|
|
235
|
+
self.show_titles = state
|
|
236
|
+
if self.verbose:
|
|
237
|
+
execenv.print(f"[STUB] Show titles set to: {state}")
|
|
238
|
+
|
|
239
|
+
# File operations
|
|
240
|
+
def save_to_h5_file(self, filename: str) -> None:
|
|
241
|
+
"""Save to a DataLab HDF5 file."""
|
|
242
|
+
if self.verbose:
|
|
243
|
+
execenv.print(f"[STUB] Simulating H5 file save to: {filename}")
|
|
244
|
+
# In stub mode, just create a dummy text file to simulate the save operation
|
|
245
|
+
# This avoids HDF5 dependencies and potential test failures
|
|
246
|
+
try:
|
|
247
|
+
with open(filename, "w", encoding="utf-8") as f:
|
|
248
|
+
f.write("# DataLab stub file (for testing)\n")
|
|
249
|
+
f.write(f"# Signals: {len(self.signals)}\n")
|
|
250
|
+
f.write(f"# Images: {len(self.images)}\n")
|
|
251
|
+
f.write("# This is a dummy file created by the stub server\n")
|
|
252
|
+
if self.verbose:
|
|
253
|
+
execenv.print(
|
|
254
|
+
f"[STUB] Successfully created dummy file with {len(self.signals)} "
|
|
255
|
+
f"signals and {len(self.images)} images"
|
|
256
|
+
)
|
|
257
|
+
except Exception as exc: # pylint: disable=broad-except
|
|
258
|
+
if self.verbose:
|
|
259
|
+
execenv.print(f"[STUB] Failed to create dummy file: {exc}")
|
|
260
|
+
# Ignore errors in stub mode
|
|
261
|
+
|
|
262
|
+
# pylint: disable=unused-argument
|
|
263
|
+
def open_h5_files(
|
|
264
|
+
self,
|
|
265
|
+
h5files: list[str] | None = None,
|
|
266
|
+
import_all: bool | None = None,
|
|
267
|
+
reset_all: bool | None = None,
|
|
268
|
+
) -> None:
|
|
269
|
+
"""Open a DataLab HDF5 file or import from any other HDF5 file."""
|
|
270
|
+
if h5files is None:
|
|
271
|
+
return
|
|
272
|
+
|
|
273
|
+
if self.verbose:
|
|
274
|
+
execenv.print(f"[STUB] Simulating H5 file loading: {h5files}")
|
|
275
|
+
|
|
276
|
+
if reset_all:
|
|
277
|
+
if self.verbose:
|
|
278
|
+
execenv.print("[STUB] Resetting all data before loading")
|
|
279
|
+
self.reset_all()
|
|
280
|
+
|
|
281
|
+
# In stub mode, just simulate loading by creating dummy objects
|
|
282
|
+
# This avoids complex HDF5 file parsing and potential test failures
|
|
283
|
+
for _i, filename in enumerate(h5files):
|
|
284
|
+
# Create a dummy signal for each file
|
|
285
|
+
signal = create_signal(f"Loaded Signal from {os.path.basename(filename)}")
|
|
286
|
+
self.signals[str(uuid.uuid4())] = signal
|
|
287
|
+
if self.verbose:
|
|
288
|
+
execenv.print(f"[STUB] Created dummy signal: {signal.title}")
|
|
289
|
+
|
|
290
|
+
# Create a dummy image for each file
|
|
291
|
+
image = create_image(f"Loaded Image from {os.path.basename(filename)}")
|
|
292
|
+
self.images[str(uuid.uuid4())] = image
|
|
293
|
+
if self.verbose:
|
|
294
|
+
execenv.print(f"[STUB] Created dummy image: {image.title}")
|
|
295
|
+
|
|
296
|
+
def import_h5_file(self, filename: str, reset_all: bool | None = None) -> None:
|
|
297
|
+
"""Open DataLab HDF5 browser to Import HDF5 file."""
|
|
298
|
+
self.open_h5_files([filename], import_all=True, reset_all=reset_all)
|
|
299
|
+
|
|
300
|
+
# Object operations
|
|
301
|
+
# pylint: disable=unused-argument
|
|
302
|
+
def add_signal(
|
|
303
|
+
self,
|
|
304
|
+
title: str,
|
|
305
|
+
xbinary: Binary,
|
|
306
|
+
ybinary: Binary,
|
|
307
|
+
xunit: str = "",
|
|
308
|
+
yunit: str = "",
|
|
309
|
+
xlabel: str = "",
|
|
310
|
+
ylabel: str = "",
|
|
311
|
+
group_id: str = "",
|
|
312
|
+
set_current: bool = True,
|
|
313
|
+
) -> bool:
|
|
314
|
+
"""Add signal data to DataLab."""
|
|
315
|
+
xdata = utils.rpcbinary_to_array(xbinary)
|
|
316
|
+
ydata = utils.rpcbinary_to_array(ybinary)
|
|
317
|
+
|
|
318
|
+
# Create real SignalObj using factory function
|
|
319
|
+
signal = create_signal(title, x=xdata, y=ydata)
|
|
320
|
+
signal.xunit = xunit or ""
|
|
321
|
+
signal.yunit = yunit or ""
|
|
322
|
+
signal.xlabel = xlabel or ""
|
|
323
|
+
signal.ylabel = ylabel or ""
|
|
324
|
+
|
|
325
|
+
# Store signal
|
|
326
|
+
obj_uuid = str(uuid.uuid4())
|
|
327
|
+
self.signals[obj_uuid] = signal
|
|
328
|
+
|
|
329
|
+
# Add to group if specified
|
|
330
|
+
if group_id and group_id in self.signal_groups:
|
|
331
|
+
self.signal_groups[group_id].objects.append(obj_uuid)
|
|
332
|
+
|
|
333
|
+
return True
|
|
334
|
+
|
|
335
|
+
# pylint: disable=unused-argument
|
|
336
|
+
def add_image(
|
|
337
|
+
self,
|
|
338
|
+
title: str,
|
|
339
|
+
zbinary: Binary,
|
|
340
|
+
xunit: str = "",
|
|
341
|
+
yunit: str = "",
|
|
342
|
+
zunit: str = "",
|
|
343
|
+
xlabel: str = "",
|
|
344
|
+
ylabel: str = "",
|
|
345
|
+
zlabel: str = "",
|
|
346
|
+
group_id: str = "",
|
|
347
|
+
set_current: bool = True,
|
|
348
|
+
) -> bool:
|
|
349
|
+
"""Add image data to DataLab."""
|
|
350
|
+
data = utils.rpcbinary_to_array(zbinary)
|
|
351
|
+
|
|
352
|
+
# Create real ImageObj using factory function
|
|
353
|
+
image = create_image(title, data=data)
|
|
354
|
+
image.xunit = xunit or ""
|
|
355
|
+
image.yunit = yunit or ""
|
|
356
|
+
image.zunit = zunit or ""
|
|
357
|
+
image.xlabel = xlabel or ""
|
|
358
|
+
image.ylabel = ylabel or ""
|
|
359
|
+
image.zlabel = zlabel or ""
|
|
360
|
+
|
|
361
|
+
# Store image
|
|
362
|
+
obj_uuid = str(uuid.uuid4())
|
|
363
|
+
self.images[obj_uuid] = image
|
|
364
|
+
|
|
365
|
+
# Add to group if specified
|
|
366
|
+
if group_id and group_id in self.image_groups:
|
|
367
|
+
self.image_groups[group_id].objects.append(obj_uuid)
|
|
368
|
+
|
|
369
|
+
return True
|
|
370
|
+
|
|
371
|
+
def add_object(
|
|
372
|
+
self, obj_data: list[str], group_id: str = "", set_current: bool = True
|
|
373
|
+
) -> bool:
|
|
374
|
+
"""Add object to stub server.
|
|
375
|
+
|
|
376
|
+
Args:
|
|
377
|
+
obj_serialized: Serialized signal or image object
|
|
378
|
+
group_id: group id in which to add the object. Defaults to ""
|
|
379
|
+
set_current: if True, set the added object as current
|
|
380
|
+
"""
|
|
381
|
+
obj: SignalObj | ImageObj = utils.rpcjson_to_dataset(obj_data)
|
|
382
|
+
if self.verbose:
|
|
383
|
+
obj_str = "signal" if isinstance(obj, SignalObj) else "image"
|
|
384
|
+
obj_uuid = str(uuid.uuid4())
|
|
385
|
+
print(f"Added {obj_str} {obj.title} with UUID {obj_uuid}")
|
|
386
|
+
if isinstance(obj, SignalObj):
|
|
387
|
+
self.signals[obj_uuid] = obj
|
|
388
|
+
if group_id and group_id in self.signal_groups:
|
|
389
|
+
self.signal_groups[group_id].objects.append(obj_uuid)
|
|
390
|
+
else:
|
|
391
|
+
self.images[obj_uuid] = obj
|
|
392
|
+
if group_id and group_id in self.image_groups:
|
|
393
|
+
self.image_groups[group_id].objects.append(obj_uuid)
|
|
394
|
+
|
|
395
|
+
def load_from_files(self, filenames: list[str]) -> None:
|
|
396
|
+
"""Load objects from files (stub implementation).
|
|
397
|
+
|
|
398
|
+
Args:
|
|
399
|
+
filenames: list of file names
|
|
400
|
+
"""
|
|
401
|
+
if self.verbose:
|
|
402
|
+
print(
|
|
403
|
+
f"load_from_files called with {len(filenames)} files "
|
|
404
|
+
"(stub - not implemented)"
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
def load_from_directory(self, path: str) -> None:
|
|
408
|
+
"""Load objects from directory (stub implementation).
|
|
409
|
+
|
|
410
|
+
Args:
|
|
411
|
+
path: directory path
|
|
412
|
+
"""
|
|
413
|
+
if self.verbose:
|
|
414
|
+
print(
|
|
415
|
+
f"load_from_directory called with path: {path} (stub - not implemented)"
|
|
416
|
+
)
|
|
417
|
+
|
|
418
|
+
def get_object_titles(self, panel: str | None = None) -> list[str]:
|
|
419
|
+
"""Get object titles for panel."""
|
|
420
|
+
panel = panel or self.current_panel
|
|
421
|
+
if panel == "signal":
|
|
422
|
+
return [signal.title for signal in self.signals.values()]
|
|
423
|
+
if panel == "image":
|
|
424
|
+
return [image.title for image in self.images.values()]
|
|
425
|
+
return []
|
|
426
|
+
|
|
427
|
+
# pylint: disable=unused-argument
|
|
428
|
+
def get_object_uuids(
|
|
429
|
+
self, panel: str | None = None, group: int | str | None = None
|
|
430
|
+
) -> list[str]:
|
|
431
|
+
"""Get object UUIDs for panel."""
|
|
432
|
+
panel = panel or self.current_panel
|
|
433
|
+
if panel == "signal":
|
|
434
|
+
return list(self.signals.keys())
|
|
435
|
+
if panel == "image":
|
|
436
|
+
return list(self.images.keys())
|
|
437
|
+
return []
|
|
438
|
+
|
|
439
|
+
def get_object(self, uuid_str: str, panel: str | None = None) -> list[str] | None:
|
|
440
|
+
"""Get object by UUID, index, or title."""
|
|
441
|
+
panel = panel or self.current_panel
|
|
442
|
+
|
|
443
|
+
# Get the appropriate objects dictionary
|
|
444
|
+
if panel == "signal":
|
|
445
|
+
objects = self.signals
|
|
446
|
+
object_list = list(self.signals.keys())
|
|
447
|
+
elif panel == "image":
|
|
448
|
+
objects = self.images
|
|
449
|
+
object_list = list(self.images.keys())
|
|
450
|
+
else:
|
|
451
|
+
return None
|
|
452
|
+
|
|
453
|
+
# Try to resolve uuid_str as UUID first
|
|
454
|
+
if uuid_str in objects:
|
|
455
|
+
obj = objects[uuid_str]
|
|
456
|
+
else:
|
|
457
|
+
# Try to resolve as 1-based index
|
|
458
|
+
try:
|
|
459
|
+
index = int(uuid_str) - 1 # Convert to 0-based
|
|
460
|
+
if 0 <= index < len(object_list):
|
|
461
|
+
uuid_key = object_list[index]
|
|
462
|
+
obj = objects[uuid_key]
|
|
463
|
+
else:
|
|
464
|
+
return None
|
|
465
|
+
except (ValueError, TypeError):
|
|
466
|
+
# Try to find by title
|
|
467
|
+
obj = None
|
|
468
|
+
for object_instance in objects.values():
|
|
469
|
+
if object_instance.title == uuid_str:
|
|
470
|
+
obj = object_instance
|
|
471
|
+
break
|
|
472
|
+
if obj is None:
|
|
473
|
+
return None
|
|
474
|
+
|
|
475
|
+
# Use standard serialization with real objects
|
|
476
|
+
return utils.dataset_to_rpcjson(obj)
|
|
477
|
+
|
|
478
|
+
# pylint: disable=unused-argument
|
|
479
|
+
def get_object_shapes(
|
|
480
|
+
self, uuid_str: str, panel: str | None = None
|
|
481
|
+
) -> list[dict] | None:
|
|
482
|
+
"""Get object shapes."""
|
|
483
|
+
obj = self.signals.get(uuid_str) or self.images.get(uuid_str)
|
|
484
|
+
if obj is None:
|
|
485
|
+
return None
|
|
486
|
+
return [roi.to_dict() for roi in obj.roi]
|
|
487
|
+
|
|
488
|
+
def delete_metadata(self, uuid_str: str, key: str) -> bool:
|
|
489
|
+
"""Delete metadata entry for object."""
|
|
490
|
+
obj = self.signals.get(uuid_str) or self.images.get(uuid_str)
|
|
491
|
+
if obj is None:
|
|
492
|
+
return False
|
|
493
|
+
if key in obj.metadata:
|
|
494
|
+
del obj.metadata[key]
|
|
495
|
+
return True
|
|
496
|
+
return False
|
|
497
|
+
|
|
498
|
+
# Selection operations
|
|
499
|
+
def select_objects(
|
|
500
|
+
self, selection: list[int | str], panel: str | None = None
|
|
501
|
+
) -> None:
|
|
502
|
+
"""Select objects by indices or UUIDs."""
|
|
503
|
+
panel = panel or self.current_panel
|
|
504
|
+
if panel == "signal":
|
|
505
|
+
uuids = list(self.signals.keys())
|
|
506
|
+
elif panel == "image":
|
|
507
|
+
uuids = list(self.images.keys())
|
|
508
|
+
else:
|
|
509
|
+
return
|
|
510
|
+
|
|
511
|
+
selected_uuids = []
|
|
512
|
+
for item in selection:
|
|
513
|
+
if isinstance(item, str):
|
|
514
|
+
# Item is a UUID
|
|
515
|
+
if item in uuids:
|
|
516
|
+
selected_uuids.append(item)
|
|
517
|
+
elif isinstance(item, int):
|
|
518
|
+
# Item is a 1-based index
|
|
519
|
+
index = item - 1 # Convert to 0-based
|
|
520
|
+
if 0 <= index < len(uuids):
|
|
521
|
+
selected_uuids.append(uuids[index])
|
|
522
|
+
|
|
523
|
+
self.selected_objects = selected_uuids
|
|
524
|
+
|
|
525
|
+
# pylint: disable=unused-argument
|
|
526
|
+
def get_sel_object_uuids(self, include_groups: bool = False) -> list[str]:
|
|
527
|
+
"""Return selected objects uuids."""
|
|
528
|
+
return self.selected_objects.copy()
|
|
529
|
+
|
|
530
|
+
def select_groups(self, selection: list[int], panel: str | None = None) -> None:
|
|
531
|
+
"""Select groups by indices."""
|
|
532
|
+
panel = panel or self.current_panel
|
|
533
|
+
if panel == "signal":
|
|
534
|
+
group_uuids = list(self.signal_groups.keys())
|
|
535
|
+
elif panel == "image":
|
|
536
|
+
group_uuids = list(self.image_groups.keys())
|
|
537
|
+
else:
|
|
538
|
+
return
|
|
539
|
+
|
|
540
|
+
self.selected_groups = [
|
|
541
|
+
group_uuids[i] for i in selection if 0 <= i < len(group_uuids)
|
|
542
|
+
]
|
|
543
|
+
|
|
544
|
+
def delete_object(self, uuid_str: str) -> bool:
|
|
545
|
+
"""Delete object by UUID."""
|
|
546
|
+
if uuid_str in self.signals:
|
|
547
|
+
del self.signals[uuid_str]
|
|
548
|
+
return True
|
|
549
|
+
if uuid_str in self.images:
|
|
550
|
+
del self.images[uuid_str]
|
|
551
|
+
return True
|
|
552
|
+
return False
|
|
553
|
+
|
|
554
|
+
def duplicate_object(self, uuid_str: str) -> str | None:
|
|
555
|
+
"""Duplicate object and return new UUID."""
|
|
556
|
+
obj = self.signals.get(uuid_str) or self.images.get(uuid_str)
|
|
557
|
+
if obj is None:
|
|
558
|
+
return None
|
|
559
|
+
|
|
560
|
+
# Create a copy using serialization/deserialization
|
|
561
|
+
json_data = utils.dataset_to_rpcjson(obj)
|
|
562
|
+
new_obj: SignalObj | ImageObj = utils.rpcjson_to_dataset(json_data)
|
|
563
|
+
obj_uuid = str(uuid.uuid4())
|
|
564
|
+
new_obj.title = f"{obj.title} (copy)"
|
|
565
|
+
|
|
566
|
+
# Store the copy
|
|
567
|
+
if isinstance(obj, SignalObj):
|
|
568
|
+
self.signals[obj_uuid] = new_obj
|
|
569
|
+
else:
|
|
570
|
+
self.images[obj_uuid] = new_obj
|
|
571
|
+
return obj_uuid
|
|
572
|
+
|
|
573
|
+
def copy_metadata(self, src_uuid: str, dst_uuid: str) -> bool:
|
|
574
|
+
"""Copy metadata from source to destination object."""
|
|
575
|
+
src_obj = self.signals.get(src_uuid) or self.images.get(src_uuid)
|
|
576
|
+
dst_obj = self.signals.get(dst_uuid) or self.images.get(dst_uuid)
|
|
577
|
+
|
|
578
|
+
if src_obj is None or dst_obj is None:
|
|
579
|
+
return False
|
|
580
|
+
|
|
581
|
+
dst_obj.metadata.update(src_obj.metadata)
|
|
582
|
+
return True
|
|
583
|
+
|
|
584
|
+
# Group operations
|
|
585
|
+
# pylint: disable=unused-argument
|
|
586
|
+
def add_group(
|
|
587
|
+
self, title: str, panel: str | None = None, select: bool = False
|
|
588
|
+
) -> str:
|
|
589
|
+
"""Add group and return UUID."""
|
|
590
|
+
panel = panel or self.current_panel
|
|
591
|
+
group = MockGroup(title)
|
|
592
|
+
|
|
593
|
+
if panel == "signal":
|
|
594
|
+
self.signal_groups[group.uuid] = group
|
|
595
|
+
elif panel == "image":
|
|
596
|
+
self.image_groups[group.uuid] = group
|
|
597
|
+
|
|
598
|
+
return group.uuid
|
|
599
|
+
|
|
600
|
+
def get_group_titles_with_object_info(
|
|
601
|
+
self,
|
|
602
|
+
) -> tuple[list[str], list[list[str]], list[list[str]]]:
|
|
603
|
+
"""Return groups titles and lists of inner objects uuids and titles."""
|
|
604
|
+
panel = self.current_panel
|
|
605
|
+
if panel == "signal":
|
|
606
|
+
groups = self.signal_groups
|
|
607
|
+
objects = self.signals
|
|
608
|
+
elif panel == "image":
|
|
609
|
+
groups = self.image_groups
|
|
610
|
+
objects = self.images
|
|
611
|
+
else:
|
|
612
|
+
return ([], [], [])
|
|
613
|
+
|
|
614
|
+
group_titles = []
|
|
615
|
+
group_uuids_lists = []
|
|
616
|
+
group_titles_lists = []
|
|
617
|
+
|
|
618
|
+
for group in groups.values():
|
|
619
|
+
group_titles.append(group.title)
|
|
620
|
+
|
|
621
|
+
# Get objects in this group
|
|
622
|
+
object_uuids = [uuid for uuid in group.objects if uuid in objects]
|
|
623
|
+
object_titles = [
|
|
624
|
+
objects[uuid].title for uuid in object_uuids if uuid in objects
|
|
625
|
+
]
|
|
626
|
+
|
|
627
|
+
group_uuids_lists.append(object_uuids)
|
|
628
|
+
group_titles_lists.append(object_titles)
|
|
629
|
+
|
|
630
|
+
return (group_titles, group_uuids_lists, group_titles_lists)
|
|
631
|
+
|
|
632
|
+
def get_group_titles(self, panel: str | None = None) -> list[str]:
|
|
633
|
+
"""Get group titles."""
|
|
634
|
+
panel = panel or self.current_panel
|
|
635
|
+
if panel == "signal":
|
|
636
|
+
return [group.title for group in self.signal_groups.values()]
|
|
637
|
+
if panel == "image":
|
|
638
|
+
return [group.title for group in self.image_groups.values()]
|
|
639
|
+
return []
|
|
640
|
+
|
|
641
|
+
def move_up(self, uuid_str: str) -> bool: # pylint: disable=unused-argument
|
|
642
|
+
"""Move object up in list."""
|
|
643
|
+
# In stub mode, just return success
|
|
644
|
+
return True
|
|
645
|
+
|
|
646
|
+
def move_down(self, uuid_str: str) -> bool: # pylint: disable=unused-argument
|
|
647
|
+
"""Move object down in list."""
|
|
648
|
+
# In stub mode, just return success
|
|
649
|
+
return True
|
|
650
|
+
|
|
651
|
+
def delete_group(self, group_id: str, panel: str | None = None) -> bool:
|
|
652
|
+
"""Delete group."""
|
|
653
|
+
panel = panel or self.current_panel
|
|
654
|
+
if panel == "signal" and group_id in self.signal_groups:
|
|
655
|
+
del self.signal_groups[group_id]
|
|
656
|
+
return True
|
|
657
|
+
if panel == "image" and group_id in self.image_groups:
|
|
658
|
+
del self.image_groups[group_id]
|
|
659
|
+
return True
|
|
660
|
+
return False
|
|
661
|
+
|
|
662
|
+
# Macro operations (stub implementations)
|
|
663
|
+
def import_macro_from_file(self, filename: str) -> None:
|
|
664
|
+
"""Import macro from file (stub implementation).
|
|
665
|
+
|
|
666
|
+
Args:
|
|
667
|
+
filename: Filename
|
|
668
|
+
"""
|
|
669
|
+
if self.verbose:
|
|
670
|
+
print(f"import_macro_from_file called: {filename} (stub - not implemented)")
|
|
671
|
+
|
|
672
|
+
def run_macro(self, number_or_title: int | str | None = None) -> None:
|
|
673
|
+
"""Run macro (stub implementation).
|
|
674
|
+
|
|
675
|
+
Args:
|
|
676
|
+
number_or_title: Number or title of the macro. Defaults to None.
|
|
677
|
+
"""
|
|
678
|
+
if self.verbose:
|
|
679
|
+
print(f"run_macro called: {number_or_title} (stub - not implemented)")
|
|
680
|
+
|
|
681
|
+
def stop_macro(self, number_or_title: int | str | None = None) -> None:
|
|
682
|
+
"""Stop macro (stub implementation).
|
|
683
|
+
|
|
684
|
+
Args:
|
|
685
|
+
number_or_title: Number or title of the macro. Defaults to None.
|
|
686
|
+
"""
|
|
687
|
+
if self.verbose:
|
|
688
|
+
print(f"stop_macro called: {number_or_title} (stub - not implemented)")
|
|
689
|
+
|
|
690
|
+
# Calculation operations
|
|
691
|
+
def calc(self, name: str, param: list[str] | None = None) -> str | None:
|
|
692
|
+
"""Execute calculation and return result object UUID."""
|
|
693
|
+
if self.verbose:
|
|
694
|
+
execenv.print(
|
|
695
|
+
f"[STUB] Simulating calculation '{name}' with params: {param}"
|
|
696
|
+
)
|
|
697
|
+
|
|
698
|
+
# In stub mode, just simulate by creating a dummy result object
|
|
699
|
+
if not self.selected_objects:
|
|
700
|
+
if self.verbose:
|
|
701
|
+
execenv.print("[STUB] No objects selected for calculation")
|
|
702
|
+
return None
|
|
703
|
+
|
|
704
|
+
src_uuid = self.selected_objects[0]
|
|
705
|
+
src_obj = self.signals.get(src_uuid) or self.images.get(src_uuid)
|
|
706
|
+
|
|
707
|
+
if src_obj is None:
|
|
708
|
+
if self.verbose:
|
|
709
|
+
execenv.print(f"[STUB] Source object {src_uuid} not found")
|
|
710
|
+
return None
|
|
711
|
+
|
|
712
|
+
# Create a dummy result object based on source type
|
|
713
|
+
if isinstance(src_obj, SignalObj):
|
|
714
|
+
result = create_signal(f"{name}({src_obj.title})")
|
|
715
|
+
obj_uuid = str(uuid.uuid4())
|
|
716
|
+
self.signals[obj_uuid] = result
|
|
717
|
+
if self.verbose:
|
|
718
|
+
execenv.print(f"[STUB] Created dummy signal result: {result.title}")
|
|
719
|
+
return obj_uuid
|
|
720
|
+
if isinstance(src_obj, ImageObj):
|
|
721
|
+
result = create_image(f"{name}({src_obj.title})")
|
|
722
|
+
obj_uuid = str(uuid.uuid4())
|
|
723
|
+
self.images[obj_uuid] = result
|
|
724
|
+
if self.verbose:
|
|
725
|
+
execenv.print(f"[STUB] Created dummy image result: {result.title}")
|
|
726
|
+
return obj_uuid
|
|
727
|
+
|
|
728
|
+
if self.verbose:
|
|
729
|
+
execenv.print("[STUB] Unsupported object type for calculation")
|
|
730
|
+
return None
|
|
731
|
+
|
|
732
|
+
# Annotation operations
|
|
733
|
+
# pylint: disable=unused-argument
|
|
734
|
+
def add_annotations_from_items(
|
|
735
|
+
self, items: list[str], refresh_plot: bool = True, panel: str | None = None
|
|
736
|
+
) -> None:
|
|
737
|
+
"""Add object annotations (annotation plot items)."""
|
|
738
|
+
# In stub mode, just acknowledge the annotations
|
|
739
|
+
# Real implementation would deserialize items and add to objects
|
|
740
|
+
|
|
741
|
+
# pylint: disable=unused-argument
|
|
742
|
+
def add_label_with_title(
|
|
743
|
+
self, title: str | None = None, panel: str | None = None
|
|
744
|
+
) -> None:
|
|
745
|
+
"""Add label with title."""
|
|
746
|
+
if self.verbose:
|
|
747
|
+
execenv.print(f"[STUB] Simulating add label with title: {title}")
|
|
748
|
+
# In stub mode, just acknowledge the label
|
|
749
|
+
|
|
750
|
+
|
|
751
|
+
@contextmanager
|
|
752
|
+
def datalab_stub_server(
|
|
753
|
+
port: int = 0, verbose: bool = True
|
|
754
|
+
) -> Generator[int, None, None]:
|
|
755
|
+
"""Context manager for DataLab stub server.
|
|
756
|
+
|
|
757
|
+
Args:
|
|
758
|
+
port: Port to bind to. If 0, uses a random available port.
|
|
759
|
+
verbose: If True, print verbose debug information.
|
|
760
|
+
|
|
761
|
+
Yields:
|
|
762
|
+
Port number the server is listening on
|
|
763
|
+
"""
|
|
764
|
+
server = DataLabStubServer(port, verbose=verbose)
|
|
765
|
+
try:
|
|
766
|
+
actual_port = server.start()
|
|
767
|
+
yield actual_port
|
|
768
|
+
finally:
|
|
769
|
+
server.stop()
|
|
770
|
+
|
|
771
|
+
|
|
772
|
+
def patch_simpleremoteproxy_for_stub() -> DataLabStubServer:
|
|
773
|
+
"""Patch SimpleRemoteProxy to connect to a stub server instead of real DataLab.
|
|
774
|
+
|
|
775
|
+
This utility function:
|
|
776
|
+
1. Creates and starts a DataLabStubServer instance
|
|
777
|
+
2. Patches SimpleRemoteProxy.__connect_to_server to use the stub server port
|
|
778
|
+
3. Returns the stub server instance for later cleanup
|
|
779
|
+
|
|
780
|
+
Returns:
|
|
781
|
+
The running DataLabStubServer instance that needs to be stopped later
|
|
782
|
+
|
|
783
|
+
Example:
|
|
784
|
+
>>> stub_server = patch_simpleremoteproxy_for_stub()
|
|
785
|
+
>>> try:
|
|
786
|
+
... # Your code using SimpleRemoteProxy here
|
|
787
|
+
... proxy = SimpleRemoteProxy()
|
|
788
|
+
... # proxy will connect to stub server automatically
|
|
789
|
+
... finally:
|
|
790
|
+
... stub_server.stop()
|
|
791
|
+
"""
|
|
792
|
+
# pylint: disable=import-outside-toplevel
|
|
793
|
+
# Import directly from remote module to avoid circular dependency
|
|
794
|
+
from sigima.client.remote import SimpleRemoteProxy
|
|
795
|
+
|
|
796
|
+
# Store original method
|
|
797
|
+
# pylint: disable=protected-access
|
|
798
|
+
original_connect_to_server = SimpleRemoteProxy._SimpleRemoteProxy__connect_to_server
|
|
799
|
+
|
|
800
|
+
# Start stub server
|
|
801
|
+
stub_server_instance = DataLabStubServer(verbose=False)
|
|
802
|
+
stub_port = stub_server_instance.start()
|
|
803
|
+
|
|
804
|
+
# pylint: disable=unused-argument
|
|
805
|
+
def patched_connect_to_server(self, port=None):
|
|
806
|
+
"""Patched connect that uses stub server port."""
|
|
807
|
+
# Always use the stub server port, ignore the requested port
|
|
808
|
+
original_connect_to_server(self, port=str(stub_port))
|
|
809
|
+
|
|
810
|
+
# Apply the patch
|
|
811
|
+
# pylint: disable=protected-access
|
|
812
|
+
SimpleRemoteProxy._SimpleRemoteProxy__connect_to_server = patched_connect_to_server
|
|
813
|
+
|
|
814
|
+
return stub_server_instance
|