diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
index b2375e71..b0403549 100644
--- a/.github/copilot-instructions.md
+++ b/.github/copilot-instructions.md
@@ -124,9 +124,7 @@ python scripts/run_with_env.py python -m ruff format
python scripts/run_with_env.py python -m ruff check --fix
# Pylint
-python scripts/run_with_env.py python -m pylint sigima \
- --disable=duplicate-code,fixme,too-many-arguments, \
- too-many-branches,too-many-instance-attributes
+python scripts/run_with_env.py python -m pylint sigima
```
### Translations
diff --git a/.github/workflows/build_deploy.yml b/.github/workflows/build_deploy.yml
index 77ff75d5..f9f4be2e 100644
--- a/.github/workflows/build_deploy.yml
+++ b/.github/workflows/build_deploy.yml
@@ -17,9 +17,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v7
- name: Set up Python
- uses: actions/setup-python@v5
+ uses: actions/setup-python@v7
with:
python-version: '3.x'
- name: Install dependencies
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 0ae6745c..c8ba9d25 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -20,6 +20,7 @@ on:
options:
- 'all'
- 'build'
+ - 'quality'
- 'build_latest'
default: 'all'
schedule:
@@ -28,7 +29,7 @@ on:
jobs:
build:
- if: ${{ (github.event_name == 'push' || github.event_name == 'pull_request' || github.event_name == 'release') || (github.event_name == 'workflow_dispatch' && (github.event.inputs.job_to_run == 'all' || github.event.inputs.job_to_run == 'build')) }}
+ if: ${{ (github.event_name == 'push' || github.event_name == 'pull_request' || github.event_name == 'release' || github.event_name == 'workflow_call') || (github.event_name == 'workflow_dispatch' && (github.event.inputs.job_to_run == 'all' || github.event.inputs.job_to_run == 'build')) }}
env:
DISPLAY: ':99.0'
@@ -40,9 +41,9 @@ jobs:
python-version: ["3.9", "3.13"]
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v7
- name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v5
+ uses: actions/setup-python@v7
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
@@ -87,6 +88,37 @@ jobs:
- name: Test with pytest
run: pytest -v --tb=long
+ quality:
+ name: Quality (visualization and Pylint)
+ if: ${{ (github.event_name == 'push' || github.event_name == 'pull_request' || github.event_name == 'release' || github.event_name == 'workflow_call') || (github.event_name == 'workflow_dispatch' && (github.event.inputs.job_to_run == 'all' || github.event.inputs.job_to_run == 'quality')) }}
+ env:
+ DISPLAY: ':99.0'
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v7
+ - name: Set up Python 3.13
+ uses: actions/setup-python@v7
+ with:
+ python-version: "3.13"
+ - name: Install quality dependencies
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y \
+ libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 \
+ libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-xfixes0 x11-utils
+ /sbin/start-stop-daemon --start --quiet \
+ --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background \
+ --exec /usr/bin/Xvfb -- :99 -screen 0 1920x1200x24 -ac +extension GLX
+ python -m pip install --upgrade pip
+ python -m pip install -e ".[dev,test,qt]" matplotlib
+ - name: Verify PlotPy backend
+ run: python -c "import plotpy; print(f'PlotPy loaded from {plotpy.__file__}')"
+ - name: Test visualization backends
+ run: python -m pytest -o addopts="--import-mode=importlib" sigima/tests/viz -vv
+ - name: Lint with Pylint
+ run: python -m pylint sigima
+
build_latest:
if: ${{ github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && (github.event.inputs.job_to_run == 'all' || github.event.inputs.job_to_run == 'build_latest')) }}
env:
@@ -94,10 +126,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v7
- name: Set up Python 3.13
- uses: actions/setup-python@v5
+ uses: actions/setup-python@v7
with:
python-version: "3.13"
diff --git a/.pylintrc b/.pylintrc
index 5ad83e90..0cea9264 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -4,10 +4,23 @@
max-line-length=88
[TYPECHECK]
-ignored-modules=qtpy.QtWidgets,qtpy.QtCore,qtpy.QtGui,cv2,plotpy._scaler,skimage.restoration,skimage.feature
+# NumPy's floating-point limits are populated dynamically.
+ignored-classes=finfo
+ignored-modules=qtpy.QtWidgets,qtpy.QtCore,qtpy.QtGui,cv2,plotpy._scaler,scipy.special,skimage.draw,skimage.restoration,skimage.feature
[MESSAGES CONTROL]
-disable=wrong-import-order
+# Ruff owns import ordering. The remaining exclusions reflect accepted complexity in
+# numerical algorithms, compatibility layers, and parallel object APIs.
+disable=duplicate-code,
+ fixme,
+ too-many-arguments,
+ too-many-branches,
+ too-many-instance-attributes,
+ too-many-lines,
+ too-many-locals,
+ too-many-public-methods,
+ too-many-statements,
+ wrong-import-order
[DESIGN]
max-args=10 # default: 5
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index 7e0e651d..cb9ff6cd 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -94,15 +94,6 @@
"-m",
"pylint",
"sigima",
- "--disable=duplicate-code",
- "--disable=fixme",
- "--disable=too-many-arguments",
- "--disable=too-many-branches",
- "--disable=too-many-instance-attributes",
- "--disable=too-many-lines",
- "--disable=too-many-locals",
- "--disable=too-many-public-methods",
- "--disable=too-many-statements",
],
"options": {
"cwd": "${workspaceFolder}",
diff --git a/doc/contributing/gitworkflow.rst b/doc/contributing/gitworkflow.rst
index 1f146dcf..bf87348c 100644
--- a/doc/contributing/gitworkflow.rst
+++ b/doc/contributing/gitworkflow.rst
@@ -87,6 +87,36 @@ This naming convention improves clarity by clearly separating
documentation efforts from code-related development (features, fixes, etc.).
+Commit Message Convention
+-------------------------
+
+New commits and pull request titles should follow the `Conventional Commits
+`_ format::
+
+ [optional scope]:
+
+Use the type that best describes the change:
+
+- ``feat`` for a new capability;
+- ``fix`` for a bug fix;
+- ``test`` for test-only changes;
+- ``docs`` for documentation;
+- ``ci`` for continuous-integration configuration;
+- ``refactor`` for a behavior-preserving code change;
+- ``chore`` for maintenance work not covered above.
+
+For example::
+
+ fix(roi): preserve inverse ROI rendering
+ test(viz): cover PlotPy ROI coordinates
+ ci: add Pylint quality gate
+
+Merge commits and annotated release tags are exceptions: keep their explicit Git
+messages, such as ``Merge branch 'feature/feature_name'`` and
+``Release version 1.2.0``. Do not rewrite published history solely to change old
+messages.
+
+
Workflow for New Features
-------------------------
@@ -95,7 +125,7 @@ Workflow for New Features
.. code-block:: sh
git checkout develop
- git checkout -b develop/feature_name
+ git checkout -b feature/feature_name
2. Develop the feature and commit changes.
@@ -104,13 +134,13 @@ Workflow for New Features
.. code-block:: sh
git checkout develop
- git merge --no-ff develop/feature_name
+ git merge --no-ff feature/feature_name
4. Delete the feature branch:
.. code-block:: sh
- git branch -d develop/feature_name
+ git branch -d feature/feature_name
.. warning::
@@ -170,13 +200,13 @@ For current maintenance release (target: ``release``):
.. warning::
- Do not create a ``fix/xxx`` branch from a ``develop/feature_name`` branch.
+ Do not create a ``fix/xxx`` branch from a ``feature/feature_name`` branch.
Always branch from ``develop`` or ``release`` to ensure fixes are correctly propagated.
.. code-block:: sh
# Incorrect:
- git checkout develop/feature_name
+ git checkout feature/feature_name
git checkout -b fix/wrong_branch
.. code-block:: sh
@@ -264,14 +294,27 @@ When ready to release a new minor or major version (e.g., 1.1.0, 2.0.0):
git tag -a v1.1.0 -m "Release version 1.1.0"
git push origin main --tags
-3. Delete the old ``release`` branch (if exists):
+3. Fast-forward ``develop`` to the released ``main`` commit:
+
+ .. code-block:: sh
+
+ git checkout develop
+ git merge --ff-only main
+ git push origin develop
+
+ This makes the release merge commit, its conflict resolutions, and the tag
+ reachable from ``develop`` without creating a redundant merge commit. If the
+ fast-forward fails, stop and reconcile ``main`` and ``develop`` through a
+ reviewed merge; never rebase or force-push either shared branch.
+
+4. Delete the old ``release`` branch (if exists):
.. code-block:: sh
git branch -d release
git push origin --delete release
-4. Create a new ``release`` branch from ``main`` when the first patch for 1.1.1 is needed:
+5. Create a new ``release`` branch from ``main`` when the first patch for 1.1.1 is needed:
.. code-block:: sh
@@ -286,8 +329,8 @@ Best Practices
.. code-block:: sh
- git checkout develop/feature_name
- git rebase develop
+ git checkout feature/feature_name
+ git rebase develop
- Avoid long-lived branches to minimize merge conflicts.
diff --git a/doc/contributing/guidelines.rst b/doc/contributing/guidelines.rst
index b5d09640..7a3de966 100644
--- a/doc/contributing/guidelines.rst
+++ b/doc/contributing/guidelines.rst
@@ -10,7 +10,7 @@ We follow the `PEP 8 `_ coding style.
In particular, we are especially strict about the following guidelines:
-- Limit all lines to a maximum of 79 characters.
+- Limit all lines to a maximum of 88 characters.
- Respect the naming conventions (classes, functions, variables, etc.).
- Use specific exceptions instead of the generic :class:`Exception`.
@@ -23,26 +23,31 @@ ruff
^^^^
If you are using `Visual Studio Code `_,
-the project settings will automatically format your code with `ruff` on save
-(you may also run the task "Run Ruff" to run `ruff` on the project).
+the project settings will automatically format your code with `ruff` on save.
+You may also run the Ruff tasks on the project.
-To run `ruff`, run the following command::
+To format and check the code with `ruff`, run the following commands from the
+repository root::
- ruff check
+ python scripts/run_with_env.py python -m ruff format
+ python scripts/run_with_env.py python -m ruff check
pylint
^^^^^^
-To run `pylint`, run the following command::
+To run `pylint`, run the following command from the repository root::
- pylint sigima
+ python scripts/run_with_env.py python -m pylint sigima
If you are using `Visual Studio Code `_
-on Windows, you may run the task "Run Pylint" to run `pylint` on the project.
+on Windows, you may run the "Pylint" task on the project. The enabled checks
+and accepted exclusions are defined in the repository's ``.pylintrc`` file.
.. note::
- A `pylint` rating greater than 9/10 is required to merge a pull request.
+ The Ruff and Pylint checks must both exit successfully before a pull request
+ may be merged. Pylint's numeric rating is informative only: the merge gate is
+ based on the absence of diagnostics from the enabled checks.
Specific coding guidelines
--------------------------
diff --git a/doc/locale/fr/LC_MESSAGES/contributing/gitworkflow.po b/doc/locale/fr/LC_MESSAGES/contributing/gitworkflow.po
index 9bd732f3..a116e17f 100644
--- a/doc/locale/fr/LC_MESSAGES/contributing/gitworkflow.po
+++ b/doc/locale/fr/LC_MESSAGES/contributing/gitworkflow.po
@@ -114,6 +114,42 @@ msgstr "``doc/user-guide``"
msgid "This naming convention improves clarity by clearly separating documentation efforts from code-related development (features, fixes, etc.)."
msgstr "Cette convention de nommage améliore la clarté en séparant clairement les efforts de documentation du développement lié au code (fonctionnalités, corrections, etc.)."
+msgid "Commit Message Convention"
+msgstr "Convention des messages de commit"
+
+msgid "New commits and pull request titles should follow the `Conventional Commits `_ format::"
+msgstr "Les nouveaux commits et les titres de demandes d'extraction doivent suivre le format `Conventional Commits `_::"
+
+msgid "Use the type that best describes the change:"
+msgstr "Utiliser le type qui décrit le mieux la modification :"
+
+msgid "``feat`` for a new capability;"
+msgstr "``feat`` pour une nouvelle fonctionnalité ;"
+
+msgid "``fix`` for a bug fix;"
+msgstr "``fix`` pour une correction d'anomalie ;"
+
+msgid "``test`` for test-only changes;"
+msgstr "``test`` pour des modifications concernant uniquement les tests ;"
+
+msgid "``docs`` for documentation;"
+msgstr "``docs`` pour la documentation ;"
+
+msgid "``ci`` for continuous-integration configuration;"
+msgstr "``ci`` pour la configuration de l'intégration continue ;"
+
+msgid "``refactor`` for a behavior-preserving code change;"
+msgstr "``refactor`` pour une modification de code qui préserve le comportement ;"
+
+msgid "``chore`` for maintenance work not covered above."
+msgstr "``chore`` pour les travaux de maintenance non couverts ci-dessus."
+
+msgid "For example::"
+msgstr "Par exemple::"
+
+msgid "Merge commits and annotated release tags are exceptions: keep their explicit Git messages, such as ``Merge branch 'feature/feature_name'`` and ``Release version 1.2.0``. Do not rewrite published history solely to change old messages."
+msgstr "Les commits de fusion et les tags de version annotés font exception : conserver leurs messages Git explicites, tels que ``Merge branch 'feature/feature_name'`` et ``Release version 1.2.0``. Ne pas réécrire l'historique publié uniquement pour modifier d'anciens messages."
+
msgid "Workflow for New Features"
msgstr "Workflow pour les nouvelles fonctionnalités"
@@ -159,8 +195,8 @@ msgstr "Créer une branche de correction d'anomalie à partir de ``release`` :"
msgid "Merge the fix branch back into ``release``:"
msgstr "Fusionner la branche de correction de nouveau dans ``release`` :"
-msgid "Do not create a ``fix/xxx`` branch from a ``develop/feature_name`` branch. Always branch from ``develop`` or ``release`` to ensure fixes are correctly propagated."
-msgstr "Ne pas créer une branche ``fix/xxx`` à partir d'une branche ``develop/feature_name``. Toujours créer une branche à partir de ``develop`` ou ``release`` pour garantir que les corrections sont correctement propagées."
+msgid "Do not create a ``fix/xxx`` branch from a ``feature/feature_name`` branch. Always branch from ``develop`` or ``release`` to ensure fixes are correctly propagated."
+msgstr "Ne pas créer une branche ``fix/xxx`` à partir d'une branche ``feature/feature_name``. Toujours créer une branche à partir de ``develop`` ou ``release`` pour garantir que les corrections sont correctement propagées."
msgid "Workflow for Critical Hotfixes"
msgstr "Workflow pour les correctifs urgents"
@@ -207,6 +243,12 @@ msgstr "Lorsque vous êtes prêt à publier une nouvelle version mineure ou maje
msgid "Merge ``develop`` into ``main``:"
msgstr "Fusionner ``develop`` dans ``main`` :"
+msgid "Fast-forward ``develop`` to the released ``main`` commit:"
+msgstr "Avancer ``develop`` en fast-forward jusqu'au commit publié de ``main`` :"
+
+msgid "This makes the release merge commit, its conflict resolutions, and the tag reachable from ``develop`` without creating a redundant merge commit. If the fast-forward fails, stop and reconcile ``main`` and ``develop`` through a reviewed merge; never rebase or force-push either shared branch."
+msgstr "Cette opération rend le commit de fusion de la version, ses résolutions de conflits et le tag accessibles depuis ``develop`` sans créer de commit de fusion redondant. Si le fast-forward échoue, arrêter l'opération et réconcilier ``main`` et ``develop`` au moyen d'une fusion revue ; ne jamais rebaser ni forcer la publication de l'une de ces branches partagées."
+
msgid "Delete the old ``release`` branch (if exists):"
msgstr "Supprimer l'ancienne branche ``release`` (si elle existe) :"
diff --git a/doc/locale/fr/LC_MESSAGES/contributing/guidelines.po b/doc/locale/fr/LC_MESSAGES/contributing/guidelines.po
index f43b662d..7bf13126 100644
--- a/doc/locale/fr/LC_MESSAGES/contributing/guidelines.po
+++ b/doc/locale/fr/LC_MESSAGES/contributing/guidelines.po
@@ -22,8 +22,8 @@ msgstr "Nous suivons le style de codage `PEP 8 `_ pour l'analyse statique du
msgid "ruff"
msgstr "ruff"
-msgid "If you are using `Visual Studio Code `_, the project settings will automatically format your code with `ruff` on save (you may also run the task \"Run Ruff\" to run `ruff` on the project)."
-msgstr "Si vous utilisez `Visual Studio Code `_, les paramètres du projet formateront automatiquement votre code avec `ruff` à l'enregistrement (vous pouvez également exécuter la tâche \"Run Ruff\" pour exécuter `ruff` sur le projet)."
+msgid "If you are using `Visual Studio Code `_, the project settings will automatically format your code with `ruff` on save. You may also run the Ruff tasks on the project."
+msgstr "Si vous utilisez `Visual Studio Code `_, les paramètres du projet formateront automatiquement votre code avec `ruff` à l'enregistrement. Vous pouvez également exécuter les tâches Ruff sur le projet."
-msgid "To run `ruff`, run the following command::"
-msgstr "Pour exécuter `ruff`, exécutez la commande suivante::"
+msgid "To format and check the code with `ruff`, run the following commands from the repository root::"
+msgstr "Pour formater et vérifier le code avec `ruff`, exécutez les commandes suivantes depuis la racine du dépôt::"
msgid "pylint"
msgstr "pylint"
-msgid "To run `pylint`, run the following command::"
-msgstr "Pour exécuter `pylint`, exécutez la commande suivante::"
+msgid "To run `pylint`, run the following command from the repository root::"
+msgstr "Pour exécuter `pylint`, lancez la commande suivante depuis la racine du dépôt::"
-msgid "If you are using `Visual Studio Code `_ on Windows, you may run the task \"Run Pylint\" to run `pylint` on the project."
-msgstr "Si vous utilisez `Visual Studio Code `_ sur Windows, vous pouvez exécuter la tâche \"Run Pylint\" pour exécuter `pylint` sur le projet."
+msgid "If you are using `Visual Studio Code `_ on Windows, you may run the \"Pylint\" task on the project. The enabled checks and accepted exclusions are defined in the repository's ``.pylintrc`` file."
+msgstr "Si vous utilisez `Visual Studio Code `_ sous Windows, vous pouvez exécuter la tâche \"Pylint\" sur le projet. Les vérifications activées et les exclusions acceptées sont définies dans le fichier ``.pylintrc`` du dépôt."
-msgid "A `pylint` rating greater than 9/10 is required to merge a pull request."
-msgstr "Une note `pylint` supérieure à 9/10 est requise pour fusionner une demande d'extraction."
+msgid "The Ruff and Pylint checks must both exit successfully before a pull request may be merged. Pylint's numeric rating is informative only: the merge gate is based on the absence of diagnostics from the enabled checks."
+msgstr "Les vérifications Ruff et Pylint doivent toutes deux se terminer avec succès avant qu'une demande d'extraction puisse être fusionnée. La note numérique de Pylint est uniquement informative : l'autorisation de fusion repose sur l'absence de diagnostics issus des vérifications activées."
msgid "Specific coding guidelines"
msgstr "Règles de codage spécifiques"
diff --git a/sigima/tests/signal/stability_unit_test.py b/sigima/tests/signal/stability_unit_test.py
index 68746b69..4b307483 100644
--- a/sigima/tests/signal/stability_unit_test.py
+++ b/sigima/tests/signal/stability_unit_test.py
@@ -45,8 +45,8 @@
import sigima.objects
import sigima.params
import sigima.proc.signal
-import sigima.tools.signal.stability as stability
from sigima.tests.helpers import check_array_result
+from sigima.tools.signal import stability
#: Number of samples used by the exact-expectation references. Kept small on purpose:
#: the trace is accumulated by looping over the canonical basis, so the cost is O(N²).
diff --git a/sigima/tests/viz/roi_items_unit_test.py b/sigima/tests/viz/roi_items_unit_test.py
new file mode 100644
index 00000000..5e192a2a
--- /dev/null
+++ b/sigima/tests/viz/roi_items_unit_test.py
@@ -0,0 +1,73 @@
+# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
+
+"""
+Test creation of PlotPy ROI items from an :class:`sigima.objects.ImageObj`.
+
+Regression test for the inverse rectangular ROI rendering bug: the PlotPy ROI
+item must be built from the rectangle's own shape, not from the extraction
+bounding box (which, for an inverse ROI, covers the whole image).
+"""
+
+# pylint: disable=import-outside-toplevel
+
+from __future__ import annotations
+
+import importlib.util
+
+import numpy as np
+import pytest
+
+import sigima.objects
+from sigima.objects import ImageObj
+
+
+def _make_image() -> ImageObj:
+ """Return a 100x100 float64 image with a unit coordinate system."""
+ obj = ImageObj(title="Test")
+ obj.data = np.zeros((100, 100), dtype=np.float64)
+ obj.set_uniform_coords(1.0, 1.0, 0.0, 0.0)
+ return obj
+
+
+class _RecordingBuilder:
+ """Minimal ``make`` stub recording the rectangle coordinates."""
+
+ def __init__(self) -> None:
+ self.rect_coords: tuple[float, float, float, float] | None = None
+
+ def annotated_rectangle(
+ self,
+ x0: float,
+ y0: float,
+ x1: float,
+ y1: float,
+ _title: str = "",
+ ) -> object:
+ """Record the rectangle coordinates and return a sentinel item."""
+ self.rect_coords = (x0, y0, x1, y1)
+ return object()
+
+
+@pytest.mark.skipif(
+ importlib.util.find_spec("plotpy") is None, reason="PlotPy not installed"
+)
+@pytest.mark.parametrize("inverse", [False, True])
+def test_rectangular_roi_item_uses_shape_not_extraction_box(
+ monkeypatch: pytest.MonkeyPatch, inverse: bool
+) -> None:
+ """Render a rectangular ROI as its own shape, even when inverse."""
+ from sigima.viz import viz_plotpy
+
+ builder = _RecordingBuilder()
+ monkeypatch.setattr(viz_plotpy, "make", builder)
+
+ obj = _make_image()
+ obj.roi = sigima.objects.create_image_roi(
+ "rectangle", [20.0, 30.0, 40.0, 25.0], inverse=inverse
+ )
+
+ create_items = getattr(viz_plotpy, "__create_image_roi_items")
+ items = create_items(obj)
+
+ assert len(items) == 1
+ assert builder.rect_coords == (20.0, 30.0, 60.0, 55.0)
diff --git a/sigima/tools/signal/replace_values.py b/sigima/tools/signal/replace_values.py
index dd7035db..a80db6a6 100644
--- a/sigima/tools/signal/replace_values.py
+++ b/sigima/tools/signal/replace_values.py
@@ -227,8 +227,8 @@ def interpolate_masked(
if interp_method == Interpolation1DMethod.LINEAR:
y[mask] = np.interp(x_masked, x_valid, y_valid)
elif interp_method == Interpolation1DMethod.SPLINE:
- knots, coeffs, degree = scipy.interpolate.splrep(x_valid, y_valid, s=0)
- y[mask] = scipy.interpolate.splev(x_masked, (knots, coeffs, degree), der=0)
+ spline = scipy.interpolate.splrep(x_valid, y_valid, s=0)
+ y[mask] = scipy.interpolate.splev(x_masked, spline, der=0)
elif interp_method == Interpolation1DMethod.QUADRATIC:
coeffs = np.polyfit(x_valid, y_valid, min(2, len(x_valid) - 1))
y[mask] = np.polyval(coeffs, x_masked)
diff --git a/sigima/viz/__init__.py b/sigima/viz/__init__.py
index df484576..d8856122 100644
--- a/sigima/viz/__init__.py
+++ b/sigima/viz/__init__.py
@@ -215,11 +215,6 @@ def create_marker(x: float, y: float, title: str | None = None) -> Any:
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.
@@ -300,8 +295,8 @@ def _select_backend() -> tuple[str, str]:
# Lazy backend initialization - deferred until first attribute access
_BACKEND_MODULE = None
-_BACKEND_NAME = None
-_BACKEND_SOURCE = None
+_BACKEND_NAME: str | None = None
+_BACKEND_SOURCE: str | None = None
_INITIALIZING = False # Flag to prevent recursion
# Public API: Set default values for BACKEND_NAME and BACKEND_SOURCE