Source code for pycif.plugins.datastreams.fluxes.dummy_txt.make.fromtxt
import numpy as np
import xarray as xr
from PIL import Image, ImageDraw, ImageFont
[docs]
def makefromtext(flx_txt, dom_xsize, dom_ysize,
file_font="/usr/share/fonts/dejavu/DejaVuSans-Bold.ttf"):
"""Render a text string onto a domain-sized grid as a synthetic flux field.
Enumerates possible line-splits of ``flx_txt`` (see :func:`split_text`)
and, for each, the largest font size that fits within
``dom_xsize``/``dom_ysize``; picks the split/font-size combination that
maximizes the rendered text area (with fewest lines and most balanced
line lengths as tie-breakers), draws it onto a blank canvas, and returns
the result as a grayscale-normalized array usable as a flux field
spelling out the text.
Args:
flx_txt (str): the text to render; an empty/whitespace-only string
produces an all-zero field.
dom_xsize (int): width of the target grid (number of longitude
cells).
dom_ysize (int): height of the target grid (number of latitude
cells).
file_font (str): path to a TrueType font file used to render the
text.
Returns:
np.ndarray: a 2D array of shape ``(dom_ysize, dom_xsize)`` with
values in ``[0, 1]`` (1 where the text is drawn, 0 on the
background).
"""
# Split the text
splits = split_text(flx_txt)
# If no text return zeros
if flx_txt.strip() == "":
return np.zeros((dom_ysize, dom_xsize))
# Loop over all possible combinations of newlines
areas = []
text_widths = []
text_heights = []
valid_splits = []
for split in splits:
width = max(list(map(len, split)))
size = min(dom_xsize // width, dom_ysize // (1 + len(split)))
size += 3 * size // 4
pil_font = ImageFont.truetype(file_font, size=size, encoding="unic")
area = 0
widths = []
heights = []
for s in split:
w, h = pil_font.getsize(s)
widths.append(w)
heights.append(h)
area += w * h
valid_splits.append(split)
areas.append(area)
text_widths.append(widths)
text_heights.append(heights)
# create a blank canvas with extra space between lines
canvas = Image.new("RGB", [dom_xsize, dom_ysize], (255, 255, 255))
draw = ImageDraw.Draw(canvas)
# Get the one with maximum occupation of space with minimum number of lines
imax = np.where(np.array(areas) >= 0.9 * np.max(areas))[0]
stds = [np.std(list(map(len, valid_splits[i]))) for i in imax]
iimax = np.where(np.array(stds) == np.min(stds))[0]
imax = [imax[i] for i in iimax]
lens = [len(valid_splits[i]) for i in iimax]
iimax = np.where(np.array(lens) == np.min(lens))[0]
imax = [imax[i] for i in iimax]
imax = imax[0]
text_width = text_widths[imax]
text_height = text_heights[imax]
split = valid_splits[imax]
# draw the text onto the canvas
offset = (
(dom_xsize - max(text_width)) // 2,
(dom_ysize - sum(text_height)) // 2,
)
white = "#000000"
width = offset[0] + max(text_width) // 2
y_text = offset[1]
for s, w, h in zip(split, text_width, text_height):
draw.text((width - (w // 2), y_text), s, font=pil_font, fill=white)
y_text += h
# Convert the canvas into an array with values in [0, 1]
flx = (255 - np.asarray(canvas)) / 255.0
return flx.mean(axis=2)
[docs]
def split_text(txt):
"""Return all possible splits for a given text
Args:
txt (str): the text to split
Returns:
list(str)
Notes: This function is not optimized and can take a long time for string
of more than a few words
"""
# If empty text
if txt == "":
return [txt]
split = txt.split()
# Recursively loop over substring of the text
if len(split) == 1:
return [[txt]]
elif len(split) == 2:
return [[txt], split]
else:
prev = [[split[0]] + b for b in split_text(" ".join(txt.split()[1:]))]
post = [
b + [split[-1]] for b in split_text(" ".join(txt.split()[:-1]))
]
return [[txt]] + prev + post