为什么matplotlib用"!"替换右括号 在乳胶表达?

use*_*620 7 python latex matplotlib

我正处于这样的情况:我必须为最终用户将python表达式转换为Latex Bitmap(他有足够的信心自己编写python函数但更喜欢在Latex中查看结果).

我正在使用Matplotlib.mathtext来完成这项工作(来自翻译的乳胶原始字符串),并使用以下代码.

import wx
import wx.lib.scrolledpanel as scrolled

import matplotlib as mpl
from matplotlib import cm 
from matplotlib import mathtext

class LatexBitmapFactory():
    """ Latex Expression to Bitmap """
    mpl.rc('image', origin='upper')
    parser = mathtext.MathTextParser("Bitmap")

    mpl.rc('text', usetex=True)
    DefaultProps = mpl.font_manager.FontProperties(family = "sans-serif",\
                                                    style = "normal",\
                                                    weight = "medium",\
                                                    size = 6)
    # size is changed from 6 to 7 
#-------------------------------------------------------------------------------
    def SetBitmap(self, _parent, _line, dpi = 150, prop = DefaultProps):
        bmp = self.mathtext_to_wxbitmap(_line, dpi, prop = prop)
        w,h = bmp.GetWidth(), bmp.GetHeight()
        return wx.StaticBitmap(_parent, -1, bmp, (80, 50), (w, h))
#-------------------------------------------------------------------------------
    def mathtext_to_wxbitmap(self, _s, dpi = 150, prop = DefaultProps):
        ftimage, depth = self.parser.parse(_s, dpi, prop)
        w,h = ftimage.get_width(), ftimage.get_height()
        return wx.BitmapFromBufferRGBA(w, h, ftimage.as_rgba_str())


EXP = r'$\left(\frac{A \cdot \left(vds \cdot rs + \operatorname{Vdp}\left(ri, Rn, Hr, Hd\right) \cdot rh\right) \cdot \left(rSurf + \left(1.0 - rSurf\right) \cdot ft\right) \cdot \left(1.0 - e^{- \left(\left(lr + \frac{\operatorname{Log}\left(2\right)}{tem \cdot 86400.0}\right)\right) \cdot tFr \cdot 3600.0}\right)}{rc \cdot \left(lr + \frac{\operatorname{Log}\left(2\right)}{tem \cdot 86400.0}\right) \cdot tFr \cdot 3600.0} + 1\right)$'

class aFrame(wx.Frame):
    def __init__(self, title="Edition"):
        wx.Frame.__init__(self, None, wx.ID_ANY, title=title, size=(600,400),
                          style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
        self.SetBackgroundColour(wx.Colour(255,255,255))

        sizer = wx.FlexGridSizer(cols=25, vgap=4, hgap=4)
        panel = scrolled.ScrolledPanel(self)
        image_latex = LatexBitmapFactory().SetBitmap(panel, EXP)

        sizer.Add(image_latex, flag=wx.EXPAND|wx.ALL)
        panel.SetSizer(sizer)
        panel.SetAutoLayout(1)
        panel.SetupScrolling()


app = wx.App(redirect=True, filename="latexlog.txt")
frame = aFrame()
frame.Show()
app.MainLoop()
Run Code Online (Sandbox Code Playgroud)

尺寸= 6时,显示以下图片 你找到了

大小= 7,我有这个 完美!

乳胶表达是正确的,第二张图是正确的.我没有任何错误消息,只是右括号替换为"!".

如果我继续写表达式,我仍然有"!" 大小为6.

T_T

如果表达式更简单,则正确显示右括号.

有什么想法解决它吗?

J R*_*ape 4

mathtext.py TL;DR第 727 行以下行有一个错误。它将 size 处的右括号Bigg与索引相关联'\x21',但这是感叹号的索引。带有一些上下文的行如下所示

_size_alternatives = {
    '('          : [('rm', '('), ('ex', '\xa1'), ('ex', '\xb3'),
                    ('ex', '\xb5'), ('ex', '\xc3')],
    ')'          : [('rm', ')'), ('ex', '\xa2'), ('ex', '\xb4'),
                    ('ex', '\xb6'), ('ex', '\x21')],
Run Code Online (Sandbox Code Playgroud)

我不太确定要更改为哪个索引,但我建议您将本地副本更改mathtext.py为如下所示:

_size_alternatives = {
    '('          : [('rm', '('), ('ex', '\xa1'), ('ex', '\xb3'),
                    ('ex', '\xb5'), ('ex', '\x28')],
    ')'          : [('rm', ')'), ('ex', '\xa2'), ('ex', '\xb4'),
                    ('ex', '\xb6'), ('ex', '\x29')]
Run Code Online (Sandbox Code Playgroud)

它产生的括号有点过于圆润,因为它们是基本括号,但它们有效。同样 - 你可以用bigg尺寸替换 -'\xb5'并且'xb6'

在 matplotlib github Issue 5210上报告


我可以使用提供的代码重现这个问题size=6(如果是宽度问题,则将常量设置得更大一些)。我无法通过设置重现“修复” size = 7,但如果我达到size = 8或更高,我可以 - 这表明这可能是一个令人讨厌的边缘情况错误,并且可能依赖于系统......

我做了相当多的调查/诊断(见下文),但似乎有一个错误 - 在matplotlib github 上报告在这里

然而,减少到matplotlib唯一的示例会产生非常好的渲染,如下所示。注意我已经将 matplotlib 设置为默认使用乳胶渲染 - 但您可以显式设置选项以获得相同的结果。

代码

 import matplotlib.pyplot as plt
 import matplotlib as mpl

 mpl.rc('image', origin='upper')

 mpl.rc('text', usetex=True)
 DefaultProps = mpl.font_manager.FontProperties(family = "sans-serif",\
                                                 style = "normal",\
                                                 weight = "medium",\
                                                 size = 6)

 EXP = r'$\left(\frac{A \cdot \left(vds \cdot rs + \operatorname{Vdp}\left(ri, Rn, Hr, Hd\right) \cdot rh\right) \cdot \left(rSurf + \left(1.0 - rSurf\right) \cdot ft\right) \cdot \left(1.0 - e^{- \left(\left(lr + \frac{\operatorname{Log}\left(2\right)}{tem \cdot 86400.0}\right)\right) \cdot tFr \cdot 3600.0}\right)}{rc \cdot \left(lr + \frac{\operatorname{Log}\left(2\right)}{tem \cdot 86400.0}\right) \cdot tFr \cdot 3600.0} + 10589 \right)$'

 plt.title(EXP, fontsize=6)
 plt.gca().set_axis_off() # Get rid of the plotting axis for clarity

 plt.show()
Run Code Online (Sandbox Code Playgroud)

输出

为了清晰起见,输出窗口被裁剪并缩放了一点,但您可以看到括号已渲染正常

公式呈现

这表明问题要么是 matplotlib 渲染引擎的使用方式、位图的输出,要么是与wxPython

通过实验,我注意到,如果将 dpi 增加到 300,代码在 处可以正常工作size = 6,但在 处又开始失败size = 3。这意味着问题在于其中一个库认为它无法以一定数量的像素渲染元素。

根本原因

诊断是哪个位在做这件事很困难(IMO)

首先,我添加了

    self.parser.to_png('D:\\math_out.png', _s, color=u'black', dpi=150)
Run Code Online (Sandbox Code Playgroud)

作为 的第一行mathtext_to_wxbitmap(self, _s, dpi = 150, prop = DefaultProps)。这给出了一个很好的输出png,让我认为这可能不是 matplotlib 解析器的错误... 根据@Baptiste 的有用答案进行编辑,我对此进行了更多测试。实际上 - 如果我明确地传递到这个调用,我可以复制感叹号的外观。另外,传递到此调用中的内容将被忽略 - 因此在我的测试中,我实际上正在处理 300 dpi 的图像。因此,重点应该放在dpi 问题上。fontsizedpiMathTextParser


进一步的调查

更多的调查 - 我猴子修补了我的 matplotlib 安装 -print(result)在调用之后直接放入parseString() a。使用运行良好并打印出文本表示的工作表达式。在有问题的场景中,我看到:

Traceback (most recent call last):
  File "D:\baptiste_test.py", line 9, in <module>
    parser.to_png(filename, s, fontsize=size)
  File "C:\Python27\lib\site-packages\matplotlib\mathtext.py", line 3101, in to_
png
    rgba, depth = self.to_rgba(texstr, color=color, dpi=dpi, fontsize=fontsize)
  File "C:\Python27\lib\site-packages\matplotlib\mathtext.py", line 3066, in to_
rgba
    x, depth = self.to_mask(texstr, dpi=dpi, fontsize=fontsize)
  File "C:\Python27\lib\site-packages\matplotlib\mathtext.py", line 3039, in to_
mask
    ftimage, depth = self.parse(texstr, dpi=dpi, prop=prop)
  File "C:\Python27\lib\site-packages\matplotlib\mathtext.py", line 3012, in par
se
    box = self._parser.parse(s, font_output, fontsize, dpi)
  File "C:\Python27\lib\site-packages\matplotlib\mathtext.py", line 2339, in par
se
    print(result[0])
  File "C:\Python27\lib\site-packages\matplotlib\mathtext.py", line 1403, in __r
epr__
    ' '.join([repr(x) for x in self.children]))
  File "C:\Python27\lib\site-packages\matplotlib\mathtext.py", line 1403, in __r
epr__
    ' '.join([repr(x) for x in self.children]))
  File "C:\Python27\lib\site-packages\matplotlib\mathtext.py", line 1403, in __r
epr__
    ' '.join([repr(x) for x in self.children]))
  File "C:\Python27\lib\site-packages\matplotlib\mathtext.py", line 1403, in __r
epr__
    ' '.join([repr(x) for x in self.children]))
UnicodeEncodeError: 'ascii' codec can't encode character u'\xb3' in position 1:
ordinal not in range(128)
Run Code Online (Sandbox Code Playgroud)

这表明该错误可能源于错误翻译的字符 - 也许字体中缺少代码点?

我还指出,您可以在没有NBaptiste 的最小示例中的字母的情况下进行复制。


进一步进一步调查

在 BakomaFonts 类中的 _get_glyph 中粘贴一些调试打印。在失败的情况下,当您期望代码查找 u'\xc4' 并返回 parenrightBigg (即相应的左括号正在查找 u' 的位置)时,代码似乎正在查找感叹号(u'!') \xc3' 并返回 parenleftBigg)。在仅使用 parenrightbigg 的情况下,没有问题(在给定示例中,这种情况发生在 fontsize=5 的情况下,但没有其他情况)。我放入 _get_glyph 的调试行是:

print('Getting glyph for symbol',repr(unicode(sym)))
print('Returning',cached_font, num, symbol_name, fontsize, slanted)
Run Code Online (Sandbox Code Playgroud)

我猜它是否需要bigg或Bigg版本是基于fontsize和dpi的组合

好的 - 我认为问题出在这一行:https://github.com/matplotlib/matplotlib/blob/master/lib/matplotlib/mathtext.py#L727

内容如下(有一点上下文):

_size_alternatives = {
    '('          : [('rm', '('), ('ex', '\xa1'), ('ex', '\xb3'),
                    ('ex', '\xb5'), ('ex', '\xc3')],
    ')'          : [('rm', ')'), ('ex', '\xa2'), ('ex', '\xb4'),
                    ('ex', '\xb6'), ('ex', '\x21')],   ## <---- Incorrect line.
Run Code Online (Sandbox Code Playgroud)

'\x21'是错误的 - 但我无法弄清楚正确的是什么是'\x29'接近的,但“太弯曲”。我猜想'\xc4'遵循该模式,但那是一个向下箭头。希望核心开发人员之一能够轻松地根据它呈现的字形查找这个数字(十进制 195)并进行更正。