diff --git a/docs/add_text.ipynb b/docs/add_text.ipynb
new file mode 100644
index 0000000..9748fdd
--- /dev/null
+++ b/docs/add_text.ipynb
@@ -0,0 +1,148 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Adding text on images"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import numpy as np\n",
+ "import stackview\n",
+ "from skimage.io import imread\n",
+ "from skimage.filters import gaussian\n",
+ "import matplotlib.pyplot as plt"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "image = imread('data/blobs.tif')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "We'll apply Gaussian blur with sigma values from 1 to 10 and store the results in lists."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Generated 10 blurred images\n",
+ "Sigma labels: ['sigma=1', 'sigma=2', 'sigma=3', 'sigma=4', 'sigma=5', 'sigma=6', 'sigma=7', 'sigma=8', 'sigma=9', 'sigma=10']\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Initialize lists to store results\n",
+ "blurred_images = []\n",
+ "sigma_labels = []\n",
+ "\n",
+ "# Apply Gaussian blur with sigma values from 1 to 10\n",
+ "for sigma in range(1, 11):\n",
+ " blurred_image = gaussian(image, sigma=sigma, preserve_range=True)\n",
+ " blurred_images.append(blurred_image)\n",
+ " sigma_labels.append(f\"sigma={sigma}\")\n",
+ "\n",
+ "print(f\"Generated {len(blurred_images)} blurred images\")\n",
+ "print(f\"Sigma labels: {sigma_labels}\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "First, we add text to images and show them afterwards."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "(254, 256)"
+ ]
+ },
+ "execution_count": 5,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "images = stackview.add_text(blurred_images, sigma_labels, position=\"bottom-left\")\n",
+ "images[0].shape"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "
"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "execution_count": 6,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "stackview.animate(images)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.11.13"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/setup.py b/setup.py
index e0bb8b7..81b650e 100644
--- a/setup.py
+++ b/setup.py
@@ -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",
@@ -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",
diff --git a/stackview/__init__.py b/stackview/__init__.py
index 8096842..9e0f879 100644
--- a/stackview/__init__.py
+++ b/stackview/__init__.py
@@ -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
@@ -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
diff --git a/stackview/_add_text.py b/stackview/_add_text.py
new file mode 100644
index 0000000..5fad893
--- /dev/null
+++ b/stackview/_add_text.py
@@ -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
\ No newline at end of file
diff --git a/stackview/_bia_bob_plugins.py b/stackview/_bia_bob_plugins.py
index 2aceb26..e7c00ea 100644
--- a/stackview/_bia_bob_plugins.py
+++ b/stackview/_bia_bob_plugins.py
@@ -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)
diff --git a/stackview/_plot_profile.py b/stackview/_plot_profile.py
new file mode 100644
index 0000000..4012951
--- /dev/null
+++ b/stackview/_plot_profile.py
@@ -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
\ No newline at end of file