Skip to content

Commit

Permalink
Merge pull request #331 from enthought/fix-savefig-size-black-image
Browse files Browse the repository at this point in the history
Fix savefig size and black image
  • Loading branch information
stefanoborini committed Apr 21, 2016
2 parents ce21cfe + f7c6e46 commit 3bccaaf
Show file tree
Hide file tree
Showing 4 changed files with 200 additions and 36 deletions.
132 changes: 127 additions & 5 deletions integrationtests/mayavi/test_mlab_savefig.py
Expand Up @@ -4,6 +4,7 @@
import tempfile

import numpy
from PIL import Image

from mayavi import mlab
from mayavi.core.engine import Engine
Expand All @@ -13,11 +14,11 @@
from common import TestCase


def create_quiver3d():
def create_quiver3d(figure):
x, y, z = numpy.mgrid[1:10, 1:10, 1:10]
u, v, w = numpy.mgrid[1:10, 1:10, 1:10]
s = numpy.sqrt(u**2 + v**2)
mlab.quiver3d(x, y, z, u, v, w, scalars=s)
mlab.quiver3d(x, y, z, u, v, w, scalars=s, figure=figure)


# Note: the figure(window) size is delibrately set to be smaller than
Expand Down Expand Up @@ -58,25 +59,146 @@ def cleanup_engine(self, engine):
engine.close_scene(scene)
engine.stop()

def test_savefig(self):
"""Test if savefig works with auto size, mag and a normal Engine"""
self.setup_engine_and_figure(Engine())

# Set up the scene
create_quiver3d(figure=self.figure)

# save the figure (magnification is default "auto")
savefig(self.filename, figure=self.figure)

# check
self.check_image()

def test_savefig_with_size(self):
"""Test if savefig works with given size and a normal Engine"""
self.setup_engine_and_figure(Engine())

# Set up the scene
create_quiver3d(figure=self.figure)

# save the figure (magnification is default "auto")
savefig(self.filename, size=(131, 217), figure=self.figure)

# check
self.check_image((217, 131))

def test_savefig_with_magnification(self):
"""Test savefig with given magnification and a normal Engine"""
self.setup_engine_and_figure(Engine())

# Set up the scene
create_quiver3d(figure=self.figure)

# save the figure with a magnification
savefig(self.filename, magnification=2, figure=self.figure)

# check
self.check_image()

@unittest.skipIf(os.environ.get("TRAVIS", False),
("Offscreen rendering is not tested on Travis "
"due to lack of GLX support"))
def test_many_savefig_offscreen(self):
def test_savefig_with_size_offscreen(self):
"""Test if savefig works with given size on an OffScreenEngine"""
self.setup_engine_and_figure(OffScreenEngine())

# Set up the scene
create_quiver3d(figure=self.figure)

# save the figure
savefig(self.filename, size=(131, 217), figure=self.figure)

# check
self.check_image((217, 131))

def test_savefig_with_size_and_magnification(self):
"""Test if savefig works with given size and magnification"""
self.setup_engine_and_figure(Engine())

# Set up the scene
create_quiver3d(figure=self.figure)

# save the figure
savefig(self.filename, size=(131, 217), magnification=2,
figure=self.figure)

# check if the image size is twice as big
self.check_image((434, 262))

@unittest.skipIf(os.environ.get("TRAVIS", False),
("Offscreen rendering is not tested on Travis "
"due to lack of GLX support"))
def test_savefig_with_size_and_magnification_offscreen(self):
"""Test savefig with off_screen_rendering and OffScreenEngine"""
self.setup_engine_and_figure(OffScreenEngine())

# Use off-screen rendering
self.figure.scene.off_screen_rendering = True

# Set up the scene
create_quiver3d(figure=self.figure)

# save the figure
savefig(self.filename, size=(131, 217), magnification=2,
figure=self.figure)

# check if the image size is twice as big
self.check_image((434, 262))

@unittest.skipIf(os.environ.get("TRAVIS", False),
("Offscreen rendering is not tested on Travis "
"due to lack of GLX support"))
def test_savefig_with_size_and_magnification_offscreen_with_engine(self):
"""Test if savefig works with off_screen_rendering and Engine"""
self.setup_engine_and_figure(Engine())

# Use off-screen rendering
self.figure.scene.off_screen_rendering = True

# Set up the scene
create_quiver3d(figure=self.figure)

# save the figure
savefig(self.filename, size=(131, 217), magnification=2,
figure=self.figure)

# check if the image size is twice as big
self.check_image((434, 262))

@unittest.skipIf(os.environ.get("TRAVIS", False),
("Offscreen rendering is not tested on Travis "
"due to lack of GLX support"))
def test_many_savefig_offscreen(self):
"""Test saving many figures offscreen"""
engine = Engine()
for _ in xrange(5):
for _ in range(5):
self.setup_engine_and_figure(engine)

# Use off-screen rendering
self.figure.scene.off_screen_rendering = True

# Set up the scene
create_quiver3d()
create_quiver3d(figure=self.figure)

# save the figure
savefig(self.filename, size=(131, 217),
figure=self.figure)

def check_image(self, size=None):
image = numpy.array(Image.open(self.filename))[:, :, :3]

# check the size is correct
if size:
self.assertEqual(image.shape[:2], size)

# check if the image has black spots
if (numpy.sum(image == [0, 0, 0], axis=2) == 3).any():
message = "The image has black spots"
self.fail(message)


class TestMlabSavefig(TestCase):

Expand Down
21 changes: 6 additions & 15 deletions mayavi/tools/figure.py
Expand Up @@ -217,9 +217,6 @@ def savefig(filename, size=None, figure=None, magnification='auto',
**Notes**
If the size specified is larger than the window size, and no
magnification parameter is passed, the magnification of the scene
is changed so that the image created has the requested size.
Please note that if you are trying to save images with sizes
larger than the window size, there will be additional computation
cost.
Expand All @@ -229,23 +226,17 @@ def savefig(filename, size=None, figure=None, magnification='auto',
"""
if figure is None:
figure = gcf()

current_mag = figure.scene.magnification
try:
if size is not None:
current_x, current_y = tuple(figure.scene.get_size())
target_x, target_y = size
if magnification is 'auto':
magnification = max(target_x // current_x,
target_y // current_y) + 1
target_x = int(target_x / magnification)
target_y = int(target_y / magnification)
size = target_x, target_y
elif magnification is 'auto':
if magnification is "auto":
magnification = 1

figure.scene.magnification = int(magnification)

figure.scene.save(filename,
size=size,
**kwargs)
size=size,
**kwargs)
finally:
figure.scene.magnification = int(current_mag)

Expand Down
62 changes: 54 additions & 8 deletions tvtk/pyface/tvtk_scene.py
Expand Up @@ -13,6 +13,7 @@

from __future__ import print_function

import sys
import os.path

from apptools.persistence import state_pickler
Expand Down Expand Up @@ -403,10 +404,55 @@ def save(self, file_name, size=None, **kw_args):
)
meth = getattr(self, 'save_' + meth_map[ext])
if size is not None:
orig_size = self.get_size()
self.set_size(size)
# We create a RenderWindow of the requested size
# instead of resizing the existing one

# Original render window and renderer
orig_renwin = self.render_window
renderer = self.renderer

# temporarily remove the render from the render window
orig_renwin.remove_renderer(renderer)

# new render window only used here for saving the image
# set the size to (1, 1) in case of off_screen_rendering
# this window would not be shown
temp_renwin = tvtk.RenderWindow(
size=(1, 1), off_screen_rendering=self.off_screen_rendering)

self._renwin = temp_renwin

# older VTK may not support stereo rendering
if orig_renwin.stereo_render:
temp_renwin.set(
stereo_capable_window=orig_renwin.stereo_capable_window,
stereo_type=orig_renwin.stereo_type,
stereo_render=True)

# We need an interactor to contain the RenderWindow so that
# upon resizing an offscreen window, the window does not pop up
# Mar 30, 2016: On Mac OSX with vtkCocoaRenderWindow, this
# causes the rendering to fail upon resizing
if sys.platform != "darwin" and self.off_screen_rendering:
interactor = tvtk.RenderWindowInteractor(render_window=temp_renwin)
interactor.initialize()

temp_renwin.add_renderer(renderer)
temp_renwin.size = size
self.render()

# More rendering occurs in the save method
meth(file_name, **kw_args)
self.set_size(orig_size)

# Give the renderer back to the original render window
temp_renwin.remove_renderer(renderer)
temp_renwin.finalize()
orig_renwin.add_renderer(renderer)

# Restore the render window
self._renwin = orig_renwin
self.render()

self._record_methods('save(%r, %r)'%(file_name, size))
else:
meth(file_name, **kw_args)
Expand All @@ -417,7 +463,7 @@ def save_ps(self, file_name):
For vector graphics use the save_gl2ps method."""
if len(file_name) != 0:
w2if = tvtk.WindowToImageFilter(read_front_buffer=
not self.off_screen_rendering)
not self.off_screen_rendering)
w2if.magnification = self.magnification
self._lift()
w2if.input = self._renwin
Expand All @@ -430,7 +476,7 @@ def save_bmp(self, file_name):
"""Save to a BMP image file."""
if len(file_name) != 0:
w2if = tvtk.WindowToImageFilter(read_front_buffer=
not self.off_screen_rendering)
not self.off_screen_rendering)
w2if.magnification = self.magnification
self._lift()
w2if.input = self._renwin
Expand All @@ -443,7 +489,7 @@ def save_tiff(self, file_name):
"""Save to a TIFF image file."""
if len(file_name) != 0:
w2if = tvtk.WindowToImageFilter(read_front_buffer=
not self.off_screen_rendering)
not self.off_screen_rendering)
w2if.magnification = self.magnification
self._lift()
w2if.input = self._renwin
Expand All @@ -456,7 +502,7 @@ def save_png(self, file_name):
"""Save to a PNG image file."""
if len(file_name) != 0:
w2if = tvtk.WindowToImageFilter(read_front_buffer=
not self.off_screen_rendering)
not self.off_screen_rendering)
w2if.magnification = self.magnification
self._lift()
w2if.input = self._renwin
Expand All @@ -473,7 +519,7 @@ def save_jpg(self, file_name, quality=None, progressive=None):
if not quality and not progressive:
quality, progressive = self.jpeg_quality, self.jpeg_progressive
w2if = tvtk.WindowToImageFilter(read_front_buffer=
not self.off_screen_rendering)
not self.off_screen_rendering)
w2if.magnification = self.magnification
self._lift()
w2if.input = self._renwin
Expand Down
21 changes: 13 additions & 8 deletions tvtk/pyface/ui/wx/scene.py
Expand Up @@ -571,6 +571,10 @@ def OnButtonUp(self, event):
def _closed_fired(self):
super(Scene, self)._closed_fired()
self.picker = None
# Remove OnPaint handler for PaintEvent, otherwise
# OnPaint tries to reset the size of the nonexisting
# renderwindow
wx.EVT_PAINT(self._vtk_control, None)
self._vtk_control = None

###########################################################################
Expand Down Expand Up @@ -658,21 +662,22 @@ def _show_parent_hack(window, parent):
# messed up when the application window is shown. To work
# around this a dynamic IDLE event handler is added and
# immediately removed once it executes. This event handler
# simply forces a resize to occur. The _idle_count allows us
# to execute the idle function a few times (this seems to work
# better).
# simply forces a resize to occur. Previously this event
# handler is excecuted for the first two idle events.
# However this causes the first resizing event after
# initialization to be effectively ignored: the idle handler
# simply resizes it back to the old size.
# Since resizing once seems to be efficient, the idle handler
# is only called for the first idle event.
def _do_idle(event, window=window):
w = wx.GetTopLevelParent(window)
# Force a resize
sz = w.GetSize()
w.SetSize((sz[0]-1, sz[1]-1))
w.SetSize(sz)
window._idle_count -= 1
if window._idle_count < 1:
wx.EVT_IDLE(window, None)
del window._idle_count
# Remove the handler
wx.EVT_IDLE(window, None)

window._idle_count = 2
wx.EVT_IDLE(window, _do_idle)

self._interactor = tvtk.to_tvtk(window._Iren)
Expand Down

0 comments on commit 3bccaaf

Please sign in to comment.