From eb056a698f6cb89250b8dfb6bb1dc50957f96fcb Mon Sep 17 00:00:00 2001 From: Raphael Date: Thu, 23 Nov 2023 18:55:25 +0100 Subject: [PATCH 01/20] fix UserDefined classification issue: max-bin replaced by data-max --- eomaps/eomaps.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eomaps/eomaps.py b/eomaps/eomaps.py index a9359b3d9..6cf649d7f 100644 --- a/eomaps/eomaps.py +++ b/eomaps/eomaps.py @@ -4567,7 +4567,7 @@ def _classify_data( bins = [vmin, *bins] if vmax > max(bins): - bins[np.argmax(bins)] = vmax + bins = [*bins, vmax] cbcmap = cmap norm = mpl.colors.BoundaryNorm(bins, cmap.N) From 159929c3e622b07700909204afd15096480c2cb5 Mon Sep 17 00:00:00 2001 From: Raphael Quast Date: Thu, 23 Nov 2023 21:05:46 +0100 Subject: [PATCH 02/20] Update version to v7.3.2 --- eomaps/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eomaps/_version.py b/eomaps/_version.py index 638b07121..6b5a69c78 100644 --- a/eomaps/_version.py +++ b/eomaps/_version.py @@ -1 +1 @@ -__version__ = "7.3.1" +__version__ = "7.3.2" From d9fe396df6ded28ce8300281e87388b6a8e54459 Mon Sep 17 00:00:00 2001 From: Raphael Quast Date: Thu, 23 Nov 2023 23:03:33 +0100 Subject: [PATCH 03/20] allow using kwargs with m.add.feature.preset(...) for multiple features --- eomaps/ne_features.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/eomaps/ne_features.py b/eomaps/ne_features.py index 4cec87112..f6986d485 100644 --- a/eomaps/ne_features.py +++ b/eomaps/ne_features.py @@ -34,7 +34,7 @@ class NaturalEarth_presets: def __init__(self, m): self._m = m - def __call__(self, *args, scale=50, layer=None): + def __call__(self, *args, scale=50, layer=None, **kwargs): """ Add multiple preset-features in one go. @@ -42,7 +42,7 @@ def __call__(self, *args, scale=50, layer=None): Parameters ---------- - *args : str + \*args : str The names of the features to add. scale : int or str Set the scale of the feature preset (10, 50, 110 or "auto") @@ -54,7 +54,9 @@ def __call__(self, *args, scale=50, layer=None): - If None, the layer of the parent object is used. The default is None. - + \*\*kwargs: + Additional style kwargs passed to all features + (e.g. alpha, facecolor, edgecolor, linewidth, ...) """ wrong_names = set(args).difference(self._feature_names) assert len(wrong_names) == 0, ( @@ -63,7 +65,7 @@ def __call__(self, *args, scale=50, layer=None): ) for a in args: - getattr(self, a)(scale=scale, layer=layer) + getattr(self, a)(scale=scale, layer=layer, **kwargs) @property def _feature_names(self): From bf03dc44335e00a91e5b2d42b4b73eb321b0c413 Mon Sep 17 00:00:00 2001 From: Raphael Quast Date: Thu, 30 Nov 2023 21:23:57 +0100 Subject: [PATCH 04/20] fix issues with RGBA snapshots for inline plots in jupyter notebooks --- eomaps/eomaps.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eomaps/eomaps.py b/eomaps/eomaps.py index 6cf649d7f..a7bdddbf4 100644 --- a/eomaps/eomaps.py +++ b/eomaps/eomaps.py @@ -3478,7 +3478,7 @@ def snapshot(self, *layer, transparent=False, clear=False): try: from IPython.display import display - display(Image.fromarray(sn, "RGBA"), display_id=True, clear=clear) + display(Image.fromarray(sn, "RGBA").convert('RGB'), display_id=True, clear=clear) except Exception: _log.exception( "Unable to display the snapshot... is the script " From 56fa3a28c2f45667db5ec53cc0f078f43ddd627d Mon Sep 17 00:00:00 2001 From: Raphael Quast Date: Thu, 30 Nov 2023 21:24:51 +0100 Subject: [PATCH 05/20] hide ipympl header label in jupyter notebooks by default --- eomaps/eomaps.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/eomaps/eomaps.py b/eomaps/eomaps.py index a7bdddbf4..f98e26a1c 100644 --- a/eomaps/eomaps.py +++ b/eomaps/eomaps.py @@ -4090,6 +4090,9 @@ def _init_figure(self, ax=None, plot_crs=None, **kwargs): _handle_backends() self._f = plt.figure(**kwargs) + # to hide canvas header in jupyter notebooks (default figure label) + self._f.canvas.header_visible = False + # override Figure.savefig with Maps.savefig but keep original # method accessible via Figure._mpl_orig_savefig From 26aa8bb5d6401e6c263757a0bf2b897a16316d9d Mon Sep 17 00:00:00 2001 From: Raphael Quast Date: Thu, 30 Nov 2023 21:27:14 +0100 Subject: [PATCH 06/20] make pre-commit happy --- eomaps/eomaps.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/eomaps/eomaps.py b/eomaps/eomaps.py index f98e26a1c..5ef870471 100644 --- a/eomaps/eomaps.py +++ b/eomaps/eomaps.py @@ -3478,7 +3478,11 @@ def snapshot(self, *layer, transparent=False, clear=False): try: from IPython.display import display - display(Image.fromarray(sn, "RGBA").convert('RGB'), display_id=True, clear=clear) + display( + Image.fromarray(sn, "RGBA").convert("RGB"), + display_id=True, + clear=clear, + ) except Exception: _log.exception( "Unable to display the snapshot... is the script " @@ -4093,7 +4097,6 @@ def _init_figure(self, ax=None, plot_crs=None, **kwargs): # to hide canvas header in jupyter notebooks (default figure label) self._f.canvas.header_visible = False - # override Figure.savefig with Maps.savefig but keep original # method accessible via Figure._mpl_orig_savefig # (this ensures that using the save-buttons in the gui or pressing From ae7a743f4b184fcea5ea5cb31755b6c443f6d1b0 Mon Sep 17 00:00:00 2001 From: Raphael Quast Date: Fri, 1 Dec 2023 12:18:27 +0100 Subject: [PATCH 07/20] improve debug logging on draw --- eomaps/helpers.py | 49 +++++++++++++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/eomaps/helpers.py b/eomaps/helpers.py index c4fc715ec..20affb7d2 100644 --- a/eomaps/helpers.py +++ b/eomaps/helpers.py @@ -2162,26 +2162,36 @@ def _disconnect_draw(self): def on_draw(self, event): """Callback to register with 'draw_event'.""" cv = self.canvas - _log.log(5, "draw") + loglevel = _log.getEffectiveLevel() - try: - if ( - "RendererBase._draw_disabled" - in cv.get_renderer().draw_image.__qualname__ - ): - # TODO this fixes issues when saving figues with a "tight" bbox, e.g.: - # m.savefig(bbox_inches='tight', pad_inches=0.1) - - # This workaround is necessary but the implementation is suboptimal since - # it relies on the __qualname__ to identify if the - # `matplotlib.backend_bases.RendererBase._draw_disabled()` context is active - # The reason why the "_draw_disabled" context has to be handled explicitly - # is because otherwise empty backgrounds would be fetched (and cached) by - # the draw-event and the export would result in an empty figure. + if loglevel <= 5: + _log.log(5, "draw") + + if hasattr(cv, "get_renderer"): + # TODO this fixes issues when saving figues with a "tight" bbox, e.g.: + # m.savefig(bbox_inches='tight', pad_inches=0.1) + + # This workaround is necessary but the implementation is suboptimal since + # it relies on the __qualname__ to identify if the + # `matplotlib.backend_bases.RendererBase._draw_disabled()` context is active + # The reason why the "_draw_disabled" context has to be handled explicitly + # is because otherwise empty backgrounds would be fetched (and cached) by + # the draw-event and the export would result in an empty figure. + + try: + if ( + "RendererBase._draw_disabled" + in cv.get_renderer().draw_image.__qualname__ + ): + return + except AttributeError: + # return on AttributeError to handle backends that don't expose the renderer + if loglevel <= 5: + _log.log( + 5, + "error during draw: there was no renderer to check", + ) return - except AttributeError: - # return on AttributeError to handle backends that don't expose the renderer - return if event is not None: if event.canvas != cv: @@ -2226,7 +2236,8 @@ def on_draw(self, event): except Exception: # we need to catch exceptions since QT does not like them... - pass + if loglevel <= 5: + _log.log(5, "There was an error during draw!", exc_info=True) def add_artist(self, art, layer=None): """ From bbc879ac3f79b3148a00530aec6295d0027eed64 Mon Sep 17 00:00:00 2001 From: Raphael Quast Date: Fri, 1 Dec 2023 14:02:32 +0100 Subject: [PATCH 08/20] minor --- eomaps/eomaps.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/eomaps/eomaps.py b/eomaps/eomaps.py index 5ef870471..0496029a8 100644 --- a/eomaps/eomaps.py +++ b/eomaps/eomaps.py @@ -93,8 +93,9 @@ def _handle_backends(): _log.info( "EOmaps disables matplotlib's interactive mode (e.g. 'plt.ioff()') " f"for the backend {plt.get_backend()}.\n" - "Call `m.snapshot()` to print a static snapshot of the map " - "to a Jupyter Notebook cell (or an IPython console)!" + "Call `m.show()`, `m.show_layer` or `m.snapshot()` to print a " + "static snapshot of the map to a Jupyter Notebook cell " + "(or an IPython console)!" ) Maps._backend_warning_shown = True From 9a3a06cbc9edb0d0ecd4de77e50124fe924c24bf Mon Sep 17 00:00:00 2001 From: Raphael Quast Date: Fri, 1 Dec 2023 14:05:23 +0100 Subject: [PATCH 09/20] minor debug logging update --- eomaps/helpers.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/eomaps/helpers.py b/eomaps/helpers.py index 20affb7d2..b3bc99b44 100644 --- a/eomaps/helpers.py +++ b/eomaps/helpers.py @@ -2164,9 +2164,6 @@ def on_draw(self, event): cv = self.canvas loglevel = _log.getEffectiveLevel() - if loglevel <= 5: - _log.log(5, "draw") - if hasattr(cv, "get_renderer"): # TODO this fixes issues when saving figues with a "tight" bbox, e.g.: # m.savefig(bbox_inches='tight', pad_inches=0.1) @@ -2178,6 +2175,11 @@ def on_draw(self, event): # is because otherwise empty backgrounds would be fetched (and cached) by # the draw-event and the export would result in an empty figure. + renderer = cv.get_renderer() + if renderer is None: + # don't run on_draw if no renderer is available + return + try: if ( "RendererBase._draw_disabled" @@ -2192,6 +2194,17 @@ def on_draw(self, event): "error during draw: there was no renderer to check", ) return + else: + # don't run on_draw if no renderer is available + # (this is true for svg export where mpl export routines + # are used to avoid issues) + if loglevel <= 5: + _log.log(5, " not drawing") + + return + + if loglevel <= 5: + _log.log(5, "draw") if event is not None: if event.canvas != cv: From 57cd8d1da4c431dc87fa21f8b8201d978137ec6b Mon Sep 17 00:00:00 2001 From: Raphael Quast Date: Fri, 1 Dec 2023 14:06:56 +0100 Subject: [PATCH 10/20] implement better syntax for disabling draw or update in the BlitManager --- eomaps/helpers.py | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/eomaps/helpers.py b/eomaps/helpers.py index b3bc99b44..887bc2b46 100644 --- a/eomaps/helpers.py +++ b/eomaps/helpers.py @@ -624,6 +624,13 @@ def modifier_pressed(self, val): self._modifier_pressed = val self.m.cb.execute_callbacks(not val) + if self._modifier_pressed: + self._disable_draw = True + self._disable_update = True + else: + self._disable_draw = False + self._disable_update = False + @property def ms(self): return [self.m.parent, *self.m.parent._children] @@ -1549,6 +1556,9 @@ def __init__(self, m): List of the artists to manage """ + self._disable_draw = False + self._disable_update = False + self._m = m self._artists = dict() @@ -2109,12 +2119,11 @@ def _do_fetch_bg(self, layer, bbox=None): return if renderer: - if not self._m.parent._layout_editor._modifier_pressed: - for art in allartists: - if art not in self._hidden_artists: - art.draw(renderer) - art.stale = False - self._bg_layers[layer] = renderer.copy_from_bbox(bbox) + for art in allartists: + if art not in self._hidden_artists: + art.draw(renderer) + art.stale = False + self._bg_layers[layer] = renderer.copy_from_bbox(bbox) def fetch_bg(self, layer=None, bbox=None): """ @@ -2132,8 +2141,6 @@ def fetch_bg(self, layer=None, bbox=None): The default is None. """ - if self._m.parent._layout_editor._modifier_pressed: - return if layer is None: layer = self.bg_layer @@ -2161,6 +2168,10 @@ def _disconnect_draw(self): def on_draw(self, event): """Callback to register with 'draw_event'.""" + + if self._disable_draw: + return + cv = self.canvas loglevel = _log.getEffectiveLevel() @@ -2681,7 +2692,7 @@ def update( If True, clear the active cell before plotting a snapshot of the figure. The default is True. """ - if self._m.parent._layout_editor._modifier_pressed: + if self._disable_update: # don't update during layout-editing return From 2e64f3ff33e937558b3dd160ecfd0b805b39f139 Mon Sep 17 00:00:00 2001 From: Raphael Quast Date: Fri, 1 Dec 2023 14:12:48 +0100 Subject: [PATCH 11/20] implement better check for issues with "tight_bbox" redraws --- eomaps/helpers.py | 26 +------------------------- 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/eomaps/helpers.py b/eomaps/helpers.py index 887bc2b46..61e7767c6 100644 --- a/eomaps/helpers.py +++ b/eomaps/helpers.py @@ -2175,36 +2175,12 @@ def on_draw(self, event): cv = self.canvas loglevel = _log.getEffectiveLevel() - if hasattr(cv, "get_renderer"): - # TODO this fixes issues when saving figues with a "tight" bbox, e.g.: - # m.savefig(bbox_inches='tight', pad_inches=0.1) - - # This workaround is necessary but the implementation is suboptimal since - # it relies on the __qualname__ to identify if the - # `matplotlib.backend_bases.RendererBase._draw_disabled()` context is active - # The reason why the "_draw_disabled" context has to be handled explicitly - # is because otherwise empty backgrounds would be fetched (and cached) by - # the draw-event and the export would result in an empty figure. + if hasattr(cv, "get_renderer") and not cv.is_saving(): renderer = cv.get_renderer() if renderer is None: # don't run on_draw if no renderer is available return - - try: - if ( - "RendererBase._draw_disabled" - in cv.get_renderer().draw_image.__qualname__ - ): - return - except AttributeError: - # return on AttributeError to handle backends that don't expose the renderer - if loglevel <= 5: - _log.log( - 5, - "error during draw: there was no renderer to check", - ) - return else: # don't run on_draw if no renderer is available # (this is true for svg export where mpl export routines From 667c361a91a146dbcaa3a5526fb012747c56c498 Mon Sep 17 00:00:00 2001 From: Raphael Quast Date: Fri, 1 Dec 2023 14:20:28 +0100 Subject: [PATCH 12/20] fix issues with jupyter inline backend and m.show() --- eomaps/eomaps.py | 45 +++++++++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/eomaps/eomaps.py b/eomaps/eomaps.py index 0496029a8..0c2a2bb05 100644 --- a/eomaps/eomaps.py +++ b/eomaps/eomaps.py @@ -3391,19 +3391,19 @@ def show(self, clear=True): show_layer : Set the currently visible layer. """ - if not plt.isinteractive(): - try: - __IPYTHON__ - except NameError: - plt.show() + # if not plt.isinteractive(): + try: + __IPYTHON__ + except NameError: + plt.show() + else: + active_backend = plt.get_backend() + # print a snapshot to the active ipython cell in case the + # inline-backend is used + if active_backend in ["module://matplotlib_inline.backend_inline"]: + self.BM.update(clear_snapshot=clear) else: - active_backend = plt.get_backend() - # print a snapshot to the active ipython cell in case the - # inline-backend is used - if active_backend in ["module://matplotlib_inline.backend_inline"]: - self.BM.update(clear_snapshot=clear) - else: - plt.show() + plt.show() def snapshot(self, *layer, transparent=False, clear=False): """ @@ -4222,16 +4222,17 @@ def _init_figure(self, ax=None, plot_crs=None, **kwargs): if self.parent._layout_editor is None: self.parent._layout_editor = LayoutEditor(self.parent, modifier="alt+l") - if newfig: - # we only need to call show if a new figure has been created! - if ( - # plt.isinteractive() or - plt.get_backend() - == "module://ipympl.backend_nbagg" - ): - # make sure to call show only if we use an interactive backend... - # or within the ipympl backend (otherwise it will block subsequent code!) - plt.show() + active_backend = plt.get_backend() + # we only need to call show if a new figure has been created! + if newfig and active_backend == "module://ipympl.backend_nbagg": + # make sure to call show only if we use an interactive backend... + # or within the ipympl backend (otherwise it will block subsequent code!) + plt.show() + + if active_backend == "module://matplotlib_inline.backend_inline": + # close the figure to avoid duplicated (empty) plots created + # by the inline-backend manager in jupyter notebooks + plt.close(self.f) def _get_ax_label(self): return "map" From 8a58f39b597d60d5d9e299193e2fa0a444f7f9fb Mon Sep 17 00:00:00 2001 From: Raphael Quast Date: Fri, 1 Dec 2023 14:45:52 +0100 Subject: [PATCH 13/20] don't turn off interactive backend with jupyter inline backend (it's no longer necessary) --- eomaps/eomaps.py | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/eomaps/eomaps.py b/eomaps/eomaps.py index 0c2a2bb05..8b783f47e 100644 --- a/eomaps/eomaps.py +++ b/eomaps/eomaps.py @@ -86,22 +86,8 @@ def _handle_backends(): active_backend = plt.get_backend() - if active_backend in ["module://matplotlib_inline.backend_inline"]: - plt.ioff() - - if not Maps._backend_warning_shown and not BlitManager._snapshot_on_update: - _log.info( - "EOmaps disables matplotlib's interactive mode (e.g. 'plt.ioff()') " - f"for the backend {plt.get_backend()}.\n" - "Call `m.show()`, `m.show_layer` or `m.snapshot()` to print a " - "static snapshot of the map to a Jupyter Notebook cell " - "(or an IPython console)!" - ) - - Maps._backend_warning_shown = True - # to avoid flickering in the layout editor in jupyter notebooks - elif active_backend in ["module://ipympl.backend_nbagg"]: + if active_backend in ["module://ipympl.backend_nbagg"]: plt.ioff() else: if Maps._use_interactive_mode is True: @@ -3391,7 +3377,6 @@ def show(self, clear=True): show_layer : Set the currently visible layer. """ - # if not plt.isinteractive(): try: __IPYTHON__ except NameError: From 5e64b30c95bffcfb8fd7a6c18ecbba33a6e8c94d Mon Sep 17 00:00:00 2001 From: Raphael Quast Date: Fri, 1 Dec 2023 16:12:12 +0100 Subject: [PATCH 14/20] fix re-fetching spines and backgrounds on `m.set_frame` --- eomaps/eomaps.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/eomaps/eomaps.py b/eomaps/eomaps.py index 8b783f47e..cd688241e 100644 --- a/eomaps/eomaps.py +++ b/eomaps/eomaps.py @@ -5638,11 +5638,8 @@ def set_frame(self, rounded=0, **kwargs): >>> path_effects=[pe.withStroke(linewidth=7, foreground="m")]) """ - self.redraw("__SPINES__") for key in ("fc", "facecolor"): - self.redraw("__BG__") - if key in kwargs: self.ax.patch.set_facecolor(kwargs.pop(key)) @@ -5705,3 +5702,6 @@ def cb(*args, **kwargs): self.BM._before_fetch_bg_actions.append(cb) self.ax._EOmaps_rounded_spine_attached = True + + self.redraw("__SPINES__") + self.redraw("__BG__") From 6d4c91d8c1be2fa0537527e9ccc7638dc4dbac76 Mon Sep 17 00:00:00 2001 From: Raphael Quast Date: Fri, 1 Dec 2023 16:22:50 +0100 Subject: [PATCH 15/20] fix transparent snapshot issues in jupyter notebooks with `pillow 10.0.0` --- eomaps/eomaps.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/eomaps/eomaps.py b/eomaps/eomaps.py index cd688241e..f8642b6a4 100644 --- a/eomaps/eomaps.py +++ b/eomaps/eomaps.py @@ -3462,10 +3462,18 @@ def snapshot(self, *layer, transparent=False, clear=False): else: sn = self._get_snapshot() try: - from IPython.display import display + from IPython.display import display, Image as IpyImage + from io import BytesIO + + # fix issues with PILLOW 10.0 and transparent snapshots + # in jupyter notebooks (TODO should be fixed in PILLOW 10.1.0) + # ...bug causes unwanted errors in _repr__jpeg_ for RGBA images + # the fix enforces png as format + temp = BytesIO() + Image.fromarray(sn, "RGBA").save(temp, format="png") display( - Image.fromarray(sn, "RGBA").convert("RGB"), + IpyImage(temp.getvalue()), display_id=True, clear=clear, ) From 2797ae4143f9a0ff1a2bee004f0c7d83e5b5b974 Mon Sep 17 00:00:00 2001 From: Raphael Quast Date: Fri, 1 Dec 2023 17:01:05 +0100 Subject: [PATCH 16/20] fix typo --- eomaps/helpers.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/eomaps/helpers.py b/eomaps/helpers.py index 61e7767c6..a0de3beac 100644 --- a/eomaps/helpers.py +++ b/eomaps/helpers.py @@ -625,11 +625,11 @@ def modifier_pressed(self, val): self.m.cb.execute_callbacks(not val) if self._modifier_pressed: - self._disable_draw = True - self._disable_update = True + self.m.BM._disable_draw = True + self.m.BM._disable_update = True else: - self._disable_draw = False - self._disable_update = False + self.m.BM._disable_draw = False + self.m.BM._disable_update = False @property def ms(self): From 1756286e9942238256fe3e17a419b53602e8c717 Mon Sep 17 00:00:00 2001 From: Raphael Quast Date: Fri, 1 Dec 2023 20:43:18 +0100 Subject: [PATCH 17/20] use logger for layout-editor warning --- eomaps/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eomaps/helpers.py b/eomaps/helpers.py index a0de3beac..3d7dd4076 100644 --- a/eomaps/helpers.py +++ b/eomaps/helpers.py @@ -1500,7 +1500,7 @@ def apply_layout(self, layout): # check if all relevant axes are specified in the layout valid_keys = set(self.get_layout()) if valid_keys != set(layout): - warnings.warn( + _log.warning( "EOmaps: The the layout does not match the expected structure! " "Layout might not be properly restored. " "Invalid or missing keys:\n" From eaeecf908862d92c0b337db8631d54ccf8e55b0c Mon Sep 17 00:00:00 2001 From: Raphael Quast Date: Sat, 2 Dec 2023 17:25:03 +0100 Subject: [PATCH 18/20] fix issues with RGB/RGBA composites --- eomaps/_data_manager.py | 12 +++++++++++- eomaps/eomaps.py | 4 +++- eomaps/shapes.py | 6 +++--- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/eomaps/_data_manager.py b/eomaps/_data_manager.py index 1a3f0cde5..61b32fd29 100644 --- a/eomaps/_data_manager.py +++ b/eomaps/_data_manager.py @@ -652,7 +652,12 @@ def _select_vals(self, val, qs, slices=None): else: val = np.asanyarray(val) - if len(val.shape) == 2 and qx is not None and qy is not None: + if ( + len(val.shape) == 2 + and qx is not None + and qy is not None + and slices is not None + ): (x0, x1, y0, y1) = slices ret = val[y0:y1, x0:x1] else: @@ -895,6 +900,11 @@ def get_props(self, *args, **kwargs): else: slices, blocksize = None, None + # remember last selection and slices (required in case explicit + # colors are provided since they must be selected accordingly) + self._last_qs = qs + self._last_slices = slices + self._current_data = dict( xorig=self._select_vals(self.xorig, qs, slices), yorig=self._select_vals(self.yorig, qs, slices), diff --git a/eomaps/eomaps.py b/eomaps/eomaps.py index f8642b6a4..6fa41ab43 100644 --- a/eomaps/eomaps.py +++ b/eomaps/eomaps.py @@ -4827,7 +4827,9 @@ def _plot_map( def _sel_c_transp(self, c): return self._data_manager._select_vals( - c.T if self._data_manager._z_transposed else c + c.T if self._data_manager._z_transposed else c, + qs=self._data_manager._last_qs, + slices=self._data_manager._last_slices, ) def _handle_explicit_colors(self, color): diff --git a/eomaps/shapes.py b/eomaps/shapes.py index 044d53c7f..bfe5640df 100644 --- a/eomaps/shapes.py +++ b/eomaps/shapes.py @@ -326,7 +326,6 @@ def _get_colors_and_array(kwargs, mask): # identify colors and the array # special treatment of array input to properly mask values array = kwargs.pop("array", None) - if array is not None: if mask is not None: array = array[mask] @@ -336,17 +335,18 @@ def _get_colors_and_array(kwargs, mask): color_vals = dict() for c_key in ["fc", "facecolor", "color"]: color = kwargs.pop(c_key, None) + if color is not None: # explicit treatment for recarrays (to avoid performance issues) # with matplotlib.colors.to_rgba_array() # (recarrays are used to convert 3/4 arrays into an rgb(a) array # in m._handle_explicit_colors() ) if isinstance(color, np.recarray): - color_vals[c_key] = color[mask].view( + color_vals[c_key] = color[mask.reshape(color.shape)].view( (float, len(color.dtype.names)) ) # .ravel() elif isinstance(color, np.ndarray): - color_vals[c_key] = color[mask] + color_vals[c_key] = color[mask.reshape(color.shape)] else: color_vals[c_key] = color From 140a7dc381c308f4071b02037a833118f185c02a Mon Sep 17 00:00:00 2001 From: Raphael Quast Date: Fri, 1 Dec 2023 19:06:51 +0100 Subject: [PATCH 19/20] allow passing kwargs to WebMap services with m.add_wms.get_service() --- eomaps/_webmap.py | 21 ++++++++++++--------- eomaps/webmap_containers.py | 13 +++++++++++-- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/eomaps/_webmap.py b/eomaps/_webmap.py index 8934b50da..3d7dccebe 100644 --- a/eomaps/_webmap.py +++ b/eomaps/_webmap.py @@ -584,12 +584,15 @@ def _do_add_layer(self, m, layer, **kwargs): class _WebServiceCollection: - def __init__(self, m, service_type="wmts", url=None): + def __init__(self, m, service_type="wmts", url=None, **kwargs): self._m = m self._service_type = service_type if url is not None: self._url = url + # additional kwargs that will be passed to owslib.WebMapService() + self._service_kwargs = kwargs.copy() + def __getitem__(self, key): return self.add_layer.__dict__[key] @@ -626,28 +629,28 @@ def findlayer(self, name): return [i for i in self.layers if name.lower() in i.lower()] @staticmethod - def _get_wmts(url): + def _get_wmts(url, **kwargs): # TODO expose useragent # lazy import used to avoid long import times from owslib.wmts import WebMapTileService - return WebMapTileService(url) + return WebMapTileService(url, **kwargs) @staticmethod - def _get_wms(url): + def _get_wms(url, **kwargs): # TODO expose useragent # lazy import used to avoid long import times from owslib.wms import WebMapService - return WebMapService(url) + return WebMapService(url, **kwargs) @property @lru_cache() def add_layer(self): if self._service_type == "wmts": - wmts = self._get_wmts(self._url) + wmts = self._get_wmts(self._url, **self._service_kwargs) layers = dict() for key in wmts.contents.keys(): layername = _sanitize(key) @@ -665,7 +668,7 @@ def add_layer(self): layers[layername] = wmtslayer elif self._service_type == "wms": - wms = self._get_wms(self._url) + wms = self._get_wms(self._url, **self._service_kwargs) layers = dict() for key in wms.contents.keys(): layername = _sanitize(key) @@ -821,14 +824,14 @@ def _fetch_layers(self): url = self._url if url is not None: if self._service_type == "wms": - wms = self._get_wms(url) + wms = self._get_wms(url, **self._service_kwargs) layer_names = list(wms.contents.keys()) for lname in layer_names: self._layers["layer_" + _sanitize(lname)] = _WMSLayer( self._m, wms, lname ) elif self._service_type == "wmts": - wmts = self._get_wmts(url) + wmts = self._get_wmts(url, **self._service_kwargs) layer_names = list(wmts.contents.keys()) for lname in layer_names: self._layers["layer_" + _sanitize(lname)] = _WMTSLayer( diff --git a/eomaps/webmap_containers.py b/eomaps/webmap_containers.py index 8162b2a5c..112970c4d 100644 --- a/eomaps/webmap_containers.py +++ b/eomaps/webmap_containers.py @@ -2422,7 +2422,9 @@ def Austria(self): WMS.__doc__ = type(self).Austria.__doc__ return WMS - def get_service(self, url, service_type="wms", rest_API=False, maxzoom=19): + def get_service( + self, url, service_type="wms", rest_API=False, maxzoom=19, **kwargs + ): """ Get a object that can be used to add WMS, WMTS or XYZ services based on a GetCapabilities-link or a link to a ArcGIS REST API @@ -2470,6 +2472,11 @@ def get_service(self, url, service_type="wms", rest_API=False, maxzoom=19): The maximum zoom-level available (to avoid http-request errors) for too high zoom levels. The default is 19. + kwargs : + Additional keyword arguments passed to `owslib.WebMapService()`. + (only relevant if type is "wms" or "wmts") + + For example: `version=1.3.0` Returns ------- @@ -2541,6 +2548,8 @@ def get_service(self, url, service_type="wms", rest_API=False, maxzoom=19): service_type=service_type, ) else: - service = _WebServiceCollection(self._m, service_type="wms", url=url) + service = _WebServiceCollection( + self._m, service_type="wms", url=url, **kwargs + ) return service From d2f3e79ff508d4bd7839f1233d18b64ef9286ad6 Mon Sep 17 00:00:00 2001 From: Raphael Date: Mon, 4 Dec 2023 09:52:01 +0100 Subject: [PATCH 20/20] improve handling of (transparent) snapshots in IPython/Jupyter Notebooks --- eomaps/eomaps.py | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/eomaps/eomaps.py b/eomaps/eomaps.py index 6fa41ab43..6d43f01ab 100644 --- a/eomaps/eomaps.py +++ b/eomaps/eomaps.py @@ -3462,21 +3462,13 @@ def snapshot(self, *layer, transparent=False, clear=False): else: sn = self._get_snapshot() try: - from IPython.display import display, Image as IpyImage - from io import BytesIO - - # fix issues with PILLOW 10.0 and transparent snapshots - # in jupyter notebooks (TODO should be fixed in PILLOW 10.1.0) - # ...bug causes unwanted errors in _repr__jpeg_ for RGBA images - # the fix enforces png as format - temp = BytesIO() - Image.fromarray(sn, "RGBA").save(temp, format="png") - - display( - IpyImage(temp.getvalue()), - display_id=True, - clear=clear, - ) + from IPython.display import display_png, clear_output + + if clear: + clear_output(wait=True) + # use display_png to avoid issues with transparent snapshots + display_png(Image.fromarray(sn, "RGBA"), raw=False) + except Exception: _log.exception( "Unable to display the snapshot... is the script "