Skip to content

fix magnitude of limits in case of a single value - #1233

Merged
sergey-yaroslavtsev merged 2 commits into
masterfrom
fix_roi_mpl
Jul 29, 2026
Merged

fix magnitude of limits in case of a single value#1233
sergey-yaroslavtsev merged 2 commits into
masterfrom
fix_roi_mpl

Conversation

@sergey-yaroslavtsev

Copy link
Copy Markdown
Collaborator

a strange bug appears in module because it uses matplotlib==3.11.0

minimal reproduction of an issue:

import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt

V = 207769447.39       
DELTA = 0.044             
fig, ax = plt.subplots(figsize=(7.4, 4.9), dpi=100)
# thin view at large magnitude, (llike PyMca.setLimits)
ax.set_xlim(-DELTA, DELTA)
ax.set_ylim(V - DELTA, V + DELTA)
# text outside that thin view
ax.text(0.0, 0.0, "marker", transform=ax.transData)
fig.canvas.draw()          # crashes on >= 3.11, fine on <= 3.10

tested on 3.10, 3.11.0, 3.11.1

@woutdenolf

Copy link
Copy Markdown
Collaborator

This might be the proper fix:

@@ -2098,11 +2098,11 @@ def insertYMarker(self, y, legend=None, text=None,
             xmin, xmax = self.getGraphXLimits()
             delta = abs(xmax - xmin)
             if xmin > xmax:
                 xmax = xmin
             xmax -= 0.005 * delta
-            line._infoText = self.ax.text(y, xmax, text,
+            line._infoText = self.ax.text(xmax, y, text,
                                           color=color,
                                           horizontalalignment='left',
                                           verticalalignment='top')
         line._plot_options = ["ymarker"]
         if selectable:

@woutdenolf

woutdenolf commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

MRE to reproduce the bug with PyMca's MatplotlibBackend

from PyMca5.PyMcaGraph.backends.MatplotlibBackend import MatplotlibBackend

V = 1e10

backend = MatplotlibBackend()
backend.setLimits(0, 1024, V, V)
backend.insertYMarker(V, text="marker")
backend.ax.get_figure().canvas.draw()
print("OK")

The fix in #1233 (comment) solves it.

@sergey-yaroslavtsev

sergey-yaroslavtsev commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator Author

I have data which is 1x1000x1000 -> ROI image -> select "1D data is first dimension"
Old behavior -> error; your fix give same error; fix in PR works.

It a strange thing to do... but user found it

@sergey-yaroslavtsev

Copy link
Copy Markdown
Collaborator Author

The full error message:

Error
Traceback (most recent call last):
  File "<string>", line 1244, in __roiImaging
  File "C:\Users\yaroslav\PycharmProjects\PyMca\pymca\src\PyMca5\PyMcaGui\pymca\QStackWidget.py", line 637, in loadStack
    self.setStack(stack)
  File "C:\Users\yaroslav\PycharmProjects\PyMca\pymca\src\PyMca5\PyMcaGui\pymca\QStackWidget.py", line 255, in setStack
    StackBase.StackBase.setStack(self, *var, **kw)
  File "C:\Users\yaroslav\PycharmProjects\PyMca\pymca\src\PyMca5\PyMcaCore\StackBase.py", line 247, in setStack
    self.stackUpdated(info.get("positioners", None))
  File "C:\Users\yaroslav\PycharmProjects\PyMca\pymca\src\PyMca5\PyMcaCore\StackBase.py", line 370, in stackUpdated
    self.showOriginalMca()
  File "C:\Users\yaroslav\PycharmProjects\PyMca\pymca\src\PyMca5\PyMcaGui\pymca\QStackWidget.py", line 1033, in showOriginalMca
    self.sendMcaSelection(self._mcaData0, action="ADD")
  File "C:\Users\yaroslav\PycharmProjects\PyMca\pymca\src\PyMca5\PyMcaGui\pymca\QStackWidget.py", line 1217, in sendMcaSelection
    self.mcaWidget._addSelection([sel])
  File "C:\Users\yaroslav\PycharmProjects\PyMca\pymca\src\PyMca5\PyMcaGui\pymca\McaWindow.py", line 1088, in _addSelection
    self.addCurve(xhelp, data,
  File "C:\Users\yaroslav\PycharmProjects\PyMca\pymca\src\PyMca5\PyMcaGui\pymca\McaWindow.py", line 1594, in addCurve
    super(McaWindow, self).addCurve(x, y, legend=legend, info=info,
  File "C:\Users\yaroslav\PycharmProjects\PyMca\pymca\src\PyMca5\PyMcaGui\pymca\ScanWindow.py", line 1313, in addCurve
    super(ScanWindow, self).addCurve(x, y, legend=legend, info=info,
  File "C:\Users\yaroslav\PycharmProjects\PyMca\pymca\src\PyMca5\PyMcaGraph\Plot.py", line 546, in addCurve
    self.resetZoom()
  File "C:\Users\yaroslav\PycharmProjects\PyMca\pymca\src\PyMca5\PyMcaGui\plotting\PlotWindow.py", line 596, in resetZoom
    super(PlotWindow, self).resetZoom(**kw)
  File "C:\Users\yaroslav\PycharmProjects\PyMca\pymca\src\PyMca5\PyMcaGraph\Plot.py", line 1099, in resetZoom
    self._plot.resetZoom(dataMargins)
  File "C:\Users\yaroslav\PycharmProjects\PyMca\pymca\src\PyMca5\PyMcaGraph\backends\MatplotlibBackend.py", line 2203, in resetZoom
    self.replot()
  File "C:\Users\yaroslav\PycharmProjects\PyMca\pymca\src\PyMca5\PyMcaGraph\backends\MatplotlibBackend.py", line 2219, in replot
    self.graph.draw()
  File "C:\Users\yaroslav\PycharmProjects\PyMca\.venv\Lib\site-packages\matplotlib\backends\backend_agg.py", line 438, in draw
    self.figure.draw(self.renderer)
  File "C:\Users\yaroslav\PycharmProjects\PyMca\.venv\Lib\site-packages\matplotlib\artist.py", line 94, in draw_wrapper
    result = draw(artist, renderer, *args, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\yaroslav\PycharmProjects\PyMca\.venv\Lib\site-packages\matplotlib\artist.py", line 71, in draw_wrapper
    return draw(artist, renderer)
           ^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\yaroslav\PycharmProjects\PyMca\.venv\Lib\site-packages\matplotlib\figure.py", line 3282, in draw
    mimage._draw_list_compositing_images(
  File "C:\Users\yaroslav\PycharmProjects\PyMca\.venv\Lib\site-packages\matplotlib\image.py", line 133, in _draw_list_compositing_images
    a.draw(renderer)
  File "C:\Users\yaroslav\PycharmProjects\PyMca\.venv\Lib\site-packages\matplotlib\artist.py", line 71, in draw_wrapper
    return draw(artist, renderer)
           ^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\yaroslav\PycharmProjects\PyMca\.venv\Lib\site-packages\matplotlib\axes\_base.py", line 3367, in draw
    mimage._draw_list_compositing_images(
  File "C:\Users\yaroslav\PycharmProjects\PyMca\.venv\Lib\site-packages\matplotlib\image.py", line 133, in _draw_list_compositing_images
    a.draw(renderer)
  File "C:\Users\yaroslav\PycharmProjects\PyMca\.venv\Lib\site-packages\matplotlib\artist.py", line 71, in draw_wrapper
    return draw(artist, renderer)
           ^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\yaroslav\PycharmProjects\PyMca\.venv\Lib\site-packages\matplotlib\text.py", line 909, in draw
    textrenderer.draw_text(gc, x, y, clean_line,
  File "C:\Users\yaroslav\PycharmProjects\PyMca\.venv\Lib\site-packages\matplotlib\backends\backend_agg.py", line 245, in draw_text
    self._draw_text_glyphs_and_boxes(
  File "C:\Users\yaroslav\PycharmProjects\PyMca\.venv\Lib\site-packages\matplotlib\backends\backend_agg.py", line 182, in _draw_text_glyphs_and_boxes
    font._set_transform(
TypeError: _set_transform(): incompatible function arguments. The following argument types are supported:
    1. (self: matplotlib.ft2font.FT2Font, matrix: typing.Annotated[collections.abc.Sequence[typing.Annotated[collections.abc.Sequence[typing.SupportsInt | typing.SupportsIndex], "FixedSize(2)"]], "FixedSize(2)"], delta: typing.Annotated[collections.abc.Sequence[typing.SupportsInt | typing.SupportsIndex], "FixedSize(2)"]) -> None

Invoked with: <matplotlib.ft2font.FT2Font object at 0x0000020E3EA31AF0>, array([[65536,     0],
       [    0, 65536]]), [42538, -54964436860202]

@woutdenolf

Copy link
Copy Markdown
Collaborator

Sorry I cannot reproduce

import sys
import h5py
import numpy as np

from PyQt5.QtWidgets import QApplication
from PyMca5.PyMcaGui.pymca.QStackWidget import QStackWidget
from PyMca5.PyMcaCore import StackBase
from PyMca5.PyMcaCore import DataObject

filename = "test_stack.h5"

with h5py.File(filename, "w") as f:
    f.create_dataset(
        "data",
        shape=(1, 1000, 1000),
        dtype=np.float32
    )

app = QApplication(sys.argv)

w = QStackWidget()
w.show()

stack = DataObject.DataObject()

with h5py.File(filename, "r") as f:
    stack.data = f["data"][()]

# "1D data is first dimension"
stack.info = {
    "McaIndex": 0,
    "SourceType": "HDF5"
}

w.setStack(stack)

sys.exit(app.exec())

The reason I'm insisting to understand what the problem is exactly is the magic abs(vmin) * 0.05. The function already is full of magic numbers like 0.044 and 0.005

@sergey-yaroslavtsev

Copy link
Copy Markdown
Collaborator Author

0.044 is "magic"number - historical for PyMca - the only reason i keep it is because it was working like this before. It probably can be substituted entirely with a new one.

abs(vmin) * 0.05 - is a new magic number - but it is only "half-magic" 😄 the main point of it is to have gaps in % and not in absolute values - this is exactly where new matplotlib fails (see example in PR comment).
We can change 5% to any other reasonable "magic"-number - this will not make a big difference.

part of the problem is that float32 (as example) keeps values in big range but with different sensitivity/resolution, so for values 2*10^8 (again example from actual bug) it could not see difference of 0.044 (initial magic value). That is why Limits should be set in % and not in absolute. Otherwise the behavior is unpredictable.

in particular it lead to overflow of pixel dimension (see the the end of original error) , which crush the positioning of the Marker (see the beginning of the original error - it happens on call of insertMarker).

So to reproduce the problem - (1) bad range + (2) use position.

try to set data like np.logspace(-9, 9, 1100 * 1100, dtype=np.float32)
and then place w.mcaWidget.insertMarker(0.0, 0.0, text="")

if vmin != vmax:
return vmin, vmax
# magnitude problem; necessary for matplotlib >= 3.11
delta = max(0.044, abs(vmin) * 0.05)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now I finally understand what the problem is

>>> numpy.float32(1e10) == (numpy.float32(1e10) + 0.044)
np.True_

This code tries to ensure that xmin != xmax.

Since this depends on the float-precision the delta must be relative not absolute.

We keep the magic 0.044 is a lower bound.

Ok I'm fine with this.

@sergey-yaroslavtsev
sergey-yaroslavtsev merged commit 64ffc97 into master Jul 29, 2026
10 checks passed
@sergey-yaroslavtsev
sergey-yaroslavtsev deleted the fix_roi_mpl branch July 29, 2026 13:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants