wxmplot 2026.2.2__tar.gz → 2026.2.3__tar.gz
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.
- {wxmplot-2026.2.2/wxmplot.egg-info → wxmplot-2026.2.3}/PKG-INFO +5 -2
- wxmplot-2026.2.3/examples/image_canvas_scroll.py +123 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/pyproject.toml +4 -2
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/wxmplot/__init__.py +1 -5
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/wxmplot/image_canvas.py +22 -8
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/wxmplot/line_plot.py +3 -3
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/wxmplot/version.py +3 -3
- {wxmplot-2026.2.2 → wxmplot-2026.2.3/wxmplot.egg-info}/PKG-INFO +5 -2
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/wxmplot.egg-info/SOURCES.txt +1 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/wxmplot.egg-info/requires.txt +3 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/wxmplot.egg-info/scm_file_list.json +1 -0
- wxmplot-2026.2.3/wxmplot.egg-info/scm_version.json +8 -0
- wxmplot-2026.2.2/wxmplot.egg-info/scm_version.json +0 -8
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/ChangeLog +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/LICENSE +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/MANIFEST.in +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/README.md +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/examples/BasicExamples.ipynb +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/examples/DynamicExamples.ipynb +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/examples/basic_screenshot.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/examples/compare_dislin_mpl.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/examples/compare_plplot.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/examples/contour.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/examples/demo.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/examples/discrete_data.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/examples/errorbar.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/examples/extra_marker_and_line_reporting.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/examples/fill_to_zero.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/examples/fill_uncertainties.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/examples/floatcontrol.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/examples/histogram.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/examples/image.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/examples/image_canvas_ex.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/examples/image_scroll.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/examples/imagematrix.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/examples/interactive_multiplewindows.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/examples/leftright.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/examples/multiple_yaxes.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/examples/multiplot.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/examples/pilatus_display.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/examples/plot_fromdatafile.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/examples/plot_many_traces.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/examples/plotapp.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/examples/rgb_image.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/examples/scatterplot.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/examples/scope_mode_function.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/examples/scope_mode_generator.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/examples/scope_mode_mpl_compare.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/examples/simple.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/examples/stackedplot.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/examples/stripchart.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/examples/theme_compare.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/examples/tiff_display.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/examples/tifffile.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/examples/time.dat +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/examples/time_update.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/examples/timedata.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/examples/wxmplot_jupyter.ipynb +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/examples/xafs.dat +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/setup.cfg +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/setup.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/tests/basic_test.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/wxmplot/allow_idle_macosx.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/wxmplot/baseframe.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/wxmplot/basepanel.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/wxmplot/colors.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/wxmplot/config.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/wxmplot/histogram.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/wxmplot/imageconf.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/wxmplot/imageframe.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/wxmplot/imagematrixframe.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/wxmplot/imagepanel.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/wxmplot/inputhook.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/wxmplot/interactive.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/wxmplot/multiframe.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/wxmplot/plotapp.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/wxmplot/plotconfigframe.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/wxmplot/plotframe.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/wxmplot/plotpanel.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/wxmplot/residualplotframe.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/wxmplot/stackedplotframe.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/wxmplot/utils.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/wxmplot/vispy_utils.py +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/wxmplot.egg-info/dependency_links.txt +0 -0
- {wxmplot-2026.2.2 → wxmplot-2026.2.3}/wxmplot.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wxmplot
|
|
3
|
-
Version: 2026.2.
|
|
3
|
+
Version: 2026.2.3
|
|
4
4
|
Summary: wxPython plotting widgets using matplotlib
|
|
5
5
|
Author-email: Matthew Newville <matt.newville@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -22,7 +22,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
22
22
|
Classifier: Programming Language :: Python :: 3.12
|
|
23
23
|
Classifier: Programming Language :: Python :: 3.13
|
|
24
24
|
Classifier: Programming Language :: Python :: 3.14
|
|
25
|
-
Requires-Python: >=3.
|
|
25
|
+
Requires-Python: >=3.11
|
|
26
26
|
Description-Content-Type: text/markdown
|
|
27
27
|
License-File: LICENSE
|
|
28
28
|
Requires-Dist: wxPython>=4.2.3
|
|
@@ -46,6 +46,9 @@ Requires-Dist: Sphinx; extra == "doc"
|
|
|
46
46
|
Requires-Dist: jupyter_sphinx; extra == "doc"
|
|
47
47
|
Requires-Dist: sphinx-copybutton; extra == "doc"
|
|
48
48
|
Requires-Dist: sphinxcontrib-video; extra == "doc"
|
|
49
|
+
Requires-Dist: nbsphinx; extra == "doc"
|
|
50
|
+
Requires-Dist: sphinx-design; extra == "doc"
|
|
51
|
+
Requires-Dist: sphinx-breeze-theme; extra == "doc"
|
|
49
52
|
Provides-Extra: test
|
|
50
53
|
Requires-Dist: pytest; extra == "test"
|
|
51
54
|
Requires-Dist: pytest-cov; extra == "test"
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import time, os, sys
|
|
2
|
+
from random import shuffle
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
import wx
|
|
7
|
+
from wxutils import pack
|
|
8
|
+
from wxmplot import ImageFrame
|
|
9
|
+
from wxmplot.image_canvas import ImageCanvas
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ImageCanvasFrame(wx.Frame):
|
|
13
|
+
def __init__(self, parent, title='WXMPLOT Image Canvas', size=(700,600)):
|
|
14
|
+
wx.Frame.__init__(self, None, title=title, size=size,
|
|
15
|
+
style=wx.DEFAULT_FRAME_STYLE)
|
|
16
|
+
|
|
17
|
+
self.im_canvas = ImageCanvas(self)
|
|
18
|
+
sizer = wx.BoxSizer(wx.VERTICAL)
|
|
19
|
+
sizer.Add(self.im_canvas, 1, wx.ALL|wx.GROW)
|
|
20
|
+
pack(self, sizer)
|
|
21
|
+
|
|
22
|
+
def set_colormap(self, colormap: str):
|
|
23
|
+
self.im_canvas.set_colormap(colormap)
|
|
24
|
+
|
|
25
|
+
def set_image(self, image: np.ndarray):
|
|
26
|
+
self.im_canvas.set_image(image)
|
|
27
|
+
self.im_canvas.set_auto_scale(True, level=0.5)
|
|
28
|
+
|
|
29
|
+
class TestFrame(wx.Frame):
|
|
30
|
+
def __init__(self, parent=None, *args,**kwds):
|
|
31
|
+
kwds["style"] = wx.DEFAULT_FRAME_STYLE|wx.RESIZE_BORDER|wx.TAB_TRAVERSAL
|
|
32
|
+
|
|
33
|
+
wx.Frame.__init__(self, parent, -1, '',
|
|
34
|
+
wx.DefaultPosition, wx.Size(-1,-1), **kwds)
|
|
35
|
+
self.SetTitle(" WXMPlot Image Scroll")
|
|
36
|
+
|
|
37
|
+
self.SetFont(wx.Font(12,wx.SWISS,wx.NORMAL,wx.BOLD,False))
|
|
38
|
+
menu = wx.Menu()
|
|
39
|
+
|
|
40
|
+
self.arrays = []
|
|
41
|
+
self.imageframe = None
|
|
42
|
+
|
|
43
|
+
framesizer = wx.BoxSizer(wx.VERTICAL)
|
|
44
|
+
|
|
45
|
+
panel = wx.Panel(self, -1, size=(-1, -1))
|
|
46
|
+
panelsizer = wx.BoxSizer(wx.VERTICAL)
|
|
47
|
+
|
|
48
|
+
panelsizer.Add( wx.StaticText(panel, -1, 'Show Images'),
|
|
49
|
+
0, wx.LEFT|wx.EXPAND, 10)
|
|
50
|
+
|
|
51
|
+
btn = wx.Button(panel, -1, 'start scrolling', size=(-1,-1))
|
|
52
|
+
btn.Bind(wx.EVT_BUTTON,self.onScrollImages)
|
|
53
|
+
|
|
54
|
+
btn2 = wx.Button(panel, -1, 'stop scrolling', size=(-1,-1))
|
|
55
|
+
btn2.Bind(wx.EVT_BUTTON,self.onStop)
|
|
56
|
+
|
|
57
|
+
self.msg = wx.StaticText(panel, label='000 images in 00.0000 seconds ', size=(500, -1))
|
|
58
|
+
|
|
59
|
+
panelsizer.Add(btn, 0, wx.ALIGN_LEFT|wx.ALIGN_CENTER|wx.LEFT, 5)
|
|
60
|
+
panelsizer.Add(btn2, 0, wx.ALIGN_LEFT|wx.ALIGN_CENTER|wx.LEFT, 5)
|
|
61
|
+
panelsizer.Add(self.msg, 0, wx.ALIGN_LEFT|wx.ALIGN_CENTER|wx.LEFT, 5)
|
|
62
|
+
|
|
63
|
+
panel.SetSizer(panelsizer)
|
|
64
|
+
panelsizer.Fit(panel)
|
|
65
|
+
|
|
66
|
+
framesizer.Add(panel, 0, wx.EXPAND, 2)
|
|
67
|
+
self.SetSizer(framesizer)
|
|
68
|
+
framesizer.Fit(self)
|
|
69
|
+
self.create_data()
|
|
70
|
+
self.ShowImageCanvasFrame()
|
|
71
|
+
self.count = 0
|
|
72
|
+
self.Bind(wx.EVT_TIMER, self.onTimer)
|
|
73
|
+
self.timer = wx.Timer(self)
|
|
74
|
+
self.Refresh()
|
|
75
|
+
|
|
76
|
+
def create_data(self):
|
|
77
|
+
nx = ny = 1001
|
|
78
|
+
print("Creating 240 images..")
|
|
79
|
+
for xoff in np.linspace(-2.5, 2.5, 20):
|
|
80
|
+
y, x = np.mgrid[-10+xoff:10+xoff:nx*1j, -10:10:nx*1j]
|
|
81
|
+
for xscale in (4.5, 5.0, 6.0):
|
|
82
|
+
for yscale in (0.8, 1.0, 1.1, 1.2):
|
|
83
|
+
dat = np.sin(x*x/xscale + y*y/yscale)/(1 + (x+y)*(x+y))
|
|
84
|
+
dat += np.random.normal(scale=0.12, size=(nx, ny))
|
|
85
|
+
self.arrays.append(dat)
|
|
86
|
+
shuffle(self.arrays)
|
|
87
|
+
print("Built %d arrays, shape=%s" % (len(self.arrays), repr(dat.shape)))
|
|
88
|
+
|
|
89
|
+
def ShowImageCanvasFrame(self):
|
|
90
|
+
if self.imageframe is None:
|
|
91
|
+
self.imageframe = ImageCanvasFrame(self)
|
|
92
|
+
try:
|
|
93
|
+
self.imageframe.Show()
|
|
94
|
+
except RuntimeError:
|
|
95
|
+
self.imageframe = ImageCanvasframe(self)
|
|
96
|
+
self.imageframe.Show()
|
|
97
|
+
|
|
98
|
+
self.imageframe.set_image(self.arrays[0])
|
|
99
|
+
self.imageframe.set_colormap('bone')
|
|
100
|
+
self.imageframe.Raise()
|
|
101
|
+
|
|
102
|
+
def onTimer(self, event):
|
|
103
|
+
self.count += 1
|
|
104
|
+
message = " %d images in %.3f sec" % (self.count,
|
|
105
|
+
time.time()-self.t0)
|
|
106
|
+
self.msg.SetLabel(message)
|
|
107
|
+
wx.CallAfter(self.imageframe.set_image,
|
|
108
|
+
self.arrays[self.count % len(self.arrays)])
|
|
109
|
+
|
|
110
|
+
def onScrollImages(self,event=None):
|
|
111
|
+
self.ShowImageCanvasFrame()
|
|
112
|
+
self.count = 0
|
|
113
|
+
self.t0 = time.time()
|
|
114
|
+
self.timer.Start(10)
|
|
115
|
+
|
|
116
|
+
def onStop(self,event=None):
|
|
117
|
+
self.timer.Stop()
|
|
118
|
+
|
|
119
|
+
if __name__ == '__main__':
|
|
120
|
+
app = wx.App()
|
|
121
|
+
f = TestFrame(None,-1)
|
|
122
|
+
f.Show(True)
|
|
123
|
+
app.MainLoop()
|
|
@@ -15,7 +15,7 @@ omit = ["tests/*"]
|
|
|
15
15
|
[project]
|
|
16
16
|
name = "wxmplot"
|
|
17
17
|
dynamic = ["version"]
|
|
18
|
-
requires-python = ">= 3.
|
|
18
|
+
requires-python = ">= 3.11"
|
|
19
19
|
description = "wxPython plotting widgets using matplotlib"
|
|
20
20
|
readme = "README.md"
|
|
21
21
|
authors = [
|
|
@@ -64,6 +64,8 @@ Tracker = "https://github.com/newville/wxmplot/issues"
|
|
|
64
64
|
|
|
65
65
|
[project.optional-dependencies]
|
|
66
66
|
dev = ["build", "twine"]
|
|
67
|
-
doc = ["Sphinx", "jupyter_sphinx", "sphinx-copybutton", "sphinxcontrib-video"
|
|
67
|
+
doc = ["Sphinx", "jupyter_sphinx", "sphinx-copybutton", "sphinxcontrib-video",
|
|
68
|
+
"nbsphinx", "sphinx-design", "sphinx-breeze-theme"]
|
|
69
|
+
|
|
68
70
|
test = ["pytest", "pytest-cov", "coverage"]
|
|
69
71
|
all = ["wxmplot[dev, doc, test]"]
|
|
@@ -8,10 +8,8 @@
|
|
|
8
8
|
with from wx programs. More importantly, the widgets created by wxmplot
|
|
9
9
|
give the end user a flexible set of tools for interacting with their
|
|
10
10
|
data and customizing the plots and displays. wxmplot panels are more
|
|
11
|
-
interactive than typical
|
|
11
|
+
interactive than typical displays from matplotlib's pyplot module.
|
|
12
12
|
|
|
13
|
-
version: 0.9.56
|
|
14
|
-
last update: 2023-Jul-02
|
|
15
13
|
License: MIT
|
|
16
14
|
Author: Matthew Newville <newville@cars.uchicago.edu>
|
|
17
15
|
Center for Advanced Radiation Sources,
|
|
@@ -47,5 +45,3 @@ from .image_canvas import ImageCanvas, BinMethod
|
|
|
47
45
|
from .line_plot import LinePlot
|
|
48
46
|
from .histogram import Histogram, compute_histogram_data
|
|
49
47
|
|
|
50
|
-
if sys.platform.lower() == 'darwin':
|
|
51
|
-
wx.PyApp.IsDisplayAvailable = lambda _: True
|
|
@@ -103,6 +103,7 @@ class ImageCanvas(wx.Panel):
|
|
|
103
103
|
self._first_image = True
|
|
104
104
|
self._auto_scale = True
|
|
105
105
|
self._filter_gaps = False
|
|
106
|
+
self._percentile_level: "float | None" = None
|
|
106
107
|
self._min_value = 0.0
|
|
107
108
|
self._max_value = 255.0
|
|
108
109
|
self._data_min = 0.0
|
|
@@ -168,7 +169,7 @@ class ImageCanvas(wx.Panel):
|
|
|
168
169
|
self._pixel_info_text = scene.visuals.Text(
|
|
169
170
|
text="",
|
|
170
171
|
color=vispy_colour("green"),
|
|
171
|
-
font_size=
|
|
172
|
+
font_size=8,
|
|
172
173
|
bold=True,
|
|
173
174
|
anchor_x="left",
|
|
174
175
|
anchor_y="top",
|
|
@@ -179,7 +180,7 @@ class ImageCanvas(wx.Panel):
|
|
|
179
180
|
self._fps_text = scene.visuals.Text(
|
|
180
181
|
text="",
|
|
181
182
|
color=vispy_colour("green"),
|
|
182
|
-
font_size=
|
|
183
|
+
font_size=8,
|
|
183
184
|
bold=True,
|
|
184
185
|
anchor_x="left",
|
|
185
186
|
anchor_y="top",
|
|
@@ -226,11 +227,23 @@ class ImageCanvas(wx.Panel):
|
|
|
226
227
|
self._image_visual.clim = (self._norm_fwd(min_val), self._norm_fwd(max_val))
|
|
227
228
|
self._canvas.update()
|
|
228
229
|
|
|
229
|
-
def set_auto_scale(self, enabled: bool, level:
|
|
230
|
+
def set_auto_scale(self, enabled: bool, level: "float | None" = None) -> None:
|
|
230
231
|
"""Enable or disable automatic contrast scaling."""
|
|
231
232
|
self._auto_scale = enabled
|
|
232
233
|
if enabled and self._raw_image is not None:
|
|
233
|
-
|
|
234
|
+
effective_level = level if level is not None else self._percentile_level
|
|
235
|
+
self._min_value, self._max_value = self._compute_auto_clim(self._raw_image, level=effective_level)
|
|
236
|
+
self._image_visual.clim = (self._norm_fwd(self._min_value), self._norm_fwd(self._max_value))
|
|
237
|
+
self._canvas.update()
|
|
238
|
+
|
|
239
|
+
def set_percentile_level(self, level: "float | None") -> None:
|
|
240
|
+
"""Set a persistent contrast clip applied on every frame regardless of auto-scale (None=off)."""
|
|
241
|
+
self._percentile_level = level
|
|
242
|
+
if self._raw_image is not None:
|
|
243
|
+
if level is not None:
|
|
244
|
+
self._min_value, self._max_value = self._compute_auto_clim(self._raw_image, level=level)
|
|
245
|
+
elif self._auto_scale:
|
|
246
|
+
self._min_value, self._max_value = self._compute_auto_clim(self._raw_image)
|
|
234
247
|
self._image_visual.clim = (self._norm_fwd(self._min_value), self._norm_fwd(self._max_value))
|
|
235
248
|
self._canvas.update()
|
|
236
249
|
|
|
@@ -386,7 +399,9 @@ class ImageCanvas(wx.Panel):
|
|
|
386
399
|
gpu_image = np.ascontiguousarray(gpu_image)
|
|
387
400
|
|
|
388
401
|
self._data_min, self._data_max = self._compute_full_range(full_res)
|
|
389
|
-
if self.
|
|
402
|
+
if self._percentile_level is not None:
|
|
403
|
+
self._min_value, self._max_value = self._compute_auto_clim(full_res, level=self._percentile_level)
|
|
404
|
+
elif self._auto_scale:
|
|
390
405
|
self._min_value, self._max_value = self._compute_auto_clim(full_res)
|
|
391
406
|
|
|
392
407
|
display_image = self._norm_apply(gpu_image)
|
|
@@ -413,7 +428,7 @@ class ImageCanvas(wx.Panel):
|
|
|
413
428
|
|
|
414
429
|
def _compute_auto_clim(self, img: np.ndarray, level=None) -> tuple[float, float]:
|
|
415
430
|
"""Estimate display clim from a sampled subset of pixels.
|
|
416
|
-
|
|
431
|
+
level: symmetric contrast_level — clips this % from each side (e.g. 2 → [2, 98])."""
|
|
417
432
|
flat = img.reshape(-1)
|
|
418
433
|
step = max(1, flat.size // self._AUTO_CLIM_SAMPLE_BUDGET)
|
|
419
434
|
sample = flat[::step]
|
|
@@ -432,9 +447,8 @@ class ImageCanvas(wx.Panel):
|
|
|
432
447
|
else:
|
|
433
448
|
vmin, vmax = float(sample.min()), float(sample.max())
|
|
434
449
|
|
|
435
|
-
# alternative implementation
|
|
436
450
|
if level is not None:
|
|
437
|
-
vmin, vmax = np.percentile(img, [level, 100.0-level])
|
|
451
|
+
vmin, vmax = np.percentile(img, [level, 100.0 - level])
|
|
438
452
|
|
|
439
453
|
# Ensure min != max to avoid histogram handle overlap
|
|
440
454
|
if vmax <= vmin:
|
|
@@ -528,7 +528,7 @@ class LinePlot(wx.Panel):
|
|
|
528
528
|
gc.StrokeLine(ml, mt + ph, ml + pw, mt + ph)
|
|
529
529
|
gc.StrokeLine(ml, mt, ml, mt + ph)
|
|
530
530
|
|
|
531
|
-
font_small = wx.Font(10, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD)
|
|
531
|
+
font_small = wx.Font(wx.Size(0, 10), wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD)
|
|
532
532
|
ranges = self._data_ranges()
|
|
533
533
|
|
|
534
534
|
if ranges is None:
|
|
@@ -536,7 +536,7 @@ class LinePlot(wx.Panel):
|
|
|
536
536
|
gc.SetPen(wx.TRANSPARENT_PEN)
|
|
537
537
|
gc.DrawRectangle(ml, mt, pw, ph)
|
|
538
538
|
gc.SetFont(
|
|
539
|
-
wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD),
|
|
539
|
+
wx.Font(wx.Size(0, 13), wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD),
|
|
540
540
|
empty_text,
|
|
541
541
|
)
|
|
542
542
|
msg = "No data"
|
|
@@ -586,7 +586,7 @@ class LinePlot(wx.Panel):
|
|
|
586
586
|
gc.PopState()
|
|
587
587
|
|
|
588
588
|
if self._hover_data_x is not None and self._hover_data_y is not None:
|
|
589
|
-
hover_font = wx.Font(
|
|
589
|
+
hover_font = wx.Font(wx.Size(0, 8), wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD)
|
|
590
590
|
gc.SetFont(hover_font, get_theme().green)
|
|
591
591
|
text = self.format_hover_info(self._hover_data_x, self._hover_data_y)
|
|
592
592
|
tw, th = gc.GetTextExtent(text)
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '2026.2.
|
|
22
|
-
__version_tuple__ = version_tuple = (2026, 2,
|
|
21
|
+
__version__ = version = '2026.2.3'
|
|
22
|
+
__version_tuple__ = version_tuple = (2026, 2, 3)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'g514ae8af4'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wxmplot
|
|
3
|
-
Version: 2026.2.
|
|
3
|
+
Version: 2026.2.3
|
|
4
4
|
Summary: wxPython plotting widgets using matplotlib
|
|
5
5
|
Author-email: Matthew Newville <matt.newville@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -22,7 +22,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
22
22
|
Classifier: Programming Language :: Python :: 3.12
|
|
23
23
|
Classifier: Programming Language :: Python :: 3.13
|
|
24
24
|
Classifier: Programming Language :: Python :: 3.14
|
|
25
|
-
Requires-Python: >=3.
|
|
25
|
+
Requires-Python: >=3.11
|
|
26
26
|
Description-Content-Type: text/markdown
|
|
27
27
|
License-File: LICENSE
|
|
28
28
|
Requires-Dist: wxPython>=4.2.3
|
|
@@ -46,6 +46,9 @@ Requires-Dist: Sphinx; extra == "doc"
|
|
|
46
46
|
Requires-Dist: jupyter_sphinx; extra == "doc"
|
|
47
47
|
Requires-Dist: sphinx-copybutton; extra == "doc"
|
|
48
48
|
Requires-Dist: sphinxcontrib-video; extra == "doc"
|
|
49
|
+
Requires-Dist: nbsphinx; extra == "doc"
|
|
50
|
+
Requires-Dist: sphinx-design; extra == "doc"
|
|
51
|
+
Requires-Dist: sphinx-breeze-theme; extra == "doc"
|
|
49
52
|
Provides-Extra: test
|
|
50
53
|
Requires-Dist: pytest; extra == "test"
|
|
51
54
|
Requires-Dist: pytest-cov; extra == "test"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|