Skip to content
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

Screenshot without margins #7

Open
wants to merge 86 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
86 commits
Select commit Hold shift + click to select a range
6873d6c
remove 2d black margins screenshot
melonora Mar 8, 2024
5948051
add param to viewer.screenshot
melonora Mar 8, 2024
d5c0c72
Merge branch 'main' into screenshot_without_margins
melonora May 5, 2024
4cc0d90
fix 1 pixel off
melonora May 5, 2024
6364fd0
Co-Authored-By: olusesan.ajina@gmail.com
melonora May 5, 2024
fed5a73
Merge branch 'screenshot_without_margins' of https://github.com/melon…
melonora May 5, 2024
d3831bd
add test
melonora May 5, 2024
d012489
fix tests
melonora May 5, 2024
4d9b73f
chance parameter name
melonora May 5, 2024
9212a9f
fix tests
melonora May 5, 2024
d091e32
rename to no_margins
melonora May 5, 2024
9daa6cb
switch to margins parameter and default old behaviour
melonora May 6, 2024
feae403
Merge branch 'main' into screenshot_without_margins
melonora May 8, 2024
221692c
revert to fit_to_data
melonora May 12, 2024
41a8326
disallow fit_to_data if canvas_only is False
melonora May 18, 2024
10e86de
adjust test
melonora May 18, 2024
bffcbb2
fix test camera center
melonora May 24, 2024
e0e2498
close viewer prevent dangling animation
melonora May 26, 2024
effce1e
add fit_to_data to nbscreenshot
melonora May 26, 2024
3a79f08
Update napari/components/viewer_model.py
melonora May 27, 2024
0997e89
change parameter
melonora May 27, 2024
33a2a32
update scale_factor calc
melonora May 27, 2024
4bb7854
Update napari/_qt/qt_main_window.py
melonora May 27, 2024
e2ebf1b
update error messages
melonora May 27, 2024
31e8e9a
Merge branch 'screenshot_without_margins' of https://github.com/melon…
melonora May 27, 2024
2f71728
update docstrings
melonora May 27, 2024
6fcf9be
fix error
melonora May 27, 2024
7635530
minor grammar fix + fit docstring in 80c
jni May 27, 2024
65729bb
Make error strings fit in 80c
jni May 27, 2024
1d3140c
Update docstring for screenshot
jni May 27, 2024
5d50d64
Fix outdated docstring for Viewer.reset_view
jni May 27, 2024
8e5cba5
Fix fit-to-data docstring in two more places
jni May 27, 2024
7eabe99
Fix test error message match
jni May 27, 2024
55d6db9
rename parameter
melonora May 27, 2024
7105658
change to export_view
melonora Jun 1, 2024
6afe24b
address comments
melonora Jun 14, 2024
f622097
remove docstring
melonora Jun 14, 2024
df0b991
Merge branch 'main' into screenshot_without_margins
melonora Jun 14, 2024
cb1e951
fix test
melonora Jun 14, 2024
6984781
Merge branch 'main' into screenshot_without_margins
melonora Jun 14, 2024
21feada
revert to FutureWarning
melonora Jun 14, 2024
b840574
Merge branch 'screenshot_without_margins' of https://github.com/melon…
melonora Jun 14, 2024
9fa60ca
change to export_figure
melonora Jun 20, 2024
933cb7f
Merge branch 'main' into screenshot_without_margins
melonora Jun 22, 2024
4b6c429
Match docstring formatting to PEP257 and clarify scale
jni Jun 22, 2024
21f03cb
Update napari/_qt/_tests/test_qt_viewer.py
melonora Jun 23, 2024
a2eee46
adjust docstring
melonora Jun 23, 2024
90ca6d3
typehints
melonora Jun 23, 2024
657eaac
typehints
melonora Jun 23, 2024
5cb8a55
typehints
melonora Jun 23, 2024
a80e64f
typehints
melonora Jun 23, 2024
b0eed32
Update napari/components/viewer_model.py
melonora Jun 23, 2024
c142c3c
add example
melonora Jun 23, 2024
6e2a16c
Merge branch 'screenshot_without_margins' of https://github.com/melon…
melonora Jun 23, 2024
5cccde9
set default scale to 1, adjust docstring
melonora Jun 23, 2024
fbe7c58
adjust example and docstrings
melonora Jun 23, 2024
74aa262
adjust explanation docstring
melonora Jun 23, 2024
e28e9d3
Only allow float or int
melonora Jun 23, 2024
b511309
fix isinstance
melonora Jun 23, 2024
32f9bb3
Merge branch 'main' into screenshot_without_margins
jni Jun 26, 2024
3b1fad7
Update napari/_qt/qt_main_window.py
melonora Jul 1, 2024
343182a
Apply suggestions from code review
melonora Jul 1, 2024
04a2ba2
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 1, 2024
667795f
add missed import
Czaki Jul 1, 2024
9137b27
Update napari/_qt/_tests/test_qt_viewer.py
Czaki Jul 1, 2024
25167dc
Update napari/viewer.py
melonora Jul 2, 2024
57a93df
use extent.step
melonora Jul 2, 2024
8cb09bf
Merge branch 'screenshot_without_margins' of https://github.com/melon…
melonora Jul 2, 2024
f6ff4bf
remove conditional
melonora Jul 2, 2024
dde2642
adjust docstring
melonora Jul 2, 2024
25596a8
adjust docstring
melonora Jul 2, 2024
9b1a230
scale -> scale_factor in gallery example
jni Jul 5, 2024
eb89264
Add some missing layer actions tests (split rgb, split and merge acti…
dalthviz Jul 6, 2024
7646145
Restore events to `QtViewer.canvas` (#7060)
Czaki Jul 6, 2024
4296ac5
Use minimum step size across all dims
jni Jul 6, 2024
e8c3f6f
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 6, 2024
f5fe4f4
Remove f-string from translation call
jni Jul 6, 2024
374a6df
Move `IO_Utilities` and `Acquire` submenus to their own group in the …
DragaDoncila Jul 6, 2024
46fdf4c
Add empty menu placeholder actions using functional context keys from…
DragaDoncila Jul 6, 2024
bc1d95f
Add warning in docstring about ignored size
jni Jul 7, 2024
2bd587d
Minor docstring clarification
jni Jul 7, 2024
4d06794
Fix incorrect setting of scale in test
jni Jul 7, 2024
29f6d66
Use allclose to test screenshot size when rounding
jni Jul 7, 2024
de4bb80
Refactor screenshot function to clarify logic flow
jni Jul 7, 2024
77bf7ed
Docstring and typing fixes
jni Jul 7, 2024
2935b04
Merge branch 'main' into screenshot_without_margins
jni Jul 7, 2024
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
19 changes: 18 additions & 1 deletion napari/_qt/_tests/test_qt_viewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,11 +254,28 @@ def test_screenshot(make_napari_viewer):

# Take screenshot
with pytest.warns(FutureWarning):
screenshot = viewer.window.qt_viewer.screenshot(flash=False)
viewer.window.qt_viewer.screenshot(flash=False)
screenshot = viewer.window.screenshot(flash=False, canvas_only=True)
assert screenshot.ndim == 3


def test_screenshot_fit_to_data(make_napari_viewer):
viewer = make_napari_viewer()

np.random.seed(0)
# Add image
data = np.random.randint(150, 250, size=(250, 250))
viewer.add_image(data)
img = viewer.screenshot(flash=False, fit_to_data=True)
assert img.shape == (250, 250, 4)
assert np.all(img != np.array([0, 0, 0, 1]))

Copy link
Owner Author

Choose a reason for hiding this comment

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

This only fails when running the test itself. If you use the same code in a python script it does not fail. For some reason, there is no response to the canvas resize event when running inside a test.

# TODO: check why this fails in the testing suite but not when testing with scratch script with same example.
img = viewer.screenshot(fit_to_data=True, scale=8)
assert img.shape == (250 * 8, 250 * 8, 4)
assert np.all(img != np.array([0, 0, 0, 1]))


@pytest.mark.skip('new approach')
def test_screenshot_dialog(make_napari_viewer, tmpdir):
"""Test save screenshot functionality."""
Expand Down
54 changes: 48 additions & 6 deletions napari/_qt/qt_main_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -1504,7 +1504,12 @@ def _restart(self):
self._qt_window.restart()

def _screenshot(
self, size=None, scale=None, flash=True, canvas_only=False
self,
size=None,
scale=None,
flash=True,
canvas_only=False,
fit_to_data: bool = False,
) -> 'QImage':
"""Capture screenshot of the currently displayed viewer.

Expand All @@ -1524,16 +1529,39 @@ def _screenshot(
If True, screenshot shows only the image display canvas, and
if False include the napari viewer frame in the screenshot,
By default, True.
fit_to_data: bool
Whether to fit a bounding box around the data to prevent margins of showing in the screenshot.
Currently, if this is False it means a screenshot of the current canvas will be generated with
black margins if visible.

Returns
-------
img : QImage
"""
from napari._qt.utils import add_flash_animation

canvas = self._qt_viewer.canvas
prev_size = canvas.size
if fit_to_data:
ndisplay = self._qt_viewer.viewer.dims.ndisplay
camera = self._qt_viewer.viewer.camera
old_center = camera.center
old_zoom = camera.zoom
if ndisplay > 2:
raise NotImplementedError(
'margins equal to False is not yet implemented for 3D. Please set margins to True.'
)

self._qt_viewer.viewer.reset_view()
canvas.size = (
self._qt_viewer.viewer.layers.extent.world[1][
-ndisplay:
].astype(int)
+ 1
)
self._qt_viewer.viewer.reset_view(screenshot=True)

if canvas_only:
canvas = self._qt_viewer.canvas
prev_size = canvas.size
if size is not None:
if len(size) != 2:
raise ValueError(
Expand All @@ -1557,16 +1585,25 @@ def _screenshot(
add_flash_animation(self._qt_viewer._welcome_widget)
finally:
# make sure we always go back to the right canvas size
if size is not None or scale is not None:
if size is not None or scale is not None or fit_to_data:
canvas.size = prev_size
if fit_to_data:
camera.center = old_center
camera.zoom = old_zoom
else:
img = self._qt_window.grab().toImage()
if flash:
add_flash_animation(self._qt_window)
return img

def screenshot(
self, path=None, size=None, scale=None, flash=True, canvas_only=False
self,
path=None,
size=None,
scale=None,
flash=True,
canvas_only=False,
fit_to_data: bool = False,
):
"""Take currently displayed viewer and convert to an image array.

Expand All @@ -1588,6 +1625,9 @@ def screenshot(
If True, screenshot shows only the image display canvas, and
if False includes the napari viewer frame in the screenshot,
By default, True.
fit_to_data : bool
Whether to fit a bounding box around the data to prevent margins of showing in the screenshot. This fits a
bounding box around all data currently being displayed in the viewer (resets the view).

Returns
-------
Expand All @@ -1596,7 +1636,9 @@ def screenshot(
upper-left corner of the rendered region.
"""

img = QImg2array(self._screenshot(size, scale, flash, canvas_only))
img = QImg2array(
self._screenshot(size, scale, flash, canvas_only, fit_to_data)
)
if path is not None:
imsave(path, img)
return img
Expand Down
22 changes: 18 additions & 4 deletions napari/components/viewer_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -377,8 +377,19 @@ def _sliced_extent_world_augmented(self) -> np.ndarray:
)
return self.layers._extent_world_augmented[:, self.dims.displayed]

def reset_view(self) -> None:
"""Reset the camera view."""
def reset_view(self, screenshot=False) -> None:
"""
Reset the camera view.

This reset has two modes, one for when viewing the data in the viewer and one for when taking a
screenshot with a canvas not showing margins around the data. The two differ in the scaling
factor of the zoom.

Parameters
----------
screenshot: bool
Whether to reset the view in screenshot mode. Default is False.
"""

extent = self._sliced_extent_world_augmented
scene_size = extent[1] - extent[0]
Expand All @@ -402,12 +413,15 @@ def reset_view(self) -> None:
# zoom is definied as the number of canvas pixels per world pixel
# The default value used below will zoom such that the whole field
# of view will occupy 95% of the canvas on the most filled axis

scale_factor = 0.95 if not screenshot else 1

if np.max(size) == 0:
self.camera.zoom = 0.95 * np.min(self._canvas_size)
self.camera.zoom = scale_factor * np.min(self._canvas_size)
else:
scale = np.array(size[-2:])
scale[np.isclose(scale, 0)] = 1
self.camera.zoom = 0.95 * np.min(
self.camera.zoom = scale_factor * np.min(
np.array(self._canvas_size) / scale
)
self.camera.angles = (0, 0, 90)
Expand Down
6 changes: 6 additions & 0 deletions napari/viewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ def screenshot(
scale=None,
canvas_only=True,
flash: bool = True,
fit_to_data: bool = False,
):
"""Take currently displayed screen and convert to an image array.

Expand All @@ -117,6 +118,10 @@ def screenshot(
Flag to indicate whether flash animation should be shown after
the screenshot was captured.
By default, True.
fit_to_data : bool
Whether to fit a bounding box around the data to prevent margins of showing in the screenshot.
Currently, if this is False it means a screenshot of the whole data will be generated without margins (a
temporary view reset is applied so the canvas has all data within the extent of the canvas).

Returns
-------
Expand All @@ -130,6 +135,7 @@ def screenshot(
scale=scale,
flash=flash,
canvas_only=canvas_only,
fit_to_data=fit_to_data,
)

def show(self, *, block=False):
Expand Down
Loading