from functools import lru_cache
import matplotlib.font_manager
import numpy as np
import PIL.Image
import PIL.ImageDraw
import PIL.ImageFont
from morphocut.core import Node, Output, RawOrVariable, ReturnOutputs
[docs]@lru_cache(128)
def draw_scalebar(
length_unit,
px_per_unit=1,
unit="px",
mode="L",
fg_color=0,
bg_color=255,
font_family="sans",
margin=10,
):
"""
Draw a scalebar image of specified length and units.
Args:
length_unit: The length of the scalebar in the specified unit.
px_per_unit: The ratio of pixels to units for the scalebar.
unit: The unit of length for the scalebar.
mode: The color mode to use for the PIL image.
fg_color: The color to use for the scalebar and text.
bg_color: The color to use for the background.
font_family: The font family to use for the scalebar text.
margin: The margin to use around the scalebar.
Returns:
A numpy array representing the image of the scalebar.
"""
length_px = round(length_unit * px_per_unit)
h = 32
w = length_px + 2 * margin
img = PIL.Image.new(mode, (w, h), bg_color)
font_fn = matplotlib.font_manager.FontManager().findfont(font_family)
fnt = PIL.ImageFont.truetype(font_fn, 12)
d = PIL.ImageDraw.Draw(img)
d.text((10, 5), f"{length_unit:.0f}{unit}", font=fnt, fill=fg_color)
d.line(
[
(margin, 28),
(margin, 25),
(margin + length_px, 25),
(margin + length_px, 28),
],
fill=fg_color,
)
return np.asarray(img)
[docs]@ReturnOutputs
@Output("image")
class DrawScalebar(Node):
"""
Append a scalebar to an image.
Args:
image: The image to append the scalebar to.
length_unit: The length of the scalebar in the specified unit.
px_per_unit: The ratio of pixels to units for the scalebar.
unit: The unit of length for the scalebar.
fg_color: The color to use for the scalebar and text.
bg_color: The color to use for the background.
font_family: The font family to use for the scalebar text.
margin: The margin to use around the scalebar.
"""
def __init__(
self,
image: RawOrVariable[np.ndarray],
length_unit,
px_per_unit=1,
unit="px",
fg_color=0,
bg_color=255,
font_family="sans",
margin=10,
):
super().__init__()
self.image = image
self.length_unit = length_unit
self.px_per_unit = px_per_unit
self.unit = unit
self.fg_color = fg_color
self.bg_color = bg_color
self.font_family = font_family
self.margin = margin
def transform(
self,
image: np.ndarray,
length_unit,
px_per_unit,
unit,
fg_color,
bg_color,
font_family,
margin,
):
# TODO: Convert colors (see image.py)
# Calculate an alpha-mask for the scalebar
scalebar = draw_scalebar(
length_unit=length_unit,
px_per_unit=px_per_unit,
unit=unit,
mode="F",
fg_color=1,
bg_color=0,
font_family=font_family,
margin=margin,
)
# Construct canvas that can contain the image and the scalebar
cheight = image.shape[0] + scalebar.shape[0]
cwidth = max(image.shape[1], scalebar.shape[1])
canvas = np.full(
(cheight, cwidth) + image.shape[2:], bg_color, dtype=image.dtype
)
# Paste image (centered)
offs = (cwidth - image.shape[1]) // 2
canvas[: image.shape[0], offs : offs + image.shape[1]] = image
# Paste scalebar (aligned left)
canvas[
image.shape[0] : image.shape[0] + scalebar.shape[0], : scalebar.shape[1]
] = (scalebar * fg_color) + (1 - scalebar) * bg_color
return canvas