Source code for xiuminglib.vis.text

from os.path import dirname
from io import BytesIO
import numpy as np
from PIL import Image, ImageDraw, ImageFont

from .. import const
from ..os import makedirs, open_file
from ..imprt import preset_import

from ..log import get_logger
logger = get_logger()


[docs]def put_text( img, text, label_top_left_xy=None, font_size=None, font_color=(1, 0, 0), font_ttf=None): r"""Puts text on image. Args: img (numpy.ndarray): Should be of type ``uint`` and of shape H-by-W (grayscale) or H-by-W-by-3 (RGB). text (str): Text to be written on the image. label_top_left_xy (tuple(int), optional): The XY coordinate of the label's top left corner. font_size (int, optional): Font size. font_color (tuple(float), optional): Font RGB, normalized to :math:`[0,1]`. Defaults to red. font_ttf (str, optional): Path to the .ttf font file. Defaults to Arial. Returns: numpy.ndarray: The modified image with text. """ assert np.issubdtype(img.dtype, np.integer) and ( not np.issubdtype(img.dtype, np.signedinteger)), \ "Input image must be `uint` (i.e., an actual image)" if font_size is None: font_size = int(0.1 * img.shape[0]) # Font if font_ttf is None: font = ImageFont.truetype(const.Path.open_sans_regular, font_size) else: with open_file(font_ttf, 'rb') as h: font_bytes = BytesIO(h.read()) font = ImageFont.truetype(font_bytes, font_size) if label_top_left_xy is None: label_top_left_xy = (int(0.1 * img.shape[1]), int(0.05 * img.shape[0])) dtype_max = np.iinfo(img.dtype).max color = tuple(int(x * dtype_max) for x in font_color) img = Image.fromarray(img) img = img.convert('RGB') # to avoid errors due to RGB text on grayscale ImageDraw.Draw(img).text(label_top_left_xy, text, fill=color, font=font) img = np.array(img) return img
[docs]def text_as_image( text, imsize=256, thickness=2, dtype='uint8', outpath=None, quiet=False): """Rasterizes a text string into an image. The text will be drawn in white to the center of a black canvas. Text size gets automatically figured out based on the provided thickness and image size. Args: text (str): Text to be drawn. imsize (float or tuple(float), optional): Output image height and width. thickness (float, optional): Text thickness. dtype (str, optional): Image type. outpath (str, optional): Where to dump the result to. ``None`` means returning instead of writing it. quiet (bool, optional): Whether to refrain from logging. Effective only when ``outpath`` is not ``None``. Returns or Writes - An image of the text. """ cv2 = preset_import('cv2', assert_success=True) if isinstance(imsize, int): imsize = (imsize, imsize) assert isinstance(imsize, tuple), \ "`imsize` must be an int or a 2-tuple of ints" # Unimportant constants not exposed to the user font_face = cv2.FONT_HERSHEY_SIMPLEX base_bgr = (0, 0, 0) # black text_bgr = (1, 1, 1) # white # Base canvas dtype_max = np.iinfo(dtype).max im = np.tile(base_bgr, imsize + (1,)).astype(dtype) * dtype_max # Figure out the correct font scale font_scale = 1 / 128 # real small while True: (text_width, text_height), bl_y = cv2.getTextSize( text, font_face, font_scale, thickness) if bl_y + text_height >= imsize[0] or text_width >= imsize[1]: # Undo the destroying step before breaking font_scale /= 2 (text_width, text_height), bl_y = cv2.getTextSize( text, font_face, font_scale, thickness) break font_scale *= 2 # Such that the text is at the center bottom_left_corner = ( (imsize[1] - text_width) // 2, (imsize[0] - text_height) // 2 + text_height) cv2.putText( im, text, bottom_left_corner, font_face, font_scale, [x * dtype_max for x in text_bgr], thickness) if outpath is None: return im # Write outdir = dirname(outpath) makedirs(outdir) cv2.imwrite(outpath, im) if not quiet: logger.info("Text rasterized into image to:\n%s", outpath)