Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions doc/locale/fr/LC_MESSAGES/release_notes/release_1.01.po
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ msgstr "Les identifiants d'annotation canoniques, les métadonnées, les extensi
msgid "The tooltip of a rectangular ROI defined in pixel indices now shows the pixels selected by the ROI, i.e. those whose centers lie inside the rectangle: the corners of the rectangle were rounded to the nearest pixel indices, which could show a selection shifted by one pixel once the rectangle had been moved."
msgstr "L'info-bulle d'une ROI rectangulaire définie en indices de pixels affiche désormais les pixels sélectionnés par la ROI, c'est-à-dire ceux dont le centre est situé dans le rectangle : les coins du rectangle étaient arrondis aux indices de pixels les plus proches, ce qui pouvait afficher une sélection décalée d'un pixel une fois le rectangle déplacé."

msgid "Auto downsampling of signals now starts as soon as a signal has more points than the maximum number of points set by the `sig_autodownsampling_maxpoints` option, and draws at most this number of points (closes [Issue #30](https://github.com/DataLab-Platform/SigimaX/issues/30)). It only started above 5 times this number (500 000 points with the default value of 100 000), so that signals of a few hundred thousand points were drawn in full and slowed down the display, and it could draw up to 20 % more points than the maximum. A change of this option is now also applied to curves which were already downsampled."
msgstr "Le sous-échantillonnage automatique des signaux commence désormais dès qu'un signal compte plus de points que le nombre maximal de points défini par l'option `sig_autodownsampling_maxpoints`, et trace au plus ce nombre de points (corrige [Issue #30](https://github.com/DataLab-Platform/SigimaX/issues/30)). Il ne commençait qu'au-delà de 5 fois ce nombre (500 000 points avec la valeur par défaut de 100 000), si bien que les signaux de quelques centaines de milliers de points étaient tracés en entier et ralentissaient l'affichage, et il pouvait tracer jusqu'à 20 % de points de plus que le maximum. Une modification de cette option s'applique désormais aussi aux courbes déjà sous-échantillonnées."

msgid "Main window"
msgstr "Fenêtre principale"

Expand Down
1 change: 1 addition & 0 deletions doc/release_notes/release_1.01.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
state are preserved across PlotPy editing round trips. Application-specific
opaque annotation entries continue to coexist with graphical annotations.
* The tooltip of a rectangular ROI defined in pixel indices now shows the pixels selected by the ROI, i.e. those whose centers lie inside the rectangle: the corners of the rectangle were rounded to the nearest pixel indices, which could show a selection shifted by one pixel once the rectangle had been moved.
* Auto downsampling of signals now starts as soon as a signal has more points than the maximum number of points set by the `sig_autodownsampling_maxpoints` option, and draws at most this number of points (closes [Issue #30](https://github.com/DataLab-Platform/SigimaX/issues/30)). It only started above 5 times this number (500 000 points with the default value of 100 000), so that signals of a few hundred thousand points were drawn in full and slowed down the display, and it could draw up to 20 % more points than the maximum. A change of this option is now also applied to curves which were already downsampled.

### Curve styles ###

Expand Down
21 changes: 14 additions & 7 deletions sigimax/adapters_plotpy/objects/signal.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,22 +260,29 @@ def apply_line_width(item: CurveItem) -> None:


def apply_downsampling(item: CurveItem, do_not_update: bool = False) -> None:
"""Apply downsampling to curve item
"""Apply automatic downsampling to curve item

When the ``sig_autodownsampling`` option is enabled, a curve with more points
than the ``sig_autodownsampling_maxpoints`` option is downsampled for display,
with the smallest factor drawing at most this number of points. The data of
the curve are not modified.

Args:
item: curve item
do_not_update: if True, do not update the item even if the downsampling
parameters have changed
"""
old_use_dsamp = item.param.use_dsamp
item.param.use_dsamp = False
param = item.param
old_state = (param.use_dsamp, param.dsamp_factor)
param.use_dsamp = False
if get_conf().sig_autodownsampling.get():
nbpoints = item.get_data()[0].size
maxpoints = get_conf().sig_autodownsampling_maxpoints.get()
if nbpoints > 5 * maxpoints:
item.param.use_dsamp = True
item.param.dsamp_factor = nbpoints // maxpoints
if not do_not_update and old_use_dsamp != item.param.use_dsamp:
if nbpoints > maxpoints:
param.use_dsamp = True
param.dsamp_factor = -(-nbpoints // maxpoints) # Rounded up
new_state = (param.use_dsamp, param.dsamp_factor)
if not do_not_update and old_state != new_state:
item.update_data()


Expand Down
5 changes: 4 additions & 1 deletion sigimax/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -1484,7 +1484,10 @@ def __init__(self) -> None:
category="view",
default=100000,
expected_type=int,
description="Maximum number of points before auto-downsampling kicks in.",
description=(
"Maximum number of points displayed for a signal: signals with more "
"points are downsampled for display (data are not modified)."
),
)
self.sig_autoscale_margin_percent = TypedOptionField(
self,
Expand Down
131 changes: 131 additions & 0 deletions sigimax/tests/adapters_plotpy/test_curve_downsampling.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.

"""
Curve auto downsampling tests (offscreen Qt)
--------------------------------------------

Tests of the automatic downsampling of curves with many points, driven by the
``sig_autodownsampling`` and ``sig_autodownsampling_maxpoints`` options.
"""

from __future__ import annotations

import math
from collections.abc import Generator

import numpy as np
import pytest
from guidata.qthelpers import qt_app_context
from plotpy.items import CurveItem
from sigima.objects import create_signal

from sigimax.adapters_plotpy.converters import create_adapter_from_object
from sigimax.adapters_plotpy.objects.signal import (
SignalObjPlotPyAdapter,
apply_downsampling,
)
from sigimax.config import get_conf

pytestmark = pytest.mark.gui

#: Maximum number of points used by the tests (even, see `_check_downsampling`)
MAXPOINTS = 1000


@pytest.fixture(name="autodownsampling")
def fixture_autodownsampling() -> Generator[None, None, None]:
"""Enable auto downsampling with a small maximum number of points, then restore
the options"""
conf = get_conf()
options = (conf.sig_autodownsampling, conf.sig_autodownsampling_maxpoints)
old_values = [option.get() for option in options]
conf.sig_autodownsampling.set(True)
conf.sig_autodownsampling_maxpoints.set(MAXPOINTS)
try:
with qt_app_context(exec_loop=False):
yield
finally:
for option, value in zip(options, old_values):
option.set(value)


def _make_adapter(size: int) -> SignalObjPlotPyAdapter:
"""Return the adapter of a new signal with a given number of points"""
x = np.linspace(0.0, 1.0, size)
obj = create_signal(f"Signal ({size} points)", x, np.sin(2 * np.pi * 50 * x))
return create_adapter_from_object(obj)


def _check_downsampling(item: CurveItem, size: int, maxpoints: int) -> None:
"""Check the downsampling of a curve item

Args:
item: curve item
size: number of points of the curve
maxpoints: maximum number of points to draw (even number)
"""
if size > maxpoints:
assert item.param.use_dsamp
assert item.param.dsamp_factor == math.ceil(size / maxpoints)
# Keeping one point out of N draws at most `maxpoints` points, and min/max
# downsampling (PlotPy >= 2.12) at most 2 more points (first and last points)
assert item.dataSize() <= maxpoints + 2
else:
assert not item.param.use_dsamp
assert item.dataSize() == size


@pytest.mark.usefixtures("autodownsampling")
@pytest.mark.parametrize(
"size",
[
MAXPOINTS // 2,
MAXPOINTS,
MAXPOINTS + 1,
5 * MAXPOINTS,
5 * MAXPOINTS + 1,
6 * MAXPOINTS - 1,
],
)
def test_downsampling_threshold(size: int) -> None:
"""Curves with more points than the maximum number of points are downsampled,
and are drawn with at most this number of points"""
item = _make_adapter(size).make_item()
_check_downsampling(item, size, MAXPOINTS)


@pytest.mark.usefixtures("autodownsampling")
def test_downsampling_disabled() -> None:
"""Curves are not downsampled when auto downsampling is disabled"""
get_conf().sig_autodownsampling.set(False)
size = 10 * MAXPOINTS
item = _make_adapter(size).make_item()
assert not item.param.use_dsamp
assert item.dataSize() == size


@pytest.mark.usefixtures("autodownsampling")
def test_downsampling_factor_change() -> None:
"""The drawn data follow a change of the downsampling factor alone"""
size = 20 * MAXPOINTS
item = _make_adapter(size).make_item()
_check_downsampling(item, size, MAXPOINTS)
get_conf().sig_autodownsampling_maxpoints.set(2 * MAXPOINTS)
apply_downsampling(item)
_check_downsampling(item, size, 2 * MAXPOINTS)
assert item.dataSize() > MAXPOINTS + 2


@pytest.mark.usefixtures("autodownsampling")
def test_update_item_follows_options() -> None:
"""Updating a plot item applies the current downsampling options"""
size = 20 * MAXPOINTS
adapter = _make_adapter(size)
item = adapter.make_item()
get_conf().sig_autodownsampling_maxpoints.set(4 * MAXPOINTS)
adapter.update_item(item, data_changed=False)
_check_downsampling(item, size, 4 * MAXPOINTS)
get_conf().sig_autodownsampling.set(False)
adapter.update_item(item, data_changed=False)
assert not item.param.use_dsamp
assert item.dataSize() == size
Loading