Skip to content

Commit

Permalink
Merge pull request #2427 from bmorris3/cmap-bad-values
Browse files Browse the repository at this point in the history
Render nans as 'bad' in mpl cmaps when mode='colormap'
  • Loading branch information
astrofrog authored Aug 17, 2023
2 parents 1609de8 + 670aa34 commit 1a5c767
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 20 deletions.
18 changes: 12 additions & 6 deletions glue/viewers/image/composite_array.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,21 +137,29 @@ def __call__(self, bounds=None):
data = interval(array)
data = contrast_bias(data, out=data)
data = stretch(data, out=data)
data[np.isnan(data)] = 0

if self.mode == 'colormap':

# ensure "bad" values have the same alpha as the
# rest of the layer:
if hasattr(layer['cmap'], 'get_bad'):
bad_color = layer['cmap'].get_bad().tolist()[:3]
layer_cmap = layer['cmap'].with_extremes(
bad=bad_color + [layer['alpha']]
)
else:
layer_cmap = layer['cmap']

if img is None:
img = np.ones(data.shape + (4,))

# Compute colormapped image
plane = layer['cmap'](data)
plane = layer_cmap(data)

# Check what the smallest colormap alpha value for this layer is
# - if it is 1 then this colormap does not change transparency,
# and this allows us to speed things up a little.

if layer['cmap'](CMAP_SAMPLING)[:, 3].min() == 1:
if layer_cmap(CMAP_SAMPLING)[:, 3].min() == 1:

if layer['alpha'] == 1:
img[...] = 0
Expand All @@ -162,7 +170,6 @@ def __call__(self, bounds=None):
else:

# Use traditional alpha compositing

alpha_plane = layer['alpha'] * plane[:, :, 3]

plane[:, :, 0] = plane[:, :, 0] * alpha_plane
Expand All @@ -176,7 +183,6 @@ def __call__(self, bounds=None):
img[:, :, 3] = 1

else:

if img is None:
img = np.zeros(data.shape + (4,))

Expand Down
42 changes: 28 additions & 14 deletions glue/viewers/image/tests/test_composite_array.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,19 @@ def test_cmap_blending(self):
expected_a = np.array([[cm.Blues(1.), cm.Blues(0.5)],
[cm.Blues(0.), cm.Blues(0.)]])

expected_b = np.array([[cm.Reds(0.), cm.Reds(1.)],
[cm.Reds(0.), cm.Reds(0.)]])
bad_value = cm.Reds.with_extremes(
# set "bad" to the default color and alpha=1
bad=cm.Reds.get_bad().tolist()[:3] + [1.]
)(np.nan)
expected_b = np.array(
[[bad_value, cm.Reds(1.)],
[cm.Reds(0.), cm.Reds(0.)]]
)

# If both layers have alpha=1, the top layer should be the only one visible

assert_allclose(self.composite(bounds=self.default_bounds), expected_b)

# If the top layer has alpha=0, the bottom layer should be the only one visible

self.composite.set('b', alpha=0.)

assert_allclose(self.composite(bounds=self.default_bounds), expected_a)
Expand All @@ -96,30 +100,34 @@ def test_cmap_alphas(self):
cmap=cm.Blues, clim=(0, 2))

self.composite.set('b', zorder=1, visible=True, array=self.array2,
cmap=lambda x: cm.Reds(x, alpha=abs(np.nan_to_num(x))), clim=(0, 1))
cmap=lambda x: cm.Reds(x, alpha=abs(x)), clim=(0, 1))

# Determine expected result for each layer individually in the absence
# of transparency

expected_a = np.array([[cm.Blues(1.), cm.Blues(0.5)],
[cm.Blues(0.), cm.Blues(0.)]])

expected_b = np.array([[cm.Reds(0.), cm.Reds(1.)],
[cm.Reds(0.), cm.Reds(0.)]])
bad_value = cm.Reds.with_extremes(
# set "bad" to the default color and alpha=0
bad=cm.Reds.get_bad().tolist()[:3] + [0.]
)(np.nan)
expected_b_1 = np.array([[bad_value, cm.Reds(1.)],
[cm.Reds(0.), cm.Reds(0.)]])

# If the top layer has alpha=1 with a colormap alpha fading proportional to absval,
# it should be visible only at the nonzero value [0, 1]

assert_allclose(self.composite(bounds=self.default_bounds),
[[expected_a[0, 0], expected_b[0, 1]], expected_a[1]])
[[expected_a[0, 0], expected_b_1[0, 1]], expected_a[1]])

# For the same case with the top layer alpha=0.5 that value should become an equal
# blend of both layers again

self.composite.set('b', alpha=0.5)

assert_allclose(self.composite(bounds=self.default_bounds),
[[expected_a[0, 0], 0.5 * (expected_a[0, 1] + expected_b[0, 1])],
[[expected_a[0, 0], 0.5 * (expected_a[0, 1] + expected_b_1[0, 1])],
expected_a[1]])

# A third layer added at the bottom should not be visible in the output
Expand All @@ -129,21 +137,27 @@ def test_cmap_alphas(self):
cmap=cm.Greens, clim=(0, 2))

assert_allclose(self.composite(bounds=self.default_bounds),
[[expected_a[0, 0], 0.5 * (expected_a[0, 1] + expected_b[0, 1])],
[[expected_a[0, 0], 0.5 * (expected_a[0, 1] + expected_b_1[0, 1])],
expected_a[1]])

# For only the bottom layer having such colormap, the top layer should appear just the same

self.composite.set('a', alpha=1., cmap=lambda x: cm.Blues(x, alpha=abs(np.nan_to_num(x))))
self.composite.set('a', alpha=1., cmap=lambda x: cm.Blues(x, alpha=abs(x)))
self.composite.set('b', alpha=1., cmap=cm.Reds)

assert_allclose(self.composite(bounds=self.default_bounds), expected_b)
bad_value = cm.Reds.with_extremes(
# set "bad" to the default color and alpha=1
bad=cm.Reds.get_bad().tolist()[:3] + [1.]
)(np.nan)
expected_b_2 = np.array([[bad_value, cm.Reds(1.)],
[cm.Reds(0.), cm.Reds(0.)]])
assert_allclose(self.composite(bounds=self.default_bounds), expected_b_2)

# Settin the third layer on top with alpha=0 should not affect the appearance
# Setting the third layer on top with alpha=0 should not affect the appearance

self.composite.set('c', zorder=2, alpha=0.)

assert_allclose(self.composite(bounds=self.default_bounds), expected_b)
assert_allclose(self.composite(bounds=self.default_bounds), expected_b_2)

def test_color_blending(self):

Expand Down

0 comments on commit 1a5c767

Please sign in to comment.