Skip to content

Commit

Permalink
grass.jupyter: Automatically determine image size based on region (#2285
Browse files Browse the repository at this point in the history
)

Using the aspect ratio of the computational region, automatically determine 2D rendered image dimensions. Defaults are now None and one or both values can be specified. Missing values are taken from defaults and modified to match aspect ratio of the computational region extent. Resulting images in notebooks are now smaller without white (empty) areas around the data. As a result, the image of the displayed data is often significantly bigger.
  • Loading branch information
petrasovaa committed Apr 12, 2022
1 parent 0cb81b3 commit 389ce3a
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 6 deletions.
17 changes: 12 additions & 5 deletions python/grass/jupyter/display.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ class GrassRenderer:

def __init__(
self,
height=400,
width=600,
height=None,
width=None,
filename=None,
env=None,
font="sans",
Expand Down Expand Up @@ -82,8 +82,8 @@ def __init__(
else:
self._env = os.environ.copy()
# Environment Settings
self._env["GRASS_RENDER_WIDTH"] = str(width)
self._env["GRASS_RENDER_HEIGHT"] = str(height)
self._env["GRASS_RENDER_WIDTH"] = str(width) if width else "600"
self._env["GRASS_RENDER_HEIGHT"] = str(height) if height else "400"
self._env["GRASS_FONT"] = font
self._env["GRASS_RENDER_TEXT_SIZE"] = str(text_size)
self._env["GRASS_RENDER_IMMEDIATE"] = renderer
Expand Down Expand Up @@ -117,7 +117,13 @@ def cleanup(tmpdir):
self._env["GRASS_LEGEND_FILE"] = str(self._legend_file)

# rendering region setting
self._region_manager = RegionManagerFor2D(use_region, saved_region, self._env)
self._region_manager = RegionManagerFor2D(
use_region=use_region,
saved_region=saved_region,
width=width,
height=height,
env=self._env,
)

@property
def filename(self):
Expand Down Expand Up @@ -146,6 +152,7 @@ def run(self, module, **kwargs):
# Check module is from display library then run
if module[0] == "d":
self._region_manager.set_region_from_command(module, **kwargs)
self._region_manager.adjust_rendering_size_from_region()
gs.run_command(module, env=self._env, **kwargs)
else:
raise ValueError("Module must begin with letter 'd'.")
Expand Down
22 changes: 21 additions & 1 deletion python/grass/jupyter/region.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
get_map_name_from_d_command,
get_region,
reproject_region,
get_rendering_size,
)


Expand Down Expand Up @@ -110,27 +111,46 @@ def _set_bbox(self, env):
class RegionManagerFor2D:
"""Region manager for 2D displays (gets region from display commands)"""

def __init__(self, use_region, saved_region, env):
def __init__(self, use_region, saved_region, width, height, env):
"""Manages region during rendering.
:param use_region: if True, use either current or provided saved region,
else derive region from rendered layers
:param saved_region: if name of saved_region is provided,
this region is then used for rendering
:param width: rendering width
:param height: rendering height
:param env: environment for rendering
"""
self._env = env
self._width = width
self._height = height
self._use_region = use_region
self._saved_region = saved_region
self._extent_set = False
self._resolution_set = False
self._size_set = False

def set_region_from_env(self, env):
"""Copies GRASS_REGION from provided environment
to local environment to set the computational region"""
if "GRASS_REGION" in env:
self._env["GRASS_REGION"] = env["GRASS_REGION"]

def adjust_rendering_size_from_region(self):
"""Sets the environmental render width and height variables
based on the region dimensions. Only first call of this
method sets the variables, subsequent calls do not adjust them.
"""
if not self._size_set:
region = gs.region(env=self._env)
width, height = get_rendering_size(region, self._width, self._height)
self._env["GRASS_RENDER_WIDTH"] = str(round(width))
self._env["GRASS_RENDER_HEIGHT"] = str(round(height))
# only when extent is set you can disable future size setting
if self._extent_set:
self._size_set = True

def set_region_from_command(self, module, **kwargs):
"""Sets computational region for rendering.
Expand Down
30 changes: 30 additions & 0 deletions python/grass/jupyter/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,33 @@ def get_map_name_from_d_command(module, **kwargs):
special = {"d.his": "hue", "d.legend": "raster", "d.rgb": "red", "d.shade": "shade"}
parameter = special.get(module, "map")
return kwargs.get(parameter, "")


def get_rendering_size(region, width, height, default_width=600, default_height=400):
"""Returns the rendering width and height based
on the region aspect ratio.
:param dict region: region dictionary
:param integer width: rendering width (can be None)
:param integer height: rendering height (can be None)
:param integer default_width: default rendering width (can be None)
:param integer default_height: default rendering height (can be None)
:return tuple (width, height): adjusted width and height
When both width and height are provided, values are returned without
adjustment. When one value is provided, the other is computed
based on the region aspect ratio. When no dimension is given,
the default width or height is used and the other dimension computed.
"""
if width and height:
return (width, height)
region_width = region["e"] - region["w"]
region_height = region["n"] - region["s"]
if width:
return (width, round(width * region_height / region_width))
if height:
return (round(height * region_width / region_height), height)
if region_height > region_width:
return (round(default_height * region_width / region_height), default_height)
return (default_width, round(default_width * region_height / region_width))

0 comments on commit 389ce3a

Please sign in to comment.