From 737218bd3bff6e46fef19620adaedc14103d6cd3 Mon Sep 17 00:00:00 2001 From: Anna Petrasova Date: Tue, 12 Apr 2022 17:14:23 -0400 Subject: [PATCH] grass.jupyter: Automatically determine image size based on region (#2285) 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. --- python/grass/jupyter/display.py | 17 ++++++++++++----- python/grass/jupyter/region.py | 22 +++++++++++++++++++++- python/grass/jupyter/utils.py | 30 ++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 6 deletions(-) diff --git a/python/grass/jupyter/display.py b/python/grass/jupyter/display.py index 27250f0418a..f747a398d7b 100644 --- a/python/grass/jupyter/display.py +++ b/python/grass/jupyter/display.py @@ -47,8 +47,8 @@ class GrassRenderer: def __init__( self, - height=400, - width=600, + height=None, + width=None, filename=None, env=None, font="sans", @@ -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 @@ -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): @@ -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'.") diff --git a/python/grass/jupyter/region.py b/python/grass/jupyter/region.py index afd198edb44..7b33e94a823 100644 --- a/python/grass/jupyter/region.py +++ b/python/grass/jupyter/region.py @@ -20,6 +20,7 @@ get_map_name_from_d_command, get_region, reproject_region, + get_rendering_size, ) @@ -110,20 +111,25 @@ 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 @@ -131,6 +137,20 @@ def set_region_from_env(self, env): 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. diff --git a/python/grass/jupyter/utils.py b/python/grass/jupyter/utils.py index c03e8ccb139..fd0754433e2 100644 --- a/python/grass/jupyter/utils.py +++ b/python/grass/jupyter/utils.py @@ -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))