Skip to content

Commit 50c69b3

Browse files
committed
annotations and typing
1 parent c8c8104 commit 50c69b3

23 files changed

Lines changed: 906 additions & 430 deletions

plotpy/builder/annotation.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -219,10 +219,10 @@ def annotated_ellipse(
219219
y0: float,
220220
x1: float,
221221
y1: float,
222-
x2: float = None,
223-
y2: float = None,
224-
x3: float = None,
225-
y3: float = None,
222+
x2: float | None = None,
223+
y2: float | None = None,
224+
x3: float | None = None,
225+
y3: float | None = None,
226226
title: str | None = None,
227227
subtitle: str | None = None,
228228
show_label: bool | None = None,

plotpy/builder/curvemarker.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121
from __future__ import annotations
2222

23-
from collections.abc import Callable
23+
from typing import TYPE_CHECKING
2424

2525
import numpy # only to help intersphinx finding numpy doc
2626
import numpy as np
@@ -45,6 +45,9 @@
4545
update_style_attr,
4646
)
4747

48+
if TYPE_CHECKING:
49+
from typing import Callable
50+
4851
CURVE_COUNT = 0
4952
HISTOGRAM_COUNT = 0
5053

@@ -228,7 +231,8 @@ def mcurve(self, *args, **kwargs) -> CurveItem | list[CurveItem]:
228231
Args:
229232
args: x, y, style
230233
kwargs: title, color, linestyle, linewidth, marker, markersize,
231-
markerfacecolor, markeredgecolor, shade, curvestyle, baseline
234+
markerfacecolor, markeredgecolor, shade, curvestyle, baseline,
235+
downsampling_factor, use_downsampling
232236
233237
Returns:
234238
:py:class:`.CurveItem` object

plotpy/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -742,6 +742,7 @@ def make_title(basename, count):
742742
"marker/cross/pen/width": 1,
743743
"marker/cross/markerstyle": "Cross",
744744
},
745+
# colormaps options
745746
"colormaps": {
746747
"colormaps/default": "colormaps_default.json",
747748
"colormaps/custom": "colormaps_custom.json",

plotpy/items/annotation.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ class AnnotatedShape(AbstractShape):
5959
"""
6060

6161
__implements__ = (IBasePlotItem, ISerializableType)
62-
SHAPE_CLASS: AbstractShape = RectangleShape # to be overridden
62+
SHAPE_CLASS: type[AbstractShape] = RectangleShape # to be overridden
6363
LABEL_ANCHOR: str = ""
6464

6565
def __init__(self, annotationparam: AnnotationParam | None = None) -> None:
@@ -278,7 +278,7 @@ def get_tr_center(self):
278278
def get_tr_center_str(self):
279279
"""Return center coordinates as a string (with units)"""
280280
xc, yc = self.get_tr_center()
281-
return "( {} ; {} )".format(self.x_to_str(xc), self.y_to_str(yc))
281+
return f"( {self.x_to_str(xc)} ; {self.y_to_str(yc)} )"
282282

283283
def get_tr_size(self):
284284
"""Return shape size after applying transform matrix"""
@@ -287,7 +287,7 @@ def get_tr_size(self):
287287
def get_tr_size_str(self):
288288
"""Return size as a string (with units)"""
289289
xs, ys = self.get_tr_size()
290-
return "{} x {}".format(self.x_to_str(xs), self.y_to_str(ys))
290+
return f"{self.x_to_str(xs)} x {self.y_to_str(ys)}"
291291

292292
def get_infos(self) -> str:
293293
"""Get informations on current shape
@@ -601,7 +601,7 @@ def set_rect(self, x1, y1, x2, y2):
601601
self.shape.set_rect(x1, y1, x2, y2)
602602
self.set_label_position()
603603

604-
def get_rect(self):
604+
def get_rect(self) -> tuple[float, float, float, float]:
605605
"""
606606
Return the coordinates of the shape's top-left and bottom-right corners
607607
"""
@@ -659,7 +659,7 @@ def get_tr_angle(self):
659659
xcoords = self.get_transformed_coords(0, 1)
660660
_x, yr1 = self.apply_transform_matrix(1.0, 1.0)
661661
_x, yr2 = self.apply_transform_matrix(1.0, 2.0)
662-
return (compute_angle(reverse=yr1 > yr2, *xcoords) + 90) % 180 - 90
662+
return (compute_angle(*xcoords, reverse=yr1 > yr2) + 90) % 180 - 90
663663

664664
def get_bounding_rect_coords(self) -> tuple[float, float, float, float]:
665665
"""Return bounding rectangle coordinates (in plot coordinates)
@@ -802,7 +802,7 @@ def get_tr_angle(self):
802802
xcoords = self.get_transformed_coords(0, 1)
803803
_x, yr1 = self.apply_transform_matrix(1.0, 1.0)
804804
_x, yr2 = self.apply_transform_matrix(1.0, 2.0)
805-
return (compute_angle(reverse=yr1 > yr2, *xcoords) + 90) % 180 - 90
805+
return (compute_angle(*xcoords, reverse=yr1 > yr2) + 90) % 180 - 90
806806

807807
# ----AnnotatedShape API-----------------------------------------------------
808808
def set_label_position(self):
@@ -834,7 +834,7 @@ def get_infos(self) -> str:
834834
[
835835
_("Center:") + " " + self.get_tr_center_str(),
836836
_("Size:") + " " + self.get_tr_size_str(),
837-
_("Angle:") + " {:.1f}°".format(self.get_tr_angle()),
837+
_("Angle:") + f" {self.get_tr_angle():.1f}°",
838838
]
839839
)
840840

plotpy/items/curve/base.py

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -358,18 +358,15 @@ def get_data(self) -> tuple[np.ndarray, np.ndarray]:
358358
tuple: Tuple with two elements: x and y NumPy arrays
359359
"""
360360
assert isinstance(self._x, np.ndarray) and isinstance(self._y, np.ndarray)
361-
# if ignore_decimation:
362361
return self._x, self._y
363-
# return (
364-
# self._x[:: self.param.decimation],
365-
# self._y[:: self.param.decimation],
366-
# )
367362

368363
def update_data(self):
364+
"""Update curve data with current arrays."""
369365
if isinstance(self._x, np.ndarray) and isinstance(self._y, np.ndarray):
370366
self._setData(self._x, self._y)
371367

372368
def _setData(self, x: np.ndarray, y: np.ndarray):
369+
"""Wrapper around QwtPlotCurve.setData() to handle downsampling"""
373370
if not self.param.use_downsampling or self.param.downsampling_factor == 1:
374371
return super().setData(x, y)
375372
return super().setData(
@@ -387,14 +384,6 @@ def set_data(self, x: np.ndarray, y: np.ndarray) -> None:
387384
this method is called to update decimated data (i.e. only update 1/N value
388385
with N set in CurveItem.param.decimation).
389386
"""
390-
# if (
391-
# isinstance(self._x, np.ndarray)
392-
# and isinstance(self._y, np.ndarray)
393-
# # and decimated_data > 1
394-
# ):
395-
# self._x[:: self.param.decimation] = np.array(x, copy=False)
396-
# self._y[:: self.param.decimation] = np.array(y, copy=False)
397-
# else:
398387
self._x = np.array(x, copy=False)
399388
self._y = np.array(y, copy=False)
400389
self._setData(self._x, self._y)
@@ -512,9 +501,8 @@ def get_closest_x(self, xc: float) -> tuple[float, float]:
512501
# We assume X is sorted, otherwise we'd need :
513502
# argmin(abs(x-xc))
514503
i = self._x.searchsorted(xc)
515-
if i > 0:
516-
if np.fabs(self._x[i - 1] - xc) < np.fabs(self._x[i] - xc):
517-
return self._x[i - 1], self._y[i - 1]
504+
if i > 0 and np.fabs(self._x[i - 1] - xc) < np.fabs(self._x[i] - xc):
505+
return self._x[i - 1], self._y[i - 1]
518506
return self._x[i], self._y[i]
519507

520508
def move_local_point_to(

plotpy/items/image/base.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,13 @@
4141
ITrackableItemType,
4242
IVoiImageItemType,
4343
)
44+
45+
# do not import rectangleshape from plotpy.items directly
4446
from plotpy.items.shape.rectangle import RectangleShape
4547
from plotpy.lutrange import lut_range_threshold
4648
from plotpy.mathutils.arrayfuncs import get_nan_range
4749
from plotpy.mathutils.colormaps import FULLRANGE, get_cmap
4850
from plotpy.styles.image import RawImageParam
49-
from plotpy.widgets.colormap_widget import CustomQwtLinearColormap
5051

5152
if TYPE_CHECKING: # pragma: no cover
5253
import guidata.dataset.io
@@ -56,8 +57,8 @@
5657
from qtpy.QtGui import QColor, QPainter
5758

5859
from plotpy.interfaces import IItemType
59-
from plotpy.items import RectangleShape
6060
from plotpy.styles.base import ItemParameters
61+
from plotpy.widgets.colormap_widget import CustomQwtLinearColormap
6162

6263

6364
class BaseImageItem(QwtPlotItem):
@@ -111,6 +112,7 @@ def __init__(
111112
self.cmap_table = None
112113
self.cmap = None
113114
self.colormap_axis = None
115+
self._image: QG.QImage | None = None
114116

115117
self._offscreen = np.array((1, 1), np.uint32)
116118

@@ -125,7 +127,6 @@ def __init__(
125127
self.border_rect.set_style("plot", "shape/imageborder")
126128
# A, B, Background, Colormap
127129
self.lut = (1.0, 0.0, None, np.zeros((LUT_SIZE,), np.uint32))
128-
129130
self.set_lut_range((0.0, 255.0))
130131
self.setItemAttribute(QwtPlotItem.AutoScale)
131132
self.setItemAttribute(QwtPlotItem.Legend, True)
@@ -467,7 +468,10 @@ def get_color_map(self) -> CustomQwtLinearColormap | None:
467468
return self.cmap_table
468469

469470
def get_color_map_name(self) -> str | None:
470-
"""Get colormap name
471+
"""Get the current colormap name if set. The output value should not directly
472+
be used to retrieve a colormap object from one of the global colormap
473+
dictionaries, as the colormap name may contain capital letters whereas the
474+
colormap string keys of the dictionaries are all lowercase.
471475
472476
Returns:
473477
Colormap name
@@ -476,6 +480,18 @@ def get_color_map_name(self) -> str | None:
476480
return None
477481
return self.cmap_table.name
478482

483+
def get_color_map_key_name(self) -> str | None:
484+
"""Same as get_color_map_name, but returns the colormap name in lowercase. This
485+
is because the colormap string keys of the global colormap dictionaries are all
486+
lowercase.
487+
488+
Returns:
489+
Colormap name
490+
"""
491+
if self.cmap_table is None:
492+
return None
493+
return self.cmap_table.name.lower()
494+
479495
def set_interpolation(self, interp_mode: int, size: int | None = None) -> None:
480496
"""Set interpolation mode
481497
@@ -984,7 +1000,7 @@ def get_histogram(self, nbins: int) -> tuple[np.ndarray, np.ndarray]:
9841000

9851001
def __process_cross_section(self, ydata, apply_lut):
9861002
if apply_lut:
987-
a, b, bg, cmap = self.lut
1003+
a, b, _bg, _cmap = self.lut
9881004
return (ydata * a + b).clip(0, LUT_MAX)
9891005
else:
9901006
return ydata

plotpy/items/shape/rectangle.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ def __init__(
5050
y1: float = 0.0,
5151
x2: float = 0.0,
5252
y2: float = 0.0,
53-
shapeparam: ShapeParam = None,
53+
shapeparam: ShapeParam | None = None,
5454
) -> None:
5555
super().__init__(shapeparam=shapeparam)
5656
self.set_rect(x1, y1, x2, y2)
@@ -151,7 +151,7 @@ def __init__(
151151
y2: float = 0.0,
152152
x3: float = 0.0,
153153
y3: float = 0.0,
154-
shapeparam: ShapeParam = None,
154+
shapeparam: ShapeParam | None = None,
155155
) -> None:
156156
super().__init__(shapeparam=shapeparam)
157157
self.set_rect(x0, y0, x1, y1, x2, y2, x3, y3)

plotpy/mathutils/colormaps.py

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010

1111
import numpy as np
1212
import qtpy.QtGui as QG
13-
import qtpy.QtWidgets as QW
1413
from guidata.configtools import get_module_data_path
1514
from qwt import QwtInterval, toQImage
1615

@@ -57,7 +56,7 @@ def load_qwt_colormaps_from_json(json_path: str) -> dict[str, CustomQwtLinearCol
5756
Dictionnary of colormpas names -> CustomQwtLinearColormap
5857
"""
5958
return {
60-
name: CustomQwtLinearColormap.from_iterable(iterable, name=name)
59+
name.lower(): CustomQwtLinearColormap.from_iterable(iterable, name=name)
6160
for name, iterable in load_raw_colormaps_from_json(json_path).items()
6261
}
6362

@@ -95,10 +94,28 @@ def build_icon_from_cmap(
9594

9695

9796
def build_icon_from_cmap_name(cmap_name: str) -> QG.QIcon:
98-
return build_icon_from_cmap(get_cmap(cmap_name))
97+
"""Builds an QIcon representing the colormap from the colormap name found in
98+
ALL_COLORMAPS global variable.
99+
100+
Args:
101+
cmap_name: colormap name to search in ALL_COLORMAPS
102+
103+
Returns:
104+
QIcon representing the colormap
105+
"""
106+
return build_icon_from_cmap(get_cmap(cmap_name.lower()))
99107

100108

101109
def get_cmap(cmap_name: str) -> CustomQwtLinearColormap:
110+
"""Returns the colormap with the given name from the ALL_COLORMAPS global variable.
111+
If the colormap is not found, returns the DEFAULT colormap.
112+
113+
Args:
114+
cmap_name: colormap name to search in ALL_COLORMAPS
115+
116+
Returns:
117+
A CustomQwtLinearColormap instance corresponding to the given name
118+
"""
102119
return ALL_COLORMAPS.get(cmap_name, DEFAULT)
103120

104121

@@ -133,22 +150,18 @@ def get_cmap_path(config_path: str):
133150
return user_config_path
134151

135152

153+
# Load default colormaps path from the config file
136154
DEFAULT_COLORMAPS_PATH = get_cmap_path(
137155
CONF.get("colormaps", "colormaps/default", default="colormaps_default.json") # type: ignore
138156
)
157+
# Load custom colormaps path from the config file
139158
CUSTOM_COLORMAPS_PATH = get_cmap_path(
140159
CONF.get("colormaps", "colormaps/custom", default="colormaps_custom.json") # type: ignore
141160
)
142161

162+
# Load default and custom colormaps from json files
143163
DEFAULT_COLORMAPS = load_qwt_colormaps_from_json(DEFAULT_COLORMAPS_PATH)
144164
CUSTOM_COLORMAPS = load_qwt_colormaps_from_json(CUSTOM_COLORMAPS_PATH)
145165

166+
# Merge default and custom colormaps into a single dictionnary to simplify access
146167
ALL_COLORMAPS = {**DEFAULT_COLORMAPS, **CUSTOM_COLORMAPS}
147-
148-
if __name__ == "__main__":
149-
CUSTOM_COLORMAPS["glow_hot"] = CustomQwtLinearColormap.from_iterable(
150-
((0, "#0000ff"), (0.5, "#ff0000"), (1.0, "#ffff00"))
151-
)
152-
# print(f"Saving custom_colormaps at {CONF.get_path(CUSTOM_COLORMAPS_PATH)}")
153-
save_colormaps(CUSTOM_COLORMAPS_PATH, CUSTOM_COLORMAPS)
154-
print(CUSTOM_COLORMAPS)

plotpy/plot/base.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1585,8 +1585,9 @@ def unselect_item(self, item: itf.IBasePlotItem) -> None:
15851585
item.unselect()
15861586
self.SIG_ITEM_SELECTION_CHANGED.emit(self)
15871587

1588-
#TODO: fix return type of the following method (should probably return a QwtPlotItem)
1589-
def get_last_active_item(self, item_type: type[itf.IItemType]) -> itf.IBasePlotItem | None:
1588+
def get_last_active_item(
1589+
self, item_type: type[itf.IItemType]
1590+
) -> itf.IBasePlotItem | None:
15901591
"""Return last active item corresponding to passed `item_type`
15911592
15921593
Args:

plotpy/plot/manager.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
from __future__ import annotations
44

55
import weakref
6-
from collections.abc import Callable
76
from typing import TYPE_CHECKING, Any
87

98
from guidata.qthelpers import create_action
@@ -56,13 +55,15 @@
5655
)
5756

5857
if TYPE_CHECKING: # pragma: no cover
58+
from typing import Callable
59+
5960
from qwt import QwtPlotCanvas, QwtScaleDiv
6061

6162
from plotpy.panels.base import PanelWidget
6263
from plotpy.panels.contrastadjustment import ContrastAdjustment
6364
from plotpy.panels.csection import XCrossSection, YCrossSection
6465
from plotpy.panels.itemlist import PlotItemList
65-
from plotpy.tools.base import GuiTool
66+
from plotpy.tools.base import GuiTool, GuiToolT
6667

6768

6869
class DefaultPlotID:
@@ -187,7 +188,7 @@ def get_default_toolbar(self) -> QW.QToolBar:
187188
"""
188189
return self.default_toolbar
189190

190-
def add_tool(self, ToolKlass: GuiTool, *args, **kwargs) -> GuiTool:
191+
def add_tool(self, ToolKlass: type[GuiToolT], *args, **kwargs) -> GuiToolT:
191192
"""
192193
Register a tool to the manager
193194
* ToolKlass: tool's class (see :ref:`tools`)
@@ -211,7 +212,7 @@ def add_tool(self, ToolKlass: GuiTool, *args, **kwargs) -> GuiTool:
211212
self.default_tool = tool
212213
return tool
213214

214-
def get_tool(self, ToolKlass: GuiTool) -> GuiTool:
215+
def get_tool(self, ToolKlass: type[GuiToolT]) -> GuiToolT | None:
215216
"""Return tool instance from its class
216217
217218
Args:
@@ -497,7 +498,7 @@ def create_action(
497498
icon: QG.QIcon | None = None,
498499
tip: str | None = None,
499500
checkable: bool | None = None,
500-
context: QC.Qt.ShortcutContext = QC.Qt.WindowShortcut,
501+
context: QC.Qt.ShortcutContext = QC.Qt.ShortcutContext.WindowShortcut,
501502
enabled: bool | None = None,
502503
):
503504
"""

0 commit comments

Comments
 (0)