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

时间:2015-09-28 14:58:41

标签: python matplotlib latex

我必须将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()

尺寸= 6,显示以下图片 Did you find the "!" ?

尺寸= 7,我有这个 perfect !

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

如果我继续写表达式,我仍然有#34;!"大小为6。

T_T

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

有什么想法解决它?

3 个答案:

答案 0 :(得分:4)

TL; DR mathtext.py Line 727的以下行中有一个错误。它将大小为Bigg的右括号与索引'\x21'相关联,但这是感叹号的索引。带有一些上下文的行读取

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

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

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

这种产生的parantheses有点过于圆润,因为它们是基本的parantheses,但它们起作用。同样 - 您可以替换bigg尺寸的'\xb5''xb6'

报道matplotlib github Issue 5210

我可以使用提供的代码使用size=6重现此问题 (如果它是宽度问题,则使常量变大)。我不能 重现"修复"通过设置size = 7,但如果我上升到size = 8或更高,我就可以 - 这表明这可能是一个令人讨厌的边缘案例错误,并且可能依赖于系统......

我做了很多调查/诊断(见下文),但似乎有一个错误 - 在matplotlib github here上报告。

然而,仅减少到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()

输出

为了清晰起见,输出窗口被裁剪并缩小了一点,但是你可以看到 支架呈现正常

Formula rendered

这表明问题无论是matplotlib的方式 正在使用渲染引擎,输出到位图或交互 与wxPython

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

根本原因

诊断哪个位正在执行此操作 hard (IMO)

首先,我添加了

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

作为mathtext_to_wxbitmap(self, _s, dpi = 150, prop = DefaultProps)的第一行。这给出了一个很好的输出png,让我觉得matplotlib解析器可能没有错误... EDIT 基于{{3}我测试了一下这个。实际上 - 如果我明确地将fontsize传入此调用,我可以复制感叹号的外观。此外,传入此调用的dpi将被忽略 - 因此在我的测试中,我实际上处理的是300 dpi图像。因此,重点应放在MathTextParser上,这可能是一个dpi问题。

进一步调查

进一步调查 - 我修改了我的matplotlib安装 - 在调用print(result) @Baptiste's useful answer后直接输入parseString()。使用正常的工作表达式并打印出文本表示。有了错误的场景,我看到了:

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)

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

我还注意到,在Baptiste的最小例子中,你可以在没有N字母的情况下复制。

进一步调查

在BakomaFonts类中的_get_glyph中粘贴一些调试打印。在失败的情况下,代码似乎正在寻找感叹号(当你想要它正在寻找你的&#39; \ xc4&#39;并返回parenrightBigg(即相应的左括号正在查找u&#39; \ xc3&#39;并返回parenleftBigg)。在它只使用parenrightbigg的情况下,没有问题(在给定的示例中,fontsize = 5会发生这种情况,但没有其他问题)。我在_get_glyph中输入的调试行是:

print('Getting glyph for symbol',repr(unicode(sym)))
print('Returning',cached_font, num, symbol_name, fontsize, slanted)

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

好的 - 我认为问题出在这一行:here

这读取(带有一点上下文):

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

'\x21'是错误的 - 但我无法弄清楚正确的'\x29'是否接近,但是&#34;太弯曲&#34;。我猜测'\xc4'跟随模式,但这是一个向下箭头。希望其中一个核心开发人员可以轻松地查找这个数字(十进制195)与它渲染和纠正的字形。

答案 1 :(得分:3)

这是我能够编写的最短代码来获得这种意外行为:

import matplotlib.mathtext as mt

s = r'$\left(\frac{\frac{\frac{M}{I}}{N}}' \
    r'{\frac{\frac{B}{U}}{G}}\right)$'
parser = mt.MathTextParser("Bitmap")
for size in range(1, 30):
    filename = "figure{0}.png".format(size)
    parser.to_png(filename, s, fontsize=size)

输出看起来像这样(选择6-12):

size 6

size 7

size 8

size 9

size 10

size 11

size 12

这篇文章更多的是分享重现错误的最小代码而不是问题的答案

答案 2 :(得分:1)

我还有“!”大小为9时dpi ='300',但大约9,除了3,它没关系。

我使用dpi和size以及'()'和'[]'创建了一个新的Frame。只需重复使用'LatexBitmapFactory'即可。

我在Windows XP上使用python 2.7。我在Windows 7上遇到同样的错误。

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

        self.sizer = wx.BoxSizer(wx.VERTICAL)
        self.panel = scrolled.ScrolledPanel(self)


        init_dpi = 300
        min_dpi, max_dpi = 100, 500
        self.dpi_slider = wx.Slider(self.panel, wx.ID_ANY,
                                    init_dpi, min_dpi, max_dpi, (30, 60), (250, -1), 
                                    wx.SL_HORIZONTAL | wx.SL_AUTOTICKS | wx.SL_LABELS)
        init_size = 9
        min_size, max_size = 3, 40
        self.size_slider = wx.Slider(
            self.panel, wx.ID_ANY, init_size, min_size, max_size, (30, 60), (250, -1), 
            wx.SL_HORIZONTAL | wx.SL_AUTOTICKS | wx.SL_LABELS)

        self.log = wx.StaticText(self.panel, wx.ID_ANY, "DPI:%d SIZE:%d" %(init_dpi, init_size))
        image_latex1 = self.create_lateximage(init_dpi, init_size)
        image_latex2 = self.create_lateximage(init_dpi, init_size, replace_parenthesis=True)
        self.sizer.Add(image_latex1, 1, flag=wx.ALIGN_CENTER|wx.EXPAND|wx.ALL)
        self.sizer.Add(image_latex2, 1, flag=wx.ALIGN_CENTER|wx.EXPAND|wx.ALL)
        self.sizer.Add(self.dpi_slider, 0, wx.ALIGN_CENTER)
        self.sizer.Add(self.size_slider, 0, wx.ALIGN_CENTER)
        self.sizer.Add(self.log, 0, wx.ALIGN_CENTER)

        self.dpi_slider.Bind(wx.EVT_SCROLL_CHANGED, self.OnSliderChanged)
        self.size_slider.Bind(wx.EVT_SCROLL_CHANGED, self.OnSliderChanged)
        self.panel.SetSizer(self.sizer)
        self.panel.SetAutoLayout(1)
        self.panel.SetupScrolling()

    def create_lateximage(self, dpi, size, replace_parenthesis=False):
        updatedProps = mpl.font_manager.FontProperties(family = "sans-serif",\
                                                       style = "normal",\
                                                       weight = "medium",\
                                                       size = size)
        if replace_parenthesis:
            tp_exp = EXP.replace("right)", "right]")
            tp_exp = tp_exp.replace("left(", "left[")
            return LatexBitmapFactory().SetBitmap(self.panel, tp_exp, dpi=dpi, prop=updatedProps)
        return LatexBitmapFactory().SetBitmap(self.panel, EXP, dpi=dpi, prop=updatedProps)

    def OnSliderChanged(self, evt):
        dpi  = int(self.dpi_slider.GetValue())
        size = int(self.size_slider.GetValue())
        self.log.SetLabel("DPI:%d SIZE:%d" %(dpi, size))

        self.Freeze()
        new_image_latex1 = self.create_lateximage(dpi, size)
        new_image_latex2 = self.create_lateximage(dpi, size, True)
        prev_image_latex1 = self.sizer.Remove(0)
        prev_image_latex2 = self.sizer.Remove(0)
        del prev_image_latex1
        del prev_image_latex2

        self.sizer.Insert(0, new_image_latex2, 1, flag=wx.ALIGN_CENTER|wx.EXPAND|wx.ALL)
        self.sizer.Insert(0, new_image_latex1, 1, flag=wx.ALIGN_CENTER|wx.EXPAND|wx.ALL)
        self.sizer.Layout()
        assert len(self.sizer.GetChildren()) == 5,\
        " must have len 5, now %d"%len(self.sizer.GetChildren())
        self.panel.SetupScrolling()
        self.panel.SetScrollRate(100,100)
        self.Thaw()

app = wx.App(redirect=True, filename="latexlog.txt")
frame = aFrame()
frame.Show()
app.MainLoop()

enter image description here