File: __init__.py

package info (click to toggle)
python-sigima 1.1.1-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 25,608 kB
  • sloc: python: 35,251; makefile: 3
file content (410 lines) | stat: -rw-r--r-- 12,874 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.

"""
Visualization tools for Sigima
==============================

This module provides visualization utilities for Sigima objects, useful for:
- Interactive testing and debugging
- Data analysis in Jupyter notebooks
- Quick visual inspection of processing results

The module automatically selects between PlotPy and Matplotlib backends based on
availability and configuration settings.

The backend selection follows this priority:
1. Environment variable SIGIMA_VIZ_BACKEND (if set)
2. Configuration option sigima.config.options.viz_backend
3. Auto-detection (PlotPy preferred, Matplotlib as fallback)

Backend selection logic:
- "auto": Try PlotPy first, fall back to Matplotlib
- "plotpy": Use PlotPy (raise ImportError if not available)
- "matplotlib": Use Matplotlib (raise ImportError if not available)

Module exports:
- BACKEND_NAME: Name of the selected backend ("plotpy" or "matplotlib")
- BACKEND_SOURCE: How the backend was selected ("env", "config", or "auto")
- All public functions from the selected backend module

Example usage::

    from sigima import viz

    # View signal objects
    viz.view_curves([signal1, signal2], title="My signals")

    # View image objects with ROIs
    viz.view_images([image], show_roi=True)

    # Compare images side by side
    viz.view_images_side_by_side([img1, img2], titles=["Before", "After"])
"""

from __future__ import annotations

import importlib
import os
from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
    # Import type hints for static analysis (Pylance, Pylint, mypy)
    # These imports only happen during type checking, not at runtime
    import numpy as np

    from sigima.objects import GeometryResult, ImageObj

    # Type stub declarations for public API
    # These tell static analyzers what functions are available
    BACKEND_NAME: str
    BACKEND_SOURCE: str

    # pylint: disable=unused-argument

    def view_curves(
        curves: list,
        titles: list[str] | None = None,
        title: str | None = None,
        maximized: bool = False,
        results: list[GeometryResult] | GeometryResult | None = None,
        show_roi: bool = True,
        object_name: str = "",
        **kwargs,
    ) -> None:
        """Display multiple curves in a dialog."""

    def view_images(
        images: list,
        titles: list[str] | None = None,
        title: str | None = None,
        maximized: bool = False,
        results: list[GeometryResult] | GeometryResult | None = None,
        show_roi: bool = True,
        object_name: str = "",
        **kwargs,
    ) -> None:
        """Display multiple images in a dialog."""

    def view_images_side_by_side(
        images: list,
        titles: list[str] | None = None,
        share_axes: bool = True,
        rows: int | None = None,
        maximized: bool = False,
        title: str | None = None,
        results: list[GeometryResult] | GeometryResult | None = None,
        show_roi: bool = True,
        object_name: str = "",
        **kwargs,
    ) -> None:
        """Display images side by side in a grid layout."""

    def view_curves_and_images(
        curves: list,
        images: list,
        curve_titles: list[str] | None = None,
        image_titles: list[str] | None = None,
        title: str | None = None,
        maximized: bool = False,
        results: list[GeometryResult] | GeometryResult | None = None,
        show_roi: bool = True,
        object_name: str = "",
        **kwargs,
    ) -> None:
        """Display curves and images together in a dialog."""

    def view_curve_items(
        items: list,
        name: str | None = None,
        title: str | None = None,
        xlabel: str | None = None,
        ylabel: str | None = None,
        xunit: str | None = None,
        yunit: str | None = None,
        add_legend: bool = True,
        datetime_format: str | None = None,
        object_name: str = "",
    ) -> None:
        """Display curve items in a plot dialog."""

    def view_image_items(
        items: list,
        name: str | None = None,
        title: str | None = None,
        xlabel: str | None = None,
        ylabel: str | None = None,
        zlabel: str | None = None,
        xunit: str | None = None,
        yunit: str | None = None,
        zunit: str | None = None,
        show_itemlist: bool = False,
        object_name: str = "",
    ) -> None:
        """Display image items in a plot dialog."""

    def create_curve(x: np.ndarray, y: np.ndarray, title: str | None = None) -> Any:
        """Create a curve item from x and y data."""

    def create_image(
        data: np.ndarray,
        title: str | None = None,
        interpolation: str = "linear",
        colormap: str | None = None,
        alpha_function: str | None = None,
        xdata: list[float] | None = None,
        ydata: list[float] | None = None,
        **kwargs,
    ) -> Any:
        """Create an image item from array data."""
        return object()

    def create_contour_shapes(
        obj: ImageObj,
        threshold: float,
        kind: str = "polygon",
    ) -> list[Any]:
        """Create contour shape items from image object."""
        return []

    def create_circle(
        xc: float,
        yc: float,
        r: float,
        title: str | None = None,
        **kwargs,
    ) -> Any:
        """Create a circle annotation item."""
        return object()

    def create_segment(
        x1: float,
        y1: float,
        x2: float,
        y2: float,
        title: str | None = None,
        **kwargs,
    ) -> Any:
        """Create a segment annotation item."""
        return object()

    def create_cursor(
        orientation: str,
        position: float | tuple[float, float],
        label: str,
    ) -> Any:
        """Create a cursor marker item."""
        return object()

    def create_range(
        xmin: float | None = None,
        xmax: float | None = None,
        ymin: float | None = None,
        ymax: float | None = None,
        title: str | None = None,
        **kwargs,
    ) -> Any:
        """Create a range annotation item."""
        return object()

    def create_label(text: str) -> Any:
        """Create a text label item."""
        return object()

    def create_marker(x: float, y: float, title: str | None = None) -> Any:
        """Create a marker item at specified coordinates."""
        return object()


# Determine which backend to use
_BACKEND_NAME: str | None = None
_BACKEND_SOURCE: str = "auto"


def _select_backend() -> tuple[str, str]:
    """Select visualization backend based on configuration and availability.

    Returns:
        Tuple of (backend_name, source) where:
        - backend_name: "plotpy" or "matplotlib"
        - source: How the backend was selected ("env", "config", "auto")

    Raises:
        ImportError: If no suitable backend is available or selected backend not found
    """
    # pylint: disable=import-outside-toplevel
    # pylint: disable=unused-import

    # Priority 1: Environment variable
    env_backend = os.environ.get("SIGIMA_VIZ_BACKEND", "").lower()
    if env_backend in ("plotpy", "matplotlib", "auto"):
        requested = env_backend
        source = "env"
    else:
        # Priority 2: Configuration option
        try:
            from sigima.config import options

            requested = options.viz_backend.get(sync_env=False).lower()
            source = "config"
        except Exception:  # pylint: disable=broad-exception-caught
            requested = "auto"
            source = "auto"

    # Try to import based on request
    if requested == "plotpy":
        try:
            import plotpy  # noqa: F401

            return ("plotpy", source)
        except ImportError as exc:
            raise ImportError(
                "PlotPy backend requested but PlotPy is not installed. "
                "Install with: pip install PlotPy"
            ) from exc

    elif requested == "matplotlib":
        try:
            import matplotlib  # noqa: F401

            return ("matplotlib", source)
        except ImportError as exc:
            raise ImportError(
                "Matplotlib backend requested but Matplotlib is not installed. "
                "Install with: pip install matplotlib"
            ) from exc

    else:  # "auto"
        # Try PlotPy first
        try:
            import plotpy  # noqa: F401

            return ("plotpy", source)
        except ImportError:
            pass

        # Fall back to Matplotlib
        try:
            import matplotlib  # noqa: F401

            return ("matplotlib", source)
        except ImportError:
            pass

        # Neither available
        raise ImportError(
            "No visualization backend available. Please install either:\n"
            "  - PlotPy: pip install PlotPy (recommended for interactive features)\n"
            "  - Matplotlib: pip install matplotlib (simpler, view-only)"
        )


# Lazy backend initialization - deferred until first attribute access
_BACKEND_MODULE = None
_BACKEND_NAME = None
_BACKEND_SOURCE = None
_INITIALIZING = False  # Flag to prevent recursion

# Public API: Set default values for BACKEND_NAME and BACKEND_SOURCE
# These will be updated when the backend is initialized
BACKEND_NAME = None  # Will be "plotpy" or "matplotlib" after initialization
BACKEND_SOURCE = None  # Will be "env", "config", or "auto" after initialization


def _initialize_backend():
    """Initialize backend on first use (lazy loading)."""
    # pylint: disable=global-statement
    global _BACKEND_MODULE, _BACKEND_NAME, _BACKEND_SOURCE, _INITIALIZING
    global BACKEND_NAME, BACKEND_SOURCE

    if _BACKEND_MODULE is not None:
        return  # Already initialized

    if _INITIALIZING:
        return  # Prevent recursion during import

    _INITIALIZING = True
    try:
        _BACKEND_NAME, _BACKEND_SOURCE = _select_backend()

        # Update public API variables
        BACKEND_NAME = _BACKEND_NAME
        BACKEND_SOURCE = _BACKEND_SOURCE

        # Import selected backend using importlib to avoid triggering __getattr__
        if _BACKEND_NAME == "plotpy":
            _BACKEND_MODULE = importlib.import_module(".viz_plotpy", package=__name__)
        elif _BACKEND_NAME == "matplotlib":
            _BACKEND_MODULE = importlib.import_module(".viz_mpl", package=__name__)
    finally:
        _INITIALIZING = False


def __getattr__(name: str):
    """Lazy loading of backend attributes."""
    # Handle special/dunder attributes that inspect might access
    # Raise AttributeError immediately to avoid backend initialization
    if name.startswith("__") and name.endswith("__"):
        raise AttributeError(f"module '{__name__}' has no attribute '{name}'")

    # For functions in __all__, try to initialize backend and forward to backend module
    # If backend is not available, return a placeholder function
    if name in __all__:
        try:
            _initialize_backend()
            if _BACKEND_MODULE is not None:
                return getattr(_BACKEND_MODULE, name)
        except ImportError:
            pass  # Fall through to placeholder

        # Return a placeholder function that will raise an error when called
        def _placeholder(*args, **kwargs):
            raise ImportError(
                f"Function '{name}' requires a visualization backend. "
                "Please install either PlotPy or Matplotlib."
            )

        _placeholder.__name__ = name
        _placeholder.__doc__ = f"Placeholder for {name} (backend not available)"
        return _placeholder

    # For other attributes, raise AttributeError
    raise AttributeError(f"module '{__name__}' has no attribute '{name}'")


def __dir__():
    """Return list of available attributes (with lazy initialization)."""
    try:
        _initialize_backend()
        base_attrs = ["BACKEND_NAME", "BACKEND_SOURCE"]
        backend_attrs = [
            name for name in dir(_BACKEND_MODULE) if not name.startswith("_")
        ]
        return base_attrs + backend_attrs
    except ImportError:
        # During pytest collection or in environments without visualization backends,
        # return minimal attributes to avoid breaking inspect.getmembers()
        return ["BACKEND_NAME", "BACKEND_SOURCE"]


# Define __all__ to include expected public API
__all__ = [
    "BACKEND_NAME",
    "BACKEND_SOURCE",
    # Creation functions
    "create_circle",
    "create_contour_shapes",
    "create_cursor",
    "create_curve",
    "create_image",
    "create_label",
    "create_marker",
    "create_range",
    "create_segment",
    # Viewer functions
    "view_curve_items",
    "view_curves",
    "view_curves_and_images",
    "view_image_items",
    "view_images",
    "view_images_side_by_side",
]