Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
7f5c884
call SetSize for the vtkRenderWindow when wx.scene.set_size is called
kitchoi Mar 15, 2016
b8694c1
add pillow to travis-requirement
kitchoi Mar 15, 2016
80f13f0
added test for mlab.savefig
kitchoi Mar 15, 2016
d7cdb87
use cleanUp to close all figure instead of using contextmanager
kitchoi Mar 17, 2016
ac061bc
only the test using Engine + off_screen_rendering fails on master but…
kitchoi Mar 17, 2016
ae1f11c
let's only test against the image size and forget about black spots w…
kitchoi Mar 17, 2016
842d052
set_size was doing the right thing, instead it was because the window…
kitchoi Mar 17, 2016
3a3761f
size should be strictly respected. int(target_size // mag) gives wron…
kitchoi Mar 17, 2016
4d85baf
mend
kitchoi Mar 17, 2016
c5e2fb3
Revert "mend"
kitchoi Mar 17, 2016
bd68dd6
improve comment and function's order
kitchoi Mar 17, 2016
b035169
fix lanaguage in the comment [ci skip]
kitchoi Mar 17, 2016
a09a1a3
figure should be passed mlab.quiver3d in the test cases
kitchoi Mar 24, 2016
19529f8
calling the resize functions for two IdleEvent causes size to be wron…
kitchoi Mar 24, 2016
2b218a8
modified docstring for savefig
Mar 29, 2016
af88c5a
remove OnPaint handler after the scene is closed
kitchoi Mar 29, 2016
c00b284
create a new render window for snapshot
Mar 31, 2016
0597d50
revert lifting the window for offscreen rendering in wx
Mar 31, 2016
44eac58
Merge branch 'fix-savefig-black-image' into fix-savefig-size
Mar 31, 2016
233d993
added test for magnification and check for black spot
Mar 31, 2016
324d67f
Merge branch 'master' into fix-savefig-size-black-image
kitchoi Mar 31, 2016
e3e0c8d
check stereo/off_screen_rendering for older version of VTK
kitchoi Mar 31, 2016
7ce1d5b
read_front_buffer = False
kitchoi Mar 31, 2016
af81647
finalize temporary render window
kitchoi Mar 31, 2016
194e89d
use interactor if not on Mac
kitchoi Mar 31, 2016
e83050f
force offscreen for the temporary renderwindow for better user experi…
kitchoi Mar 31, 2016
e6563e7
safer to use offscreen rendering only when directed
kitchoi Mar 31, 2016
30e6e2b
use interactor to hold the render window when offscreen is true and n…
kitchoi Mar 31, 2016
9b91e6d
clean code
kitchoi Mar 31, 2016
fc916f9
whenever render is called, disable_render should be honoured
kitchoi Mar 31, 2016
8996ce2
fix comment
kitchoi Mar 31, 2016
2976e90
changes to read_front_buffer is not needed now
kitchoi Apr 1, 2016
54b7562
Merge branch 'master' into fix-savefig-size-black-image
kitchoi Apr 1, 2016
de243bb
Merge branch 'master' into fix-savefig-size-black-image
kitchoi Apr 1, 2016
82ece3a
better ordering of tests
kitchoi Apr 1, 2016
f7c6e46
xrange to range
kitchoi Apr 6, 2016
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 127 additions & 5 deletions integrationtests/mayavi/test_mlab_savefig.py
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
1 change: 1 addition & 0 deletions travis-requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pillow
mock
Sphinx
coverage
Expand Down
62 changes: 54 additions & 8 deletions tvtk/pyface/tvtk_scene.py
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one makes me a bit nervous because I checked the rest of the class, and nowhere it is enforced that self._vtk_control is not None. We have the same problem pretty much everywhere. I am generally fine with throwing an exception, but if None is an allowed value, we should react appropriately without throwing. If it's not, it's better to enforce the presence of a control at construction time.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_create_control is called here https://github.com/enthought/mayavi/blob/fix-savefig-size-black-image/tvtk/pyface/tvtk_scene.py#L205 which assigns _vtk_control. Unless self._vtk_control = None is done somewhere outside of this function (which I have not checked), we can ensure _vtk_control is not None?

@kitchoi kitchoi Apr 21, 2016

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On other hand, this change is meant to silent error message on Linux + wx 2.8.10 (see message in #346)
The same error does not occur on OSX with wxpython 3.0.2, so I have omitted this change in the reimplementation #346

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good. Merging.

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