Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 21
Fix norm behavior, add tests#419
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
Merged
Uh oh!
There was an error while loading. Please reload this page.
Merged
Changes from all commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
d26829d
use datashader span arg, fix image single channel with norm rendering
760f5c7
update upload-artifact action to v4
9123cb1
update incorrect image
744c446
fix datashader behavior for points and shapes when clip=False
64857a9
add/modify tests for norm
8644a1c
Merge branch 'main' into bugfix/372-norm-tests-and-clipping
Sonja-Stockhaus ee0efda
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] f36ea13
update test images
623e899
fix datashader vmin==vmax behavior and add tests
2cecbf1
add new test images
c45c082
incorporate review feedback
698af16
datashader min_alpha max. 254
d9ed0fb
Applied prettier changes
timtreis 5871bd2
modified prettier version
timtreis c86cba6
attempt to copy prettier from spatialdata
timtreis 8c6cd1c
Merge branch 'main' into bugfix/372-norm-tests-and-clipping
timtreis e351353
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] e5e6a39
add explanatory comments
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Jump to file
Failed to load files.
Loading
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -37,6 +37,7 @@ | ||
| _ax_show_and_transform, | ||
| _create_image_from_datashader_result, | ||
| _datashader_aggregate_with_function, | ||
| _datashader_map_aggregate_to_color, | ||
| _datshader_get_how_kw_for_spread, | ||
| _decorate_axs, | ||
| _get_collection_shape, | ||
| @@ -229,18 +230,20 @@ def _render_shapes( | ||
| line_width=render_params.outline_params.linewidth, | ||
| ) | ||
| ds_span = None | ||
| if norm.vmin is not None or norm.vmax is not None: | ||
| norm.vmin = np.min(agg) if norm.vmin is None else norm.vmin | ||
| norm.vmax = np.max(agg) if norm.vmax is None else norm.vmax | ||
| norm.clip = True # NOTE: mpl currently behaves like clip is always True | ||
| ds_span = [norm.vmin, norm.vmax] | ||
| if norm.vmin == norm.vmax: | ||
| # data is mapped to 0 | ||
| agg = agg - agg | ||
| else: | ||
| agg = (agg - norm.vmin) / (norm.vmax - norm.vmin) | ||
| # edge case, value vmin is rendered as the middle of the cmap | ||
| ds_span = [0, 1] | ||
| if norm.clip: | ||
| agg = np.maximum(agg, 0) | ||
| agg = np.minimum(agg, 1) | ||
| agg = (agg - agg) + 0.5 | ||
| else: | ||
| agg = agg.where((agg >= norm.vmin) | (np.isnan(agg)), other=-1) | ||
| agg = agg.where((agg <= norm.vmin) | (np.isnan(agg)), other=2) | ||
| agg = agg.where((agg != norm.vmin) | (np.isnan(agg)), other=0.5) | ||
| color_key = ( | ||
| [x[:-2] for x in color_vector.categories.values] | ||
| @@ -256,13 +259,12 @@ def _render_shapes( | ||
| if isinstance(ds_cmap, str) and ds_cmap[0] == "#": | ||
| ds_cmap = ds_cmap[:-2] | ||
| ds_result = ds.tf.shade( | ||
| ds_result = _datashader_map_aggregate_to_color( | ||
| agg, | ||
| cmap=ds_cmap, | ||
| color_key=color_key, | ||
| min_alpha=np.min([254, render_params.fill_alpha * 255]), | ||
| how="linear", | ||
| ) | ||
| ) # prevent min_alpha == 255, bc that led to fully colored test plots instead of just colored points/shapes | ||
| elif aggregate_with_reduction is not None: # to shut up mypy | ||
| ds_cmap = render_params.cmap_params.cmap | ||
| # in case all elements have the same value X: we render them using cmap(0.0), | ||
| @@ -272,12 +274,13 @@ def _render_shapes( | ||
| ds_cmap = matplotlib.colors.to_hex(render_params.cmap_params.cmap(0.0), keep_alpha=False) | ||
| aggregate_with_reduction = (aggregate_with_reduction[0], aggregate_with_reduction[0] + 1) | ||
| ds_result = ds.tf.shade( | ||
| ds_result = _datashader_map_aggregate_to_color( | ||
| agg, | ||
| cmap=ds_cmap, | ||
| how="linear", | ||
| min_alpha=np.min([254, render_params.fill_alpha * 255]), | ||
| ) | ||
| span=ds_span, | ||
| clip=norm.clip, | ||
| ) # prevent min_alpha == 255, bc that led to fully colored test plots instead of just colored points/shapes | ||
| # shade outlines if needed | ||
| outline_color = render_params.outline_params.outline_color | ||
| @@ -294,7 +297,7 @@ def _render_shapes( | ||
| cmap=outline_color, | ||
| min_alpha=np.min([254, render_params.outline_alpha * 255]), | ||
| how="linear", | ||
| ) | ||
| ) # prevent min_alpha == 255, bc that led to fully colored test plots instead of just colored points/shapes | ||
| rgba_image, trans_data = _create_image_from_datashader_result(ds_result, factor, ax) | ||
| _cax = _ax_show_and_transform( | ||
| @@ -322,8 +325,10 @@ def _render_shapes( | ||
| vmin = aggregate_with_reduction[0].values if norm.vmin is None else norm.vmin | ||
| vmax = aggregate_with_reduction[1].values if norm.vmin is None else norm.vmax | ||
| if (norm.vmin is not None or norm.vmax is not None) and norm.vmin == norm.vmax: | ||
| vmin = norm.vmin | ||
| vmax = norm.vmin + 1 | ||
| # value (vmin=vmax) is placed in the middle of the colorbar so that we can distinguish it from over and | ||
| # under values in case clip=True or clip=False with cmap(under)=cmap(0) & cmap(over)=cmap(1) | ||
| vmin = norm.vmin - 0.5 | ||
| vmax = norm.vmin + 0.5 | ||
| cax = ScalarMappable( | ||
| norm=matplotlib.colors.Normalize(vmin=vmin, vmax=vmax), | ||
| cmap=render_params.cmap_params.cmap, | ||
| @@ -586,18 +591,21 @@ def _render_points( | ||
| else: | ||
| agg = cvs.points(transformed_element, "x", "y", agg=ds.count()) | ||
| ds_span = None | ||
| if norm.vmin is not None or norm.vmax is not None: | ||
| norm.vmin = np.min(agg) if norm.vmin is None else norm.vmin | ||
| norm.vmax = np.max(agg) if norm.vmax is None else norm.vmax | ||
| norm.clip = True # NOTE: mpl currently behaves like clip is always True | ||
| ds_span = [norm.vmin, norm.vmax] | ||
| if norm.vmin == norm.vmax: | ||
| # data is mapped to 0 | ||
| agg = agg - agg | ||
| else: | ||
| agg = (agg - norm.vmin) / (norm.vmax - norm.vmin) | ||
| ds_span = [0, 1] | ||
| if norm.clip: | ||
| agg = np.maximum(agg, 0) | ||
| agg = np.minimum(agg, 1) | ||
| # all data is mapped to 0.5 | ||
| agg = (agg - agg) + 0.5 | ||
| else: | ||
| # values equal to norm.vmin are mapped to 0.5, the rest to -1 or 2 | ||
| agg = agg.where((agg >= norm.vmin) | (np.isnan(agg)), other=-1) | ||
| agg = agg.where((agg <= norm.vmin) | (np.isnan(agg)), other=2) | ||
| agg = agg.where((agg != norm.vmin) | (np.isnan(agg)), other=0.5) | ||
| color_key = ( | ||
| list(color_vector.categories.values) | ||
| @@ -615,13 +623,12 @@ def _render_points( | ||
| color_vector = np.asarray([x[:-2] for x in color_vector]) | ||
| if color_by_categorical or col_for_color is None: | ||
| ds_result = ds.tf.shade( | ||
| ds_result = _datashader_map_aggregate_to_color( | ||
| ds.tf.spread(agg, px=px), | ||
| cmap=color_vector[0], | ||
| color_key=color_key, | ||
| min_alpha=np.min([254, render_params.alpha * 255]), | ||
| how="linear", | ||
| ) | ||
| ) # prevent min_alpha == 255, bc that led to fully colored test plots instead of just colored points/shapes | ||
| else: | ||
| spread_how = _datshader_get_how_kw_for_spread(render_params.ds_reduction) | ||
| agg = ds.tf.spread(agg, px=px, how=spread_how) | ||
| @@ -631,15 +638,17 @@ def _render_points( | ||
| # in case all elements have the same value X: we render them using cmap(0.0), | ||
| # using an artificial "span" of [X, X + 1] for the color bar | ||
| # else: all elements would get alpha=0 and the color bar would have a weird range | ||
| if aggregate_with_reduction[0] == aggregate_with_reduction[1]: | ||
| if aggregate_with_reduction[0] == aggregate_with_reduction[1] and (ds_span is None or ds_span != [0, 1]): | ||
| ds_cmap = matplotlib.colors.to_hex(render_params.cmap_params.cmap(0.0), keep_alpha=False) | ||
| aggregate_with_reduction = (aggregate_with_reduction[0], aggregate_with_reduction[0] + 1) | ||
| ds_result = ds.tf.shade( | ||
| ds_result = _datashader_map_aggregate_to_color( | ||
| agg, | ||
| cmap=ds_cmap, | ||
| how="linear", | ||
| ) | ||
| span=ds_span, | ||
| clip=norm.clip, | ||
| min_alpha=np.min([254, render_params.alpha * 255]), | ||
| ) # prevent min_alpha == 255, bc that led to fully colored test plots instead of just colored points/shapes | ||
| rgba_image, trans_data = _create_image_from_datashader_result(ds_result, factor, ax) | ||
| _ax_show_and_transform( | ||
| @@ -656,8 +665,10 @@ def _render_points( | ||
| vmin = aggregate_with_reduction[0].values if norm.vmin is None else norm.vmin | ||
| vmax = aggregate_with_reduction[1].values if norm.vmax is None else norm.vmax | ||
| if (norm.vmin is not None or norm.vmax is not None) and norm.vmin == norm.vmax: | ||
| vmin = norm.vmin | ||
| vmax = norm.vmin + 1 | ||
| # value (vmin=vmax) is placed in the middle of the colorbar so that we can distinguish it from over and | ||
| # under values in case clip=True or clip=False with cmap(under)=cmap(0) & cmap(over)=cmap(1) | ||
| vmin = norm.vmin - 0.5 | ||
| vmax = norm.vmin + 0.5 | ||
| cax = ScalarMappable( | ||
| norm=matplotlib.colors.Normalize(vmin=vmin, vmax=vmax), | ||
| cmap=render_params.cmap_params.cmap, | ||
| @@ -723,7 +734,6 @@ def _render_images( | ||
| legend_params: LegendParams, | ||
| rasterize: bool, | ||
| ) -> None: | ||
| sdata_filt = sdata.filter_by_coordinate_system( | ||
| coordinate_system=coordinate_system, | ||
| filter_tables=False, | ||
| @@ -781,9 +791,6 @@ def _render_images( | ||
| if n_channels == 1 and not isinstance(render_params.cmap_params, list): | ||
| layer = img.sel(c=channels[0]).squeeze() if isinstance(channels[0], str) else img.isel(c=channels[0]).squeeze() | ||
| if render_params.cmap_params.norm: # type: ignore[attr-defined] | ||
| layer = render_params.cmap_params.norm(layer) # type: ignore[attr-defined] | ||
| cmap = ( | ||
| _get_linear_colormap(palette, "k")[0] | ||
| if isinstance(palette, list) and all(isinstance(p, str) for p in palette) | ||
| @@ -794,7 +801,10 @@ def _render_images( | ||
| cmap._init() | ||
| cmap._lut[:, -1] = render_params.alpha | ||
| _ax_show_and_transform(layer, trans_data, ax, cmap=cmap, zorder=render_params.zorder) | ||
| # norm needs to be passed directly to ax.imshow(). If we normalize before, that method would always clip. | ||
| _ax_show_and_transform( | ||
| layer, trans_data, ax, cmap=cmap, zorder=render_params.zorder, norm=render_params.cmap_params.norm | ||
| ) | ||
Sonja-Stockhaus marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| if legend_params.colorbar: | ||
| sm = plt.cm.ScalarMappable(cmap=cmap, norm=render_params.cmap_params.norm) | ||
Oops, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.