Skip to content

Commit

Permalink
Merge 6b5b1be into 6c91f8a
Browse files Browse the repository at this point in the history
  • Loading branch information
alex-l-kong committed Dec 3, 2020
2 parents 6c91f8a + 6b5b1be commit b49cc96
Show file tree
Hide file tree
Showing 7 changed files with 305 additions and 186 deletions.
7 changes: 4 additions & 3 deletions ark/utils/io_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,10 @@ def extract_delimited_names(names, delimiter='_', delimiter_optional=True):
"""For a given list of names, extract the delimited prefix
Examples (if delimiter='_'):
- 'fov1' becomes 'fov1'
- 'fov2_part1' becomes 'fov2'
- 'fov3_part1_part2' becomes 'fov3'
- 'fov1' becomes 'fov1'
- 'fov2_part1' becomes 'fov2'
- 'fov3_part1_part2' becomes 'fov3'
Args:
names (list):
Expand Down
1 change: 1 addition & 0 deletions ark/utils/notebooks_test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ def save_seg_labels(tb, delimiter='_feature_0', xr_dim_name='compartments',
# now overlay data_xr
tb.execute_cell('load_summed')
tb.execute_cell('overlay_mask')
tb.execute_cell('save_mask')


def create_exp_mat(tb, is_mibitiff=False, batch_size=5):
Expand Down
166 changes: 83 additions & 83 deletions ark/utils/plot_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,103 +11,103 @@
from skimage.exposure import rescale_intensity


# plotting functions
def tif_overlay_preprocess(segmentation_labels, plotting_tif):
"""Validates plotting_tif and preprocesses it accordingly
def plot_overlay(predicted_contour, plotting_tif, alternate_contour=None, path=None, show=False):
Args:
segmentation_labels (numpy.ndarray):
2D numpy array of labeled cell objects
plotting_tif (numpy.ndarray):
2D or 3D numpy array of imaging signal
Returns:
numpy.ndarray:
The preprocessed image
"""

if len(plotting_tif.shape) == 2:
if plotting_tif.shape != segmentation_labels.shape:
raise ValueError("plotting_tif and segmentation_labels array dimensions not equal.")
else:
# convert RGB image with the blue channel containing the plotting tif data
formatted_tif = np.zeros((plotting_tif.shape[0], plotting_tif.shape[1], 3),
dtype=plotting_tif.dtype)
formatted_tif[..., 2] = plotting_tif
elif len(plotting_tif.shape) == 3:
# can only support up to 3 channels
if plotting_tif.shape[2] > 3:
raise ValueError("max 3 channels of overlay supported, got {}".
format(plotting_tif.shape))

# set first n channels (in reverse order) of formatted_tif to plotting_tif
# (n = num channels in plotting_tif)
formatted_tif = np.zeros((plotting_tif.shape[0], plotting_tif.shape[1], 3),
dtype=plotting_tif.dtype)
formatted_tif[..., :plotting_tif.shape[2]] = plotting_tif
formatted_tif = np.flip(formatted_tif, axis=2)
else:
raise ValueError("plotting tif must be 2D or 3D array, got {}".
format(plotting_tif.shape))

return formatted_tif


def create_overlay(segmentation_labels, plotting_tif, alternate_segmentation=None):
"""Take in labeled contour data, along with optional mibi tif and second contour,
and overlay them for comparison"
Plots the outline(s) of the mask(s) as well as intensity from plotting tif. Predicted
contours are plotted in red, while alternate contours are plotted in white. Plot is
saved as a TIF file in provided file path, if specified.
Generates the outline(s) of the mask(s) as well as intensity from plotting tif. Predicted
contours are colored red, while alternate contours are colored white.
Args:
predicted_contour (numpy.ndarray):
segmentation_labels (numpy.ndarray):
2D numpy array of labeled cell objects
plotting_tif (numpy.ndarray):
2D or 3D numpy array of imaging signal
alternate_contour (numpy.ndarray):
alternate_segmentation (numpy.ndarray):
2D numpy array of labeled cell objects
path (str):
path to save the resulting image
show (bool):
whether or not to show plot
"""
if plotting_tif is None:
# will just plot the outlines
pass
else:
if len(plotting_tif.shape) == 2:
if plotting_tif.shape != predicted_contour.shape:
raise ValueError("plotting_tif and predicted_contour array dimensions not equal.")
else:
# convert RGB image with same data across all three channels
plotting_tif = np.stack((plotting_tif, plotting_tif, plotting_tif), axis=2)
elif len(plotting_tif.shape) == 3:
blank_channel = np.zeros(plotting_tif.shape[:2] + (1,), dtype=plotting_tif.dtype)
if plotting_tif.shape[2] == 1:
# pad two empty channels
plotting_tif = np.concatenate((plotting_tif, blank_channel, blank_channel), axis=2)
elif plotting_tif.shape[2] == 2:
# pad one empty channel
plotting_tif = np.concatenate((plotting_tif, blank_channel), axis=2)
elif plotting_tif.shape[2] == 3:
# don't need to do anything
pass
else:
raise ValueError("only 3 channels of overlay supported, got {}".
format(plotting_tif.shape))
else:
raise ValueError("plotting tif must be 2D or 3D array, got {}".
format(plotting_tif.shape))
Returns:
numpy.ndarray:
The image with the channel overlay
"""

if path is not None:
if os.path.exists(os.path.split(path)[0]) is False:
raise ValueError("File path does not exist.")
plotting_tif = tif_overlay_preprocess(segmentation_labels, plotting_tif)

# define borders of cells in mask
predicted_contour_mask = find_boundaries(predicted_contour,
predicted_contour_mask = find_boundaries(segmentation_labels,
connectivity=1, mode='inner').astype(np.uint8)
predicted_contour_mask[predicted_contour_mask > 0] = 255

if plotting_tif is None:
# will just save the contour mask
io.imsave(path, predicted_contour_mask)
else:
# rescale each channel to go from 0 to 255
rescaled = np.zeros(plotting_tif.shape, dtype='uint8')

for idx in range(plotting_tif.shape[2]):
if np.max(plotting_tif[:, :, idx]) == 0:
# don't need to rescale this channel
pass
else:
percentiles = np.percentile(plotting_tif[:, :, idx][plotting_tif[:, :, idx] > 0],
[5, 95])
rescaled_intensity = rescale_intensity(plotting_tif[:, :, idx],
in_range=(percentiles[0], percentiles[1]),
out_range='uint8')
rescaled[:, :, idx] = rescaled_intensity

# overlay first contour on all three RGB, to have it show up as white border
rescaled[predicted_contour_mask > 0, :] = 255

# overlay second contour as red outline if present
if alternate_contour is not None:

if predicted_contour.shape != alternate_contour.shape:
raise ValueError("predicted_contour and alternate_"
"contour array dimensions not equal.")

# define borders of cell in mask
alternate_contour_mask = find_boundaries(alternate_contour, connectivity=1,
mode='inner').astype(np.uint8)
rescaled[alternate_contour_mask > 0, 0] = 255
rescaled[alternate_contour_mask > 0, 1:] = 0

# save as TIF if path supplied, otherwise display on screen
if path is not None:
io.imsave(path, rescaled)
if show:
io.imshow(rescaled)
# rescale each channel to go from 0 to 255
rescaled = np.zeros(plotting_tif.shape, dtype='uint8')

for idx in range(plotting_tif.shape[2]):
if np.max(plotting_tif[:, :, idx]) == 0:
# don't need to rescale this channel
pass
else:
percentiles = np.percentile(plotting_tif[:, :, idx][plotting_tif[:, :, idx] > 0],
[5, 95])
rescaled_intensity = rescale_intensity(plotting_tif[:, :, idx],
in_range=(percentiles[0], percentiles[1]),
out_range='uint8')
rescaled[:, :, idx] = rescaled_intensity

# overlay first contour on all three RGB, to have it show up as white border
rescaled[predicted_contour_mask > 0, :] = 255

# overlay second contour as red outline if present
if alternate_segmentation is not None:

if segmentation_labels.shape != alternate_segmentation.shape:
raise ValueError("segmentation_labels and alternate_"
"segmentation array dimensions not equal.")

# define borders of cell in mask
alternate_contour_mask = find_boundaries(alternate_segmentation, connectivity=1,
mode='inner').astype(np.uint8)
rescaled[alternate_contour_mask > 0, 0] = 255
rescaled[alternate_contour_mask > 0, 1:] = 0

return rescaled
98 changes: 82 additions & 16 deletions ark/utils/plot_utils_test.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import tempfile
import os
import pytest
import tempfile

import numpy as np

Expand Down Expand Up @@ -28,21 +29,86 @@ def _generate_image_data(img_dims):
return np.random.randint(low=0, high=100, size=img_dims)


def test_plot_overlay():
def test_tif_overlay_preprocess():
example_labels = _generate_segmentation_labels((1024, 1024))
example_images = _generate_image_data((1024, 1024, 3))

# 2-D tests
# dimensions are not the same for 2-D example_images
with pytest.raises(ValueError):
plot_utils.tif_overlay_preprocess(segmentation_labels=example_labels[:100, :100],
plotting_tif=example_images[..., 0])

plotting_tif = plot_utils.tif_overlay_preprocess(segmentation_labels=example_labels,
plotting_tif=example_images[..., 0])

# assert the channels all contain the same data
assert np.all(plotting_tif[:, :, 0] == 0)
assert np.all(plotting_tif[:, :, 1] == 0)
assert np.all(plotting_tif[:, :, 2] == example_images[..., 0])

# 3-D tests
# test for third dimension == 1
plotting_tif = plot_utils.tif_overlay_preprocess(segmentation_labels=example_labels,
plotting_tif=example_images[..., 0:1])

assert np.all(plotting_tif[..., 0] == 0)
assert np.all(plotting_tif[..., 1] == 0)
assert np.all(plotting_tif[..., 2] == example_images[..., 0])

# test for third dimension == 2
plotting_tif = plot_utils.tif_overlay_preprocess(segmentation_labels=example_labels,
plotting_tif=example_images[..., 0:2])

assert np.all(plotting_tif[..., 0] == 0)
assert np.all(plotting_tif[..., 1] == example_images[..., 1])
assert np.all(plotting_tif[..., 2] == example_images[..., 0])

# test for third dimension == 3
plotting_tif = plot_utils.tif_overlay_preprocess(segmentation_labels=example_labels,
plotting_tif=example_images)

assert np.all(plotting_tif[..., 0] == example_images[..., 2])
assert np.all(plotting_tif[..., 1] == example_images[..., 1])
assert np.all(plotting_tif[..., 2] == example_images[..., 0])

# test for third dimension == anything else
with pytest.raises(ValueError):
# add another layer to the last dimension
blank_channel = np.zeros(example_images.shape[:2] + (1,), dtype=example_images.dtype)
bad_example_images = np.concatenate((example_images, blank_channel), axis=2)

plot_utils.tif_overlay_preprocess(segmentation_labels=example_labels,
plotting_tif=bad_example_images)

# n-D test (n > 3)
with pytest.raises(ValueError):
# add a fourth dimension
plot_utils.tif_overlay_preprocess(segmentation_labels=example_labels,
plotting_tif=np.expand_dims(example_images, axis=0))


def test_create_overlay():
example_labels = _generate_segmentation_labels((1024, 1024))
alternate_labels = _generate_segmentation_labels((1024, 1024))
example_images = _generate_image_data((1024, 1024, 3))

with tempfile.TemporaryDirectory() as temp_dir:
# save with both tif and labels
plot_utils.plot_overlay(predicted_contour=example_labels, plotting_tif=example_images,
alternate_contour=None,
path=os.path.join(temp_dir, "example_plot1.tiff"))
# save with just labels
plot_utils.plot_overlay(predicted_contour=example_labels, plotting_tif=None,
alternate_contour=None,
path=os.path.join(temp_dir, "example_plot2.tiff"))

# save with two sets of labels
plot_utils.plot_overlay(predicted_contour=example_labels, plotting_tif=example_images,
alternate_contour=example_labels,
path=os.path.join(temp_dir, "example_plot3.tiff"))
# base test: just contour and tif provided
contour_mask = plot_utils.create_overlay(segmentation_labels=example_labels,
plotting_tif=example_images,
alternate_segmentation=None)

assert contour_mask.shape == (1024, 1024, 3)

# test with an alternate contour
contour_mask = plot_utils.create_overlay(segmentation_labels=example_labels,
plotting_tif=example_images,
alternate_segmentation=alternate_labels)

assert contour_mask.shape == (1024, 1024, 3)

# invalid alternate contour provided
with pytest.raises(ValueError):
plot_utils.create_overlay(segmentation_labels=example_labels,
plotting_tif=example_images,
alternate_segmentation=alternate_labels[:100, :100])
Loading

0 comments on commit b49cc96

Please sign in to comment.