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/tests/env.py
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Environment (:mod:`sigima.env`)
|
|
5
|
+
--------------------------------
|
|
6
|
+
|
|
7
|
+
This module defines the execution environment for `sigima` library, which is used
|
|
8
|
+
by Sigima test suite. It provides a way to manage execution settings, such as
|
|
9
|
+
unattended mode and verbosity level, and allows for parsing command line arguments
|
|
10
|
+
to set these settings.
|
|
11
|
+
|
|
12
|
+
.. note::
|
|
13
|
+
|
|
14
|
+
This module is intended for use in testing and development environments, and
|
|
15
|
+
should not be used in production code. It is designed to facilitate testing
|
|
16
|
+
and debugging by providing a controlled environment for running tests and
|
|
17
|
+
executing code.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import argparse
|
|
23
|
+
import enum
|
|
24
|
+
import os
|
|
25
|
+
import pprint
|
|
26
|
+
import sys
|
|
27
|
+
from contextlib import contextmanager
|
|
28
|
+
from typing import Any, Generator
|
|
29
|
+
|
|
30
|
+
from guidata.env import ExecEnv as GuiDataExecEnv
|
|
31
|
+
|
|
32
|
+
# We could import DEBUG from sigima.config, but is it really worth it?
|
|
33
|
+
DEBUG = os.environ.get("DEBUG", "").lower() in ("1", "true")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class VerbosityLevels(enum.Enum):
|
|
37
|
+
"""Print verbosity levels (for testing purpose)"""
|
|
38
|
+
|
|
39
|
+
QUIET = "quiet"
|
|
40
|
+
NORMAL = "normal"
|
|
41
|
+
DEBUG = "debug"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class SigimaExecEnv:
|
|
45
|
+
"""Object representing `sigima` test environment"""
|
|
46
|
+
|
|
47
|
+
UNATTENDED_ARG = "unattended"
|
|
48
|
+
VERBOSE_ARG = "verbose"
|
|
49
|
+
SCREENSHOT_ARG = "screenshot"
|
|
50
|
+
SCREENSHOT_PATH_ARG = "screenshot_path"
|
|
51
|
+
UNATTENDED_ENV = GuiDataExecEnv.UNATTENDED_ENV
|
|
52
|
+
VERBOSE_ENV = GuiDataExecEnv.VERBOSE_ENV
|
|
53
|
+
SCREENSHOT_ENV = GuiDataExecEnv.SCREENSHOT_ENV
|
|
54
|
+
SCREENSHOT_PATH_ENV = GuiDataExecEnv.SCREENSHOT_PATH_ENV
|
|
55
|
+
|
|
56
|
+
def __init__(self):
|
|
57
|
+
# Check if "pytest" is in the command line arguments:
|
|
58
|
+
if "pytest" not in sys.argv[0]:
|
|
59
|
+
# Do not parse command line arguments when running tests with pytest
|
|
60
|
+
# (otherwise, pytest arguments are parsed as DataLab arguments)
|
|
61
|
+
self.parse_args()
|
|
62
|
+
if self.unattended: # Do not run this code in production
|
|
63
|
+
# Check that calling `to_dict` do not raise any exception
|
|
64
|
+
self.to_dict()
|
|
65
|
+
|
|
66
|
+
def iterate_over_attrs_envvars(self) -> Generator[tuple[str, str], None, None]:
|
|
67
|
+
"""Iterate over Sigima environment variables
|
|
68
|
+
|
|
69
|
+
Yields:
|
|
70
|
+
A tuple (attribute name, environment variable name)
|
|
71
|
+
"""
|
|
72
|
+
for name in dir(self):
|
|
73
|
+
if name.endswith("_ENV"):
|
|
74
|
+
envvar: str = getattr(self, name)
|
|
75
|
+
attrname = "_".join(name.split("_")[:-1]).lower()
|
|
76
|
+
yield attrname, envvar
|
|
77
|
+
|
|
78
|
+
def to_dict(self):
|
|
79
|
+
"""Return a dictionary representation of the object"""
|
|
80
|
+
# The list of properties match the list of environment variable attribute names,
|
|
81
|
+
# modulo the "_ENV" suffix:
|
|
82
|
+
props = [attrname for attrname, _envvar in self.iterate_over_attrs_envvars()]
|
|
83
|
+
|
|
84
|
+
# Check that all properties are defined in the class and that they are
|
|
85
|
+
# really properties:
|
|
86
|
+
for prop in props:
|
|
87
|
+
assert hasattr(self, prop), (
|
|
88
|
+
f"Property {prop} is not defined in class {self.__class__.__name__}"
|
|
89
|
+
)
|
|
90
|
+
assert isinstance(getattr(self.__class__, prop), property), (
|
|
91
|
+
f"Attribute {prop} is not a property in class {self.__class__.__name__}"
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
# Return a dictionary with the properties as keys and their values as values:
|
|
95
|
+
return {p: getattr(self, p) for p in props}
|
|
96
|
+
|
|
97
|
+
def __str__(self):
|
|
98
|
+
"""Return a string representation of the object"""
|
|
99
|
+
return pprint.pformat(self.to_dict())
|
|
100
|
+
|
|
101
|
+
@staticmethod
|
|
102
|
+
def __get_mode(env):
|
|
103
|
+
"""Get mode value"""
|
|
104
|
+
env_val = os.environ.get(env)
|
|
105
|
+
if env_val is None:
|
|
106
|
+
return False
|
|
107
|
+
return env_val.lower() in ("1", "true", "yes", "on", "enable", "enabled")
|
|
108
|
+
|
|
109
|
+
@staticmethod
|
|
110
|
+
def __set_mode(env, value):
|
|
111
|
+
"""Set mode value"""
|
|
112
|
+
if env in os.environ:
|
|
113
|
+
os.environ.pop(env)
|
|
114
|
+
if value:
|
|
115
|
+
os.environ[env] = "1"
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def unattended(self):
|
|
119
|
+
"""Get unattended value"""
|
|
120
|
+
return self.__get_mode(self.UNATTENDED_ENV)
|
|
121
|
+
|
|
122
|
+
@unattended.setter
|
|
123
|
+
def unattended(self, value):
|
|
124
|
+
"""Set unattended value"""
|
|
125
|
+
self.__set_mode(self.UNATTENDED_ENV, value)
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def screenshot(self):
|
|
129
|
+
"""Get screenshot value"""
|
|
130
|
+
return self.__get_mode(self.SCREENSHOT_ENV)
|
|
131
|
+
|
|
132
|
+
@screenshot.setter
|
|
133
|
+
def screenshot(self, value):
|
|
134
|
+
"""Set screenshot value"""
|
|
135
|
+
self.__set_mode(self.SCREENSHOT_ENV, value)
|
|
136
|
+
|
|
137
|
+
@property
|
|
138
|
+
def screenshot_path(self):
|
|
139
|
+
"""Get screenshot path"""
|
|
140
|
+
return os.environ.get(self.SCREENSHOT_PATH_ENV, "")
|
|
141
|
+
|
|
142
|
+
@screenshot_path.setter
|
|
143
|
+
def screenshot_path(self, value):
|
|
144
|
+
"""Set screenshot path"""
|
|
145
|
+
if value:
|
|
146
|
+
os.environ[self.SCREENSHOT_PATH_ENV] = str(value)
|
|
147
|
+
elif self.SCREENSHOT_PATH_ENV in os.environ:
|
|
148
|
+
os.environ.pop(self.SCREENSHOT_PATH_ENV)
|
|
149
|
+
|
|
150
|
+
@property
|
|
151
|
+
def verbose(self):
|
|
152
|
+
"""Get verbosity level"""
|
|
153
|
+
env_val = os.environ.get(self.VERBOSE_ENV)
|
|
154
|
+
if env_val in (None, ""):
|
|
155
|
+
return VerbosityLevels.NORMAL.value
|
|
156
|
+
return env_val.lower()
|
|
157
|
+
|
|
158
|
+
@verbose.setter
|
|
159
|
+
def verbose(self, value):
|
|
160
|
+
"""Set verbosity level"""
|
|
161
|
+
os.environ[self.VERBOSE_ENV] = value
|
|
162
|
+
|
|
163
|
+
def parse_args(self):
|
|
164
|
+
"""Parse command line arguments"""
|
|
165
|
+
parser = argparse.ArgumentParser(description="Run `sigima` tests")
|
|
166
|
+
parser.add_argument(
|
|
167
|
+
"--" + self.UNATTENDED_ARG,
|
|
168
|
+
action="store_true",
|
|
169
|
+
help="non-interactive mode",
|
|
170
|
+
default=None,
|
|
171
|
+
)
|
|
172
|
+
parser.add_argument(
|
|
173
|
+
"--" + self.SCREENSHOT_ARG,
|
|
174
|
+
action="store_true",
|
|
175
|
+
help="automatic screenshots",
|
|
176
|
+
default=None,
|
|
177
|
+
)
|
|
178
|
+
parser.add_argument(
|
|
179
|
+
"--" + self.SCREENSHOT_PATH_ARG,
|
|
180
|
+
type=str,
|
|
181
|
+
help="path to save screenshots",
|
|
182
|
+
default=None,
|
|
183
|
+
)
|
|
184
|
+
parser.add_argument(
|
|
185
|
+
"--" + self.VERBOSE_ARG,
|
|
186
|
+
choices=[lvl.value for lvl in VerbosityLevels],
|
|
187
|
+
required=False,
|
|
188
|
+
default=None,
|
|
189
|
+
help="verbosity level: for debugging/testing purpose",
|
|
190
|
+
)
|
|
191
|
+
args, _unknown = parser.parse_known_args()
|
|
192
|
+
self.set_env_from_args(args)
|
|
193
|
+
|
|
194
|
+
def set_env_from_args(self, args):
|
|
195
|
+
"""Set appropriate environment variables"""
|
|
196
|
+
for argname in (
|
|
197
|
+
self.UNATTENDED_ARG,
|
|
198
|
+
self.SCREENSHOT_ARG,
|
|
199
|
+
self.SCREENSHOT_PATH_ARG,
|
|
200
|
+
self.VERBOSE_ARG,
|
|
201
|
+
):
|
|
202
|
+
argvalue = getattr(args, argname)
|
|
203
|
+
if argvalue is not None:
|
|
204
|
+
setattr(self, argname, argvalue)
|
|
205
|
+
|
|
206
|
+
def log(self, source: Any, *objects: Any) -> None:
|
|
207
|
+
"""Log text on screen
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
source: object from which the log is issued
|
|
211
|
+
*objects: objects to log
|
|
212
|
+
"""
|
|
213
|
+
if DEBUG or self.verbose == VerbosityLevels.DEBUG.value:
|
|
214
|
+
print(str(source) + ":", *objects)
|
|
215
|
+
# TODO: [P4] Eventually, log in a file (optionally)
|
|
216
|
+
|
|
217
|
+
def print(self, *objects, sep=" ", end="\n", file=sys.stdout, flush=False):
|
|
218
|
+
"""Print in file, depending on verbosity level"""
|
|
219
|
+
if self.verbose != VerbosityLevels.QUIET.value or DEBUG:
|
|
220
|
+
print(*objects, sep=sep, end=end, file=file, flush=flush)
|
|
221
|
+
|
|
222
|
+
def pprint(
|
|
223
|
+
self,
|
|
224
|
+
obj,
|
|
225
|
+
stream=None,
|
|
226
|
+
indent=1,
|
|
227
|
+
width=80,
|
|
228
|
+
depth=None,
|
|
229
|
+
compact=False,
|
|
230
|
+
sort_dicts=True,
|
|
231
|
+
):
|
|
232
|
+
"""Pretty-print in stream, depending on verbosity level"""
|
|
233
|
+
if self.verbose != VerbosityLevels.QUIET.value or DEBUG:
|
|
234
|
+
pprint.pprint(
|
|
235
|
+
obj,
|
|
236
|
+
stream=stream,
|
|
237
|
+
indent=indent,
|
|
238
|
+
width=width,
|
|
239
|
+
depth=depth,
|
|
240
|
+
compact=compact,
|
|
241
|
+
sort_dicts=sort_dicts,
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
@contextmanager
|
|
245
|
+
def context(
|
|
246
|
+
self,
|
|
247
|
+
unattended=None,
|
|
248
|
+
screenshot=None,
|
|
249
|
+
verbose=None,
|
|
250
|
+
) -> Generator[None, None, None]:
|
|
251
|
+
"""Return a context manager that sets some execenv properties at enter,
|
|
252
|
+
and restores them at exit. This is useful to run some code in a
|
|
253
|
+
controlled environment, for example to accept dialogs in unattended
|
|
254
|
+
mode, and restore the previous value at exit.
|
|
255
|
+
|
|
256
|
+
Args:
|
|
257
|
+
unattended: whether to run in unattended mode
|
|
258
|
+
screenshot: whether to take screenshots
|
|
259
|
+
verbose: verbosity level
|
|
260
|
+
|
|
261
|
+
.. note::
|
|
262
|
+
If a passed value is None, the corresponding property is not changed.
|
|
263
|
+
"""
|
|
264
|
+
old_values = self.to_dict()
|
|
265
|
+
new_values = {
|
|
266
|
+
"unattended": unattended,
|
|
267
|
+
"screenshot": screenshot,
|
|
268
|
+
"verbose": verbose,
|
|
269
|
+
}
|
|
270
|
+
for key, value in new_values.items():
|
|
271
|
+
if value is not None:
|
|
272
|
+
setattr(self, key, value)
|
|
273
|
+
try:
|
|
274
|
+
yield
|
|
275
|
+
finally:
|
|
276
|
+
for key, value in old_values.items():
|
|
277
|
+
setattr(self, key, value)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
execenv = SigimaExecEnv()
|
sigima/tests/guiutils.py
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Utilities to manage GUI activation for tests executed with pytest
|
|
5
|
+
or as standalone scripts.
|
|
6
|
+
|
|
7
|
+
?? This module must not import any Qt-related module at the top level,
|
|
8
|
+
as Qt is an optional dependency of Sigima.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import os
|
|
14
|
+
import types
|
|
15
|
+
from contextlib import contextmanager
|
|
16
|
+
from typing import TYPE_CHECKING, Generator, Optional
|
|
17
|
+
|
|
18
|
+
from sigima.tests import SIGIMA_TESTS_GUI_ENV
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
# ?? Type-only: no runtime Qt import
|
|
22
|
+
from qtpy.QtWidgets import QApplication
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# Single source of truth (module-global); None means "not forced".
|
|
26
|
+
_FORCED_GUI: bool | None = None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def enable_gui(on: bool | None = True) -> None:
|
|
30
|
+
"""Force GUI mode on/off (or reset to auto when None)."""
|
|
31
|
+
global _FORCED_GUI # pylint: disable=global-statement
|
|
32
|
+
_FORCED_GUI = on
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def is_gui_enabled() -> bool:
|
|
36
|
+
"""Return True if GUI mode is enabled."""
|
|
37
|
+
# 1) explicit override
|
|
38
|
+
if _FORCED_GUI is not None:
|
|
39
|
+
return _FORCED_GUI
|
|
40
|
+
# 2) pytest --gui, exposed by conftest via env var (see below)
|
|
41
|
+
if os.environ.get(SIGIMA_TESTS_GUI_ENV, "") in ("1", "true", "True"):
|
|
42
|
+
return True
|
|
43
|
+
return False
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class DummyRequest:
|
|
47
|
+
"""
|
|
48
|
+
Dummy request object to simulate pytest --gui when running a test manually.
|
|
49
|
+
|
|
50
|
+
Example usage:
|
|
51
|
+
test_x(request=DummyRequest(gui=True))
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def __init__(self, gui: bool = True):
|
|
55
|
+
self.config = types.SimpleNamespace()
|
|
56
|
+
self.config.getoption = lambda name: gui if name == "--gui" else None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@contextmanager
|
|
60
|
+
def lazy_qt_app_context(
|
|
61
|
+
*, exec_loop: bool = False, force: bool | None = None
|
|
62
|
+
) -> Generator[Optional[QApplication], None, None]:
|
|
63
|
+
"""Provide a Qt app context lazily; no-op if GUI is disabled.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
exec_loop: Run the Qt event loop (e.g. when showing a non-blocking widget).
|
|
67
|
+
force: None ? auto (use is_gui_enabled());
|
|
68
|
+
True ? force GUI ON (always create Qt app);
|
|
69
|
+
False ? force GUI OFF (no-op).
|
|
70
|
+
|
|
71
|
+
Yields:
|
|
72
|
+
The QApplication instance if enabled, else None.
|
|
73
|
+
|
|
74
|
+
.. note::
|
|
75
|
+
|
|
76
|
+
This context manager is useful for tests that require a Qt application context,
|
|
77
|
+
but should be used with caution to avoid unnecessary Qt imports. For tests
|
|
78
|
+
that are exclusively GUI-based, option `force=True` can be used to ensure
|
|
79
|
+
the Qt application context is always created. For tests that must be executable
|
|
80
|
+
without a GUI, option `force` may be skipped so that operations inside the
|
|
81
|
+
context are only performed if the GUI is enabled.
|
|
82
|
+
"""
|
|
83
|
+
enabled = is_gui_enabled() if force is None else force
|
|
84
|
+
if not enabled:
|
|
85
|
+
# No Qt import, block executes as a no-op context
|
|
86
|
+
yield None
|
|
87
|
+
return
|
|
88
|
+
|
|
89
|
+
# Lazy import: only when enabled
|
|
90
|
+
# pylint: disable=import-outside-toplevel
|
|
91
|
+
from guidata.qthelpers import qt_app_context
|
|
92
|
+
|
|
93
|
+
with qt_app_context(exec_loop=exec_loop) as qt_app:
|
|
94
|
+
yield qt_app
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _vistools_call_if_gui(func_name: str, *args, **kwargs) -> bool:
|
|
98
|
+
"""Call sigima.tests.vistools.<func_name>(...) only if GUI is enabled.
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
True if the call executed (GUI enabled or forced), else False.
|
|
102
|
+
"""
|
|
103
|
+
with lazy_qt_app_context() as app:
|
|
104
|
+
if app is None:
|
|
105
|
+
return False
|
|
106
|
+
from sigima.tests import vistools # pylint: disable=import-outside-toplevel
|
|
107
|
+
|
|
108
|
+
getattr(vistools, func_name)(*args, **kwargs)
|
|
109
|
+
return True
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def view_curves_if_gui(*args, **kwargs) -> None:
|
|
113
|
+
"""Create a curve dialog and plot curves if GUI mode enabled.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
data_or_objs: Single `SignalObj` or `np.ndarray`, or a list/tuple of these,
|
|
117
|
+
or a list/tuple of (xdata, ydata) pairs
|
|
118
|
+
name: Name of the dialog, or None to use a default name
|
|
119
|
+
title: Title of the dialog, or None to use a default title
|
|
120
|
+
xlabel: Label for the x-axis, or None for no label
|
|
121
|
+
ylabel: Label for the y-axis, or None for no label
|
|
122
|
+
"""
|
|
123
|
+
return _vistools_call_if_gui("view_curves", *args, **kwargs)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def view_images_if_gui(*args, **kwargs) -> None:
|
|
127
|
+
"""Show sequence of images if GUI mode enabled.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
data_or_objs: Single `ImageObj` or `np.ndarray`, or a list/tuple of these
|
|
131
|
+
name: Name of the dialog, or None to use a default name
|
|
132
|
+
title: Title of the dialog, or None to use a default title
|
|
133
|
+
xlabel: Label for the x-axis, or None for no label
|
|
134
|
+
ylabel: Label for the y-axis, or None for no label
|
|
135
|
+
"""
|
|
136
|
+
return _vistools_call_if_gui("view_images", *args, **kwargs)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def view_curves_and_images_if_gui(*args, **kwargs) -> None:
|
|
140
|
+
"""View signals, then images in two successive dialogs if GUI mode enabled.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
data_or_objs: List of `SignalObj`, `ImageObj`, `np.ndarray` or a mix of these
|
|
144
|
+
name: Name of the dialog, or None to use a default name
|
|
145
|
+
title: Title of the dialog, or None to use a default title
|
|
146
|
+
xlabel: Label for the x-axis, or None for no label
|
|
147
|
+
ylabel: Label for the y-axis, or None for no label
|
|
148
|
+
"""
|
|
149
|
+
return _vistools_call_if_gui("view_curves_and_images", *args, **kwargs)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def view_images_side_by_side_if_gui(*args, **kwargs) -> None:
|
|
153
|
+
"""Show sequence of images side-by-side if GUI mode enabled.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
images: List of `ImageItem`, `np.ndarray`, or `ImageObj` objects to display
|
|
157
|
+
titles: List of titles for each image
|
|
158
|
+
share_axes: Whether to share axes across plots, default is True
|
|
159
|
+
rows: Fixed number of rows in the grid, or None to compute automatically
|
|
160
|
+
maximized: Whether to show the dialog maximized, default is False
|
|
161
|
+
title: Title of the dialog, or None for a default title
|
|
162
|
+
"""
|
|
163
|
+
return _vistools_call_if_gui("view_images_side_by_side", *args, **kwargs)
|