-
-
Notifications
You must be signed in to change notification settings - Fork 56
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Image2latex #751
Image2latex #751
Changes from 10 commits
9be6dc5
670dc14
50420fd
e079ec8
52f88c7
20e0497
0f6c8bd
e042b5c
fd4c54c
e217cfb
4f834c9
f2bfb3c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,25 @@ | ||
# -*- coding: utf-8 -*- | ||
|
||
import base64 | ||
import tempfile | ||
from copy import deepcopy | ||
from io import BytesIO | ||
from typing import Tuple | ||
|
||
from mathics.builtin.box.expression import BoxExpression | ||
from mathics.eval.image import pixels_as_ubyte | ||
|
||
try: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No (And in the far future when image processing is moved out of Mathics-core PIL will still be required in that package. Let's not complicate.) |
||
import warnings | ||
|
||
import PIL | ||
import PIL.Image | ||
import PIL.ImageEnhance | ||
import PIL.ImageFilter | ||
import PIL.ImageOps | ||
|
||
except ImportError: | ||
pass | ||
|
||
|
||
class ImageBox(BoxExpression): | ||
|
@@ -13,18 +32,80 @@ class ImageBox(BoxExpression): | |
an Image object. | ||
""" | ||
|
||
def boxes_to_b64text(self, elements=None, **options): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Type annotation and a docstring would be nice here. |
||
contents, size = self.boxes_to_png(elements, **options) | ||
encoded = base64.b64encode(contents) | ||
encoded = b"data:image/png;base64," + encoded | ||
return encoded, size | ||
|
||
def boxes_to_png(self, elements=None, **options) -> Tuple: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The return type is more than just a generic tuple it is something like Please add and fill in "type of contents". |
||
""" | ||
returns a tuple with the set of bytes with a png representation of the image | ||
and the scaled size. | ||
""" | ||
image = self.elements[0] if elements is None else elements[0] | ||
|
||
pixels = pixels_as_ubyte(image.color_convert("RGB", True).pixels) | ||
shape = pixels.shape | ||
|
||
width = shape[1] | ||
height = shape[0] | ||
scaled_width = width | ||
scaled_height = height | ||
|
||
# If the image was created from PIL, use that rather than | ||
# reconstruct it from pixels which we can get wrong. | ||
# In particular getting color-mapping info right can be | ||
# tricky. | ||
if hasattr(image, "pillow"): | ||
pillow = deepcopy(image.pillow) | ||
else: | ||
pixels_format = "RGBA" if len(shape) >= 3 and shape[2] == 4 else "RGB" | ||
pillow = PIL.Image.fromarray(pixels, pixels_format) | ||
|
||
# if the image is very small, scale it up using nearest neighbour. | ||
min_size = 128 | ||
if width < min_size and height < min_size: | ||
scale = min_size / max(width, height) | ||
scaled_width = int(scale * width) | ||
scaled_height = int(scale * height) | ||
pillow = pillow.resize( | ||
(scaled_height, scaled_width), resample=PIL.Image.NEAREST | ||
) | ||
|
||
with warnings.catch_warnings(): | ||
warnings.simplefilter("ignore") | ||
|
||
stream = BytesIO() | ||
pillow.save(stream, format="png") | ||
stream.seek(0) | ||
contents = stream.read() | ||
stream.close() | ||
|
||
return (contents, (scaled_width, scaled_height)) | ||
|
||
def boxes_to_text(self, elements=None, **options): | ||
return "-Image-" | ||
|
||
def boxes_to_mathml(self, elements=None, **options): | ||
if elements is None: | ||
elements = self._elements | ||
encoded, size = self.boxes_to_b64text(elements, **options) | ||
# see https://tools.ietf.org/html/rfc2397 | ||
return '<mglyph src="%s" width="%dpx" height="%dpx" />' % ( | ||
elements[0].get_string_value(), | ||
elements[1].get_int_value(), | ||
elements[2].get_int_value(), | ||
) | ||
return '<mglyph src="%s" width="%dpx" height="%dpx" />' % (encoded, *size) | ||
rocky marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
def boxes_to_tex(self, elements=None, **options): | ||
return "-Image-" | ||
data, size = self.boxes_to_png(elements, **options) | ||
res = 100 # pixels/cm | ||
width_str, height_str = (str(n / res).strip() for n in size) | ||
head = rf"\includegraphics[width={width_str}cm,height={height_str}cm]" | ||
|
||
# This produces a random name, where the png file is going to be stored. | ||
# LaTeX does not have a native way to store an figure embeded in | ||
# the source. | ||
fp = tempfile.NamedTemporaryFile(delete=True, suffix=".png") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider using pillow routines for writing PNG. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The conversion was done when Image is converted in ImageBox. imageBox stores a B64 encoded version of the PNG file. Here we just decode it and store it in a file. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If ImageBox can keep the pillow structure, that may be a win. A more general problem we have is that in digesting things for M-Expressions we lose the efficient and sometimes more flexible properties of whatever the object was before. And we spend a lot of time in conversion. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is done now. |
||
path = fp.name | ||
fp.close() | ||
|
||
with open(path, "wb") as imgfile: | ||
imgfile.write(data) | ||
|
||
return head + "{" + format(path) + "}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Without a docstring at the top, this doesn't appear in any printed documentation or in Django docs. If that is intended, then the homegrown tagging such as
ImageBox
(on line 26) is not used.We should decide way we want to go and either add a docstring at the top or remove the homegrown tagging.