Skip to content
This repository

Use dvipng to format sympy.Matrix #1861

Merged
merged 7 commits into from almost 2 years ago

3 participants

Takafumi Arakaki Min RK Fernando Perez
Takafumi Arakaki
tkf commented June 05, 2012

Two changes are introduced:

  1. new dvipng backend is added to latex_to_png
  2. PNG formatter for sympy.Matrix uses the dvipng backend
Min RK
Owner
minrk commented June 05, 2012

This looks pretty nice. Though you mustn't use the dvipng backend unconditionally, as it may not be present.

Takafumi Arakaki
tkf commented June 05, 2012

Quite right. Is returning None delegate the work to other formatter?

Min RK
Owner
minrk commented June 05, 2012

returning None will delegate to other mimetypes, but if you want to fallback on the mpl backend, then you have to do that yourself.

Takafumi Arakaki latex_to_png returns None when the backend cannot be used
When matplotlib/dvipng is not present, other formatter will be used.
170a5ca
Takafumi Arakaki
tkf commented June 05, 2012

I think latex output of Matrix can't rendered by matplotlib. So I guess returning None when dvipng is not present is OK here.

Min RK
Owner
minrk commented June 05, 2012

That makes sense, can you add a comment to that effect? The function itself looks generic, so it's unclear why it would never be used with mpl.

And please don't do pep8 cleanup on code other than your own in the future.

Takafumi Arakaki
tkf commented June 05, 2012

Added comment/docstrings.

Sorry about PEP8. I just removed it. I remember the discussion (I think it was about copyright year stuff) saying this kind of cleanup should be done when the code is touched. Now I guess it was only for core developers.

Min RK
Owner
minrk commented June 05, 2012

Thanks!

IPython doesn't enforce pep8, so not all changes it would make should be made just for the sake of it. The copyright discussion specifically referred to the copyright notice at the top of the file, which everyone is welcome to fix as they edit fies. Even (or especially) core devs don't do file-wide pep8 at any time.

Takafumi Arakaki
tkf commented June 05, 2012

I had impression that IPython devs try to follow pep8 as much as possible because devs try to apply pep8 even to JS. But anyway, lesson learned! Thanks for the explanation.

Min RK
Owner
minrk commented June 05, 2012

We have been more strict in the JS because it's unfamiliar territory for all of us, so it helps us all stay on the same page while learning a new language.

We are much more comfortable in Python, and thus more comfortable letting authors make personal style choices in Python code. pep8 is a great guideline, and "when in doubt: pep8", but that doesn't mean an 82 character line needs to be wrapped.

Min RK
Owner
minrk commented June 06, 2012

I don't have dvipng on my machine, so I'm just checking that it does the right thing. It appears to do the right thing at the Python level, but latex barfs a bunch of crap to the terminal. Can you squash that by capturing stdout/err on your subprocesses?

You might also want to use IPython.utils.process.find_cmd on dvipng before even trying, because then you can be fairly sure it's not worth it.

Takafumi Arakaki
tkf commented June 06, 2012

Done.

Fernando Perez
Owner
fperez commented June 10, 2012

Sorry if it's a bit of a dumb question: why is this nicer than using mathjax? In general, mathjax renders more cleanly in the browser, zooms correctly, etc. Dvipng solutions tend to suffer from nasty resolution-dependent behavior (which are likely to get worse with systems like the upcoming macs with retina displays).

I'm just trying to understand and make sure this is really always an improvement (since it's activated unconditionally).

Min RK
Owner
minrk commented June 10, 2012

This is for the QtConsole, and not relevant to the notebook. The notebook will still display the math with mathjax, ignoring the png this produces.

Takafumi Arakaki
tkf commented June 10, 2012

I think it is an improvement because mathjax does not work in Qt console. My intention to add dvipng backend to latex_to_png function is to cover latex math expressions which are not covered by matplotlib (such as the one by sympy.Matrix). Besides, if you put latex mime-type notebook won't show png (Am I understanding it correctly?). So I think it is improvement (or at least zero improvement) for all frontends.

Takafumi Arakaki
tkf commented June 10, 2012

Ah, notebook server will send unused data to notebook client, so I guess it will slow down the response a bit.

Min RK
Owner
minrk commented June 10, 2012

But this is already true of all the other sympy latex, so no big deal to add Matrices to the list.

The long-term answer is to render latex client-side in the QtConsole, which I think is preferable for various reasons.

Fernando Perez
Owner
fperez commented June 10, 2012

Ah, got it. Then it's good to go, I just wanted to clarify that. I'll explain in the merge commit.

@tkf, thanks for the contribution! Merging now, thanks Min for the careful review.

Fernando Perez fperez merged commit 7cd55e5 into from June 10, 2012
Fernando Perez fperez closed this June 10, 2012
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
22  IPython/extensions/sympyprinting.py
@@ -11,7 +11,7 @@
11 11
 
12 12
 """
13 13
 #-----------------------------------------------------------------------------
14  
-#  Copyright (C) 2008-2011  The IPython Development Team
  14
+#  Copyright (C) 2008  The IPython Development Team
15 15
 #
16 16
 #  Distributed under the terms of the BSD License.  The full license is in
17 17
 #  the file COPYING, distributed as part of this software.
@@ -45,7 +45,9 @@ def print_basic_unicode(o, p, cycle):
45 45
 
46 46
 
47 47
 def print_png(o):
48  
-    """A function to display sympy expression using LaTex -> PNG."""
  48
+    """
  49
+    A function to display sympy expression using inline style LaTeX in PNG.
  50
+    """
49 51
     s = latex(o, mode='inline')
50 52
     # mathtext does not understand certain latex flags, so we try to replace
51 53
     # them with suitable subs.
@@ -54,6 +56,19 @@ def print_png(o):
54 56
     png = latex_to_png(s)
55 57
     return png
56 58
 
  59
+
  60
+def print_display_png(o):
  61
+    """
  62
+    A function to display sympy expression using display style LaTeX in PNG.
  63
+    """
  64
+    s = latex(o, mode='plain')
  65
+    s = s.strip('$')
  66
+    # As matplotlib does not support display style, dvipng backend is
  67
+    # used here.
  68
+    png = latex_to_png('$$%s$$' % s, backend='dvipng')
  69
+    return png
  70
+
  71
+
57 72
 def can_print_latex(o):
58 73
     """
59 74
     Return True if type o can be printed with LaTeX.
@@ -115,6 +130,9 @@ def load_ipython_extension(ip):
115 130
         png_formatter.for_type_by_name(
116 131
             'sympy.core.basic', 'Basic', print_png
117 132
         )
  133
+        png_formatter.for_type_by_name(
  134
+            'sympy.matrices.matrices', 'Matrix', print_display_png
  135
+        )
118 136
         for cls in [dict, int, long, float] + printable_containers:
119 137
             png_formatter.for_type(cls, print_png)
120 138
 
87  IPython/lib/latextools.py
@@ -6,7 +6,7 @@
6 6
 * Brian Granger
7 7
 """
8 8
 #-----------------------------------------------------------------------------
9  
-# Copyright (C) 2010-2011, IPython Development Team.
  9
+# Copyright (C) 2010 IPython Development Team.
10 10
 #
11 11
 # Distributed under the terms of the Modified BSD License.
12 12
 #
@@ -19,14 +19,20 @@
19 19
 
20 20
 from StringIO import StringIO
21 21
 from base64 import encodestring
  22
+import os
  23
+import tempfile
  24
+import shutil
  25
+import subprocess
  26
+
  27
+from IPython.utils.process import find_cmd, FindCmdError
22 28
 
23 29
 #-----------------------------------------------------------------------------
24 30
 # Tools
25 31
 #-----------------------------------------------------------------------------
26 32
 
27 33
 
28  
-def latex_to_png(s, encode=False):
29  
-    """Render a LaTeX string to PNG using matplotlib.mathtext.
  34
+def latex_to_png(s, encode=False, backend='mpl'):
  35
+    """Render a LaTeX string to PNG.
30 36
 
31 37
     Parameters
32 38
     ----------
@@ -34,18 +40,82 @@ def latex_to_png(s, encode=False):
34 40
         The raw string containing valid inline LaTeX.
35 41
     encode : bool, optional
36 42
         Should the PNG data bebase64 encoded to make it JSON'able.
  43
+    backend : {mpl, dvipng}
  44
+        Backend for producing PNG data.
  45
+
  46
+    None is returned when the backend cannot be used.
  47
+
37 48
     """
38  
-    from matplotlib import mathtext
  49
+    if backend == 'mpl':
  50
+        f = latex_to_png_mpl
  51
+    elif backend == 'dvipng':
  52
+        f = latex_to_png_dvipng
  53
+    else:
  54
+        raise ValueError('No such backend {0}'.format(backend))
  55
+    bin_data = f(s)
  56
+    if encode and bin_data:
  57
+        bin_data = encodestring(bin_data)
  58
+    return bin_data
  59
+
  60
+
  61
+def latex_to_png_mpl(s):
  62
+    try:
  63
+        from matplotlib import mathtext
  64
+    except ImportError:
  65
+        return None
39 66
     
40 67
     mt = mathtext.MathTextParser('bitmap')
41 68
     f = StringIO()
42 69
     mt.to_png(f, s, fontsize=12)
43  
-    bin_data = f.getvalue()
44  
-    if encode:
45  
-        bin_data = encodestring(bin_data)
  70
+    return f.getvalue()
  71
+
  72
+
  73
+def latex_to_png_dvipng(s):
  74
+    try:
  75
+        find_cmd('latex')
  76
+        find_cmd('dvipng')
  77
+    except FindCmdError:
  78
+        return None
  79
+    try:
  80
+        workdir = tempfile.mkdtemp()
  81
+        tmpfile = os.path.join(workdir, "tmp.tex")
  82
+        dvifile = os.path.join(workdir, "tmp.dvi")
  83
+        outfile = os.path.join(workdir, "tmp.png")
  84
+
  85
+        with open(tmpfile, "w") as f:
  86
+            f.write(_latex_header)
  87
+            f.write(s)
  88
+            f.write(_latex_footer)
  89
+
  90
+        subprocess.check_call(
  91
+            ["latex", "-halt-on-errror", tmpfile], cwd=workdir,
  92
+            stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  93
+
  94
+        subprocess.check_call(
  95
+            ["dvipng", "-T", "tight", "-x", "1500", "-z", "9",
  96
+             "-bg", "transparent", "-o", outfile, dvifile], cwd=workdir,
  97
+            stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  98
+
  99
+        with open(outfile) as f:
  100
+            bin_data = f.read()
  101
+    finally:
  102
+        shutil.rmtree(workdir)
46 103
     return bin_data
47 104
 
48 105
 
  106
+_latex_header = r'''
  107
+\documentclass{article}
  108
+\usepackage{amsmath}
  109
+\usepackage{amsthm}
  110
+\usepackage{amssymb}
  111
+\usepackage{bm}
  112
+\pagestyle{empty}
  113
+\begin{document}
  114
+'''
  115
+
  116
+_latex_footer = r'\end{document}'
  117
+
  118
+
49 119
 _data_uri_template_png = """<img src="data:image/png;base64,%s" alt=%s />"""
50 120
 
51 121
 def latex_to_html(s, alt='image'):
@@ -59,7 +129,8 @@ def latex_to_html(s, alt='image'):
59 129
         The alt text to use for the HTML.
60 130
     """
61 131
     base64_data = latex_to_png(s, encode=True)
62  
-    return _data_uri_template_png  % (base64_data, alt)
  132
+    if base64_data:
  133
+        return _data_uri_template_png  % (base64_data, alt)
63 134
 
64 135
 
65 136
 # From matplotlib, thanks to mdboom. Once this is in matplotlib releases, we
Commit_comment_tip

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.