Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 148 additions & 0 deletions docs/add_text.ipynb

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

setuptools.setup(
name="stackview",
version="0.19.1",
version="0.19.2",
license="BSD-3-Clause",
author="Robert Haase",
author_email="robert.haase@uni-leipzig.de",
Expand All @@ -15,7 +15,7 @@
url="https://github.com/haesleinhuepf/stackview/",
packages=setuptools.find_packages(),
include_package_data=True,
install_requires=["numpy!=1.19.4", "ipycanvas", "ipywidgets", "scikit-image", "ipyevents", "toolz", "matplotlib", "ipykernel", "imageio", "ipympl", "wordcloud"],
install_requires=["numpy!=1.19.4", "ipycanvas", "ipywidgets", "scikit-image", "ipyevents", "toolz", "matplotlib", "ipykernel", "imageio", "ipympl", "wordcloud", "scipy"],
python_requires='>=3.6',
classifiers=[
"Programming Language :: Python :: 3",
Expand Down
4 changes: 3 additions & 1 deletion stackview/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version__ = "0.19.1"
__version__ = "0.19.2"

from ._static_view import jupyter_displayable_output, insight
from ._utilities import merge_rgb
Expand Down Expand Up @@ -27,3 +27,5 @@
from ._histogram import histogram
from ._blend import blend
from ._zoom import zoom
from ._add_text import add_text
from ._plot_profile import plot_profile
114 changes: 114 additions & 0 deletions stackview/_add_text.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import numpy as np
from typing import List, Union

def add_text(
images: Union[List[np.ndarray], np.ndarray],
texts: List[str],
font_size: int = 16,
text_color: str = 'white',
bg_color: str = 'black',
position: str = 'top',
colormap: str = 'Greys_r'
) -> List[np.ndarray]:
"""
Burn text onto images using matplotlib.

Parameters:
-----------
images : list of np.ndarray or np.ndarray
List of images or n-dimensional array where first dimension is images
texts : list of str
List of text strings to burn onto each image
font_size : int
Font size for the text (default: 16)
text_color : str
Color of the text (default: 'white')
position : str
Text position: 'top', 'bottom', 'center', 'top-left', 'top-right',
'bottom-left', 'bottom-right', 'center-left', 'center-right' (default: 'top')
colormap : str
Colormap to use for displaying the image (default: 'Greys_r')

Returns:
--------
list of np.ndarray
List of images with text burned in
"""
import matplotlib.pyplot as plt
from matplotlib.backends.backend_agg import FigureCanvasAgg

# Convert to list if numpy array
if isinstance(images, np.ndarray):
images = [images[i] for i in range(images.shape[0])]

if len(images) != len(texts):
raise ValueError(f"Number of images ({len(images)}) must match number of texts ({len(texts)})")

result_images = []

for img, text in zip(images, texts):
# Create figure with exact image size
h, w = img.shape[:2]
dpi = 100
fig = plt.figure(figsize=(w/dpi, h/dpi), dpi=dpi)
ax = fig.add_axes([0, 0, 1, 1])
ax.axis('off')

# Display the image
ax.imshow(img, cmap=colormap)

# Determine text position
# Default to center
x_pos, y_pos = 0.5, 0.5
ha, va = 'center', 'center'

# Vertical positioning
if 'top' in position:
y_pos, va = 0.95, 'top'
elif 'bottom' in position:
y_pos, va = 0.05, 'bottom'

# Horizontal positioning
if 'left' in position:
x_pos, ha = 0.05, 'left'
elif 'right' in position:
x_pos, ha = 0.95, 'right'

# Add text with background
px = 1.0 / w
for delta_x, delta_y in [(-px, -px), (px, -px), (-px, px), (px, px)]:
ax.text(
x_pos+delta_x, y_pos+delta_y, text,
transform=ax.transAxes,
fontsize=font_size,
color=bg_color,
ha=ha,
va=va
)
ax.text(
x_pos, y_pos, text,
transform=ax.transAxes,
fontsize=font_size,
color=text_color,
ha=ha,
va=va
)

# Convert figure to numpy array
canvas = FigureCanvasAgg(fig)
canvas.draw()
buf = canvas.buffer_rgba()
result = np.asarray(buf)

# Convert RGBA to RGB if original was RGB
if img.ndim == 3 and img.shape[2] == 3:
result = result[:, :, :3]
# Convert back to single-channel if original was single-channel
elif img.ndim == 2 or img.shape[-1] not in [3, 4]:
result = result[:, :, 0]
result = result.astype(img.dtype)

result_images.append(result)
plt.close(fig)

return result_images
8 changes: 8 additions & 0 deletions stackview/_bia_bob_plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ def list_bia_bob_plugins():
* Allows switching between multiple images and displaying them with a slider.
stackview.switch(images:list)

* Add bounding boxes to an image.
bounding_boxes = [ {'x':5, 'y':5, 'width':10, 'height':15} ]
stackview.add_bounding_boxes(image, bounding_boxes)

* Add text to images at specified positions.
texts = ["Sample Text"] # List of texts for each image
stackview.add_text([image], texts, position='top-left')

* Allows plotting a scatterplot of a pandas dataframe while interactively choosing the columns and using a lasso tool for selecting data points
stackview.scatterplot(dataframe, column_x, column_y, selection_column)

Expand Down
73 changes: 73 additions & 0 deletions stackview/_plot_profile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
def plot_profile(image, point1, point2, linecolor='orange'):
"""
Extract intensity along a line between two points and create a combined figure.

Parameters:
-----------
image : numpy.ndarray
Input image (2D grayscale or 3D RGB)
point1 : tuple
First point coordinates (x, y)
point2 : tuple
Second point coordinates (x, y)
linecolor : str
Color for the line and points on the image and the intensity profile plot

Returns:
--------
numpy.ndarray
Combined figure as a numpy array (RGB image)
"""
import numpy as np
import matplotlib.pyplot as plt
from scipy import ndimage
from matplotlib.backends.backend_agg import FigureCanvasAgg

x1, y1 = point1
x2, y2 = point2

# Calculate number of points along the line
length = int(np.hypot(x2 - x1, y2 - y1))

# Generate coordinates along the line
x_coords = np.linspace(x1, x2, length)
y_coords = np.linspace(y1, y2, length)

# Extract intensity values along the line
if image.ndim == 2: # Grayscale image
intensities = ndimage.map_coordinates(image, [y_coords, x_coords], order=1)
else: # RGB image - use average of channels or first channel
intensities = ndimage.map_coordinates(
np.mean(image, axis=2), [y_coords, x_coords], order=1
)

# Create figure with two subplots
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))

# Plot the image with the line
if image.ndim == 2:
ax1.imshow(image, cmap='gray')
else:
ax1.imshow(image)

ax1.plot([x1, x2], [y1, y2], '-', color=linecolor, linewidth=2, label='Profile Line')
ax1.plot([x1, x2], [y1, y2], 'o', color=linecolor, markersize=8)
ax1.axis('off')

# Plot the intensity profile
ax2.plot(intensities, linewidth=2, color=linecolor)
ax2.set_xlabel('Distance along line (pixels)')
ax2.set_ylabel('Intensity')
ax2.grid(True, alpha=0.3)

plt.subplots_adjust(left=0, right=1, bottom=0, top=1, wspace=0.05)

# Convert figure to numpy array
canvas = FigureCanvasAgg(fig)
canvas.draw()
buf = canvas.buffer_rgba()
result = np.asarray(buf)

plt.close(fig) # Close figure to prevent displaying

return result