class VGSLBlock(object):
class TorchVGSLModel(object):
    def __init__(self, block: str, layer: str, name: str, idx: int):
    def __str__(self):
    def name(self):
    def layer(self):
    def __init__(self, spec: str) -> None:
    def _parse(self, input: Tuple[int, int, int, int], blocks: Sequence[str], parallel=False) -> None:
    def append(self, idx: int, spec: str) -> None:
    def to(self, device: Union[str, torch.device]) -> None:
    def eval(self) -> None:
    def train(self) -> None:
    def set_num_threads(self, num: int) -> None:
    def load_pronn_model(cls, path: Union[str, pathlib.Path]):
    def load_clstm_model(cls, path: Union[str, pathlib.Path]):
    def load_model(cls, path: Union[str, pathlib.Path]):
        def _deserialize_layers(name, layer):
    def one_channel_mode(self):
    def one_channel_mode(self, val: str):
    def model_type(self):
    def model_type(self, val: str):
    def seg_type(self):
    def seg_type(self, val: str):
    def hyper_params(self, **kwargs):
    def hyper_params(self, val: Dict[str, Any]):
    def save_model(self, path: str):
            def _serialize_layer(net, input, net_builder):
    def add_codec(self, codec: PytorchCodec) -> None:
    def init_weights(self, idx: slice = slice(0, None)) -> None:
        def _wi(m):
    def resize_output(self, output_size: int, del_indices: Optional[Iterable] = None) -> None:
    def build_rnn(self,
                  input: Tuple[int, int, int, int],
                  blocks: List[str],
                  idx: int) -> Union[Tuple[None, None, None], Tuple[Tuple[int, int, int, int], str, Callable]]:
    def build_dropout(self,
                      input: Tuple[int, int, int, int],
                      blocks: List[str],
                      idx: int) -> Union[Tuple[None, None, None], Tuple[Tuple[int, int, int, int], str, Callable]]:
    def build_addition(self,
                       input: Tuple[int, int, int, int],
                       blocks: List[str],
                       idx: int) -> Union[Tuple[None, None, None], Tuple[Tuple[int, int, int, int], str, Callable]]:
    def build_identity(self,
                       input: Tuple[int, int, int, int],
                       blocks: List[str],
                       idx: int) -> Union[Tuple[None, None, None], Tuple[Tuple[int, int, int, int], str, Callable]]:
    def build_groupnorm(self,
                        input: Tuple[int, int, int, int],
                        blocks: List[str],
                        idx: int) -> Union[Tuple[None, None, None], Tuple[Tuple[int, int, int, int], str, Callable]]:
    def build_conv(self,
                   input: Tuple[int, int, int, int],
                   blocks: List[str],
                   idx: int) -> Union[Tuple[None, None, None], Tuple[Tuple[int, int, int, int], str, Callable]]:
    def build_maxpool(self,
                      input: Tuple[int, int, int, int],
                      blocks: List[str],
                      idx: int) -> Union[Tuple[None, None, None], Tuple[Tuple[int, int, int, int], str, Callable]]:
    def build_reshape(self,
                      input: Tuple[int, int, int, int],
                      blocks: List[str],
                      idx: int) -> Union[Tuple[None, None, None], Tuple[Tuple[int, int, int, int], str, Callable]]:
    def build_output(self,
                     input: Tuple[int, int, int, int],
                     blocks: List[str],
                     idx: int) -> Union[Tuple[None, None, None], Tuple[Tuple[int, int, int, int], str, Callable]]:
    def build_series(self,
                     input: Tuple[int, int, int, int],
                     blocks: List[str],
                     idx: int) -> Union[Tuple[None, None, None], Tuple[Tuple[int, int, int, int], str, Callable]]:
    def build_parallel(self,
                       input: Tuple[int, int, int, int],
                       blocks: List[str],
                       idx: int) -> Union[Tuple[None, None, None], Tuple[Tuple[int, int, int, int], str, Callable]]: class ImageInputTransforms(transforms.Compose):
class InfiniteDataLoader(DataLoader):
class ArrowIPCRecognitionDataset(Dataset):
class PolygonGTDataset(Dataset):
class GroundTruthDataset(Dataset):
class BaselineSet(Dataset):
    def __init__(self,
                 batch: int,
                 height: int,
                 width: int,
                 channels: int,
                 pad: int,
                 valid_norm: bool = True,
                 force_binarization: bool = False) -> None:
    def _create_transforms(self) -> None:
    def batch(self) -> int:
    def batch(self, batch: int) -> None:
    def channels(self) -> int:
    def channels(self, channels: int) -> None:
    def height(self) -> int:
    def height(self, height: int) -> None:
    def width(self) -> int:
    def width(self, width: int) -> None:
    def mode(self) -> str:
    def scale(self) -> Tuple[int, int]:
    def scale(self, scale: Tuple[int, int]) -> None:
    def pad(self) -> int:
    def pad(self, pad: int) -> None:
    def valid_norm(self) -> bool:
    def valid_norm(self, valid_norm: bool) -> None:
    def centerline_norm(self) -> bool:
    def force_binarization(self) -> bool:
    def force_binarization(self, force_binarization: bool) -> None:
def _fast_levenshtein(seq1: Sequence[Any], seq2: Sequence[Any]) -> int:
def global_align(seq1: Sequence[Any], seq2: Sequence[Any]) -> Tuple[int, List[str], List[str]]:
def compute_confusions(algn1: Sequence[str], algn2: Sequence[str]):
    def _get_script(c):
def compute_error(model: TorchSeqRecognizer, batch: Dict[str, torch.Tensor]) -> Tuple[int, int]:
def collate_sequences(batch):
    def __init__(self, *args, **kwargs):
    def __iter__(self):
    def __next__(self):
    def __init__(self,
                 normalization: Optional[str] = None,
                 whitespace_normalization: bool = True,
                 reorder: Union[bool, str] = True,
                 im_transforms: Callable[[Any], torch.Tensor] = transforms.Compose([]),
                 augmentation: bool = False,
                 split_filter: Optional[str] = None) -> None:
    def add(self, file: Union[str, pathlib.Path]) -> None:
    def encode(self, codec: Optional[PytorchCodec] = None) -> None:
    def no_encode(self) -> None:
    def __getitem__(self, index: int) -> Tuple[torch.Tensor, torch.Tensor]:
    def __len__(self) -> int:
    def __init__(self,
                 normalization: Optional[str] = None,
                 whitespace_normalization: bool = True,
                 reorder: Union[bool, str] = True,
                 im_transforms: Callable[[Any], torch.Tensor] = transforms.Compose([]),
                 augmentation: bool = False) -> None:
    def add(self, *args, **kwargs):
    def parse(self,
              image: Union[str, Image.Image],
              text: str,
              baseline: List[Tuple[int, int]],
              boundary: List[Tuple[int, int]],
              *args,
              **kwargs):
    def encode(self, codec: Optional[PytorchCodec] = None) -> None:
    def no_encode(self) -> None:
    def __getitem__(self, index: int) -> Tuple[torch.Tensor, torch.Tensor]:
    def __len__(self) -> int:
    def __init__(self, split: Callable[[str], str] = F_t.default_split,
                 suffix: str = '.gt.txt',
                 normalization: Optional[str] = None,
                 whitespace_normalization: bool = True,
                 reorder: Union[bool, str] = True,
                 im_transforms: Callable[[Any], torch.Tensor] = transforms.Compose([]),
                 augmentation: bool = False) -> None:
    def add(self, *args, **kwargs) -> None:
    def parse(self, image: Union[str, Image.Image], *args, **kwargs) -> Dict:
    def encode(self, codec: Optional[PytorchCodec] = None) -> None:
    def no_encode(self) -> None:
    def __getitem__(self, index: int) -> Tuple[torch.Tensor, torch.Tensor]:
    def __len__(self) -> int:
    def __init__(self, imgs: Sequence[str] = None,
                 suffix: str = '.path',
                 line_width: int = 4,
                 im_transforms: Callable[[Any], torch.Tensor] = transforms.Compose([]),
                 mode: str = 'path',
                 augmentation: bool = False,
                 valid_baselines: Sequence[str] = None,
                 merge_baselines: Dict[str, Sequence[str]] = None,
                 valid_regions: Sequence[str] = None,
                 merge_regions: Dict[str, Sequence[str]] = None):
    def add(self,
            image: Union[str, Image.Image],
            baselines: List[List[List[Tuple[int, int]]]] = None,
            regions: Dict[str, List[List[Tuple[int, int]]]] = None,
            *args,
            **kwargs):
    def __getitem__(self, idx):
    def _get_ortho_line(lineseg, point, line_width, offset):
    def transform(self, image, target):
    def __len__(self): def is_bitonal(im: Union[Image.Image, torch.Tensor]) -> bool:
    """
    Tests a PIL image or torch tensor for bitonality.

    Args:
        im: Image to test

    Returns:
        True if the image contains only two different color values. False
        otherwise.
    """
    if isinstance(im, Image.Image):
        return im.getcolors(2) is not None and len(im.getcolors(2)) == 2
    elif isinstance(im, torch.Tensor):
        return len(im.int().unique()) == 2 def get_im_str(im: Image.Image) -> str:
    return im.filename if hasattr(im, 'filename') else str(im) class KrakenInputException(Exception):

    def __init__(self, message=None):
        Exception.__init__(self, message) class KrakenInvalidModelException(Exception):

    def __init__(self, message=None):
        Exception.__init__(self, message) def polygonal_reading_order(lines: Sequence[Tuple[List[Tuple[int, int]], List[Tuple[int, int]]]],
                            text_direction: str = 'lr',
                            regions: Optional[Sequence[List[Tuple[int, int]]]] = None) -> Sequence[Tuple[List[Tuple[int, int]], List[Tuple[int, int]]]]:
    """
    Given a list of baselines and regions, calculates the correct reading order
    and applies it to the input.

    Args:
        lines (Sequence): List of tuples containing the baseline and its
                          polygonization.
        regions (Sequence): List of region polygons.
        text_direction (str): Set principal text direction for column ordering.
                              Can be 'lr' or 'rl'

    Returns:
        A reordered input.
    """
    bounds = []
    if regions is not None:
        r = [geom.Polygon(reg) for reg in regions]
    else:
        r = []
    region_lines = [[] for _ in range(len(r))]
    indizes = {}
    for line_idx, line in enumerate(lines):
        s_line = geom.LineString(line[1])
        in_region = False
        for idx, reg in enumerate(r):
            if is_in_region(s_line, reg):
                region_lines[idx].append((line_idx, (slice(s_line.bounds[1], s_line.bounds[3]),
                                                     slice(s_line.bounds[0], s_line.bounds[2]))))
                in_region = True
                break
        if not in_region:
            bounds.append((slice(s_line.bounds[1], s_line.bounds[3]),
                           slice(s_line.bounds[0], s_line.bounds[2])))
            indizes[line_idx] = ('line', line)
    # order everything in regions
    intra_region_order = [[] for _ in range(len(r))]
    for idx, reg in enumerate(r):
        if len(region_lines[idx]) > 0:
            order = reading_order([x[1] for x in region_lines[idx]], text_direction)
            lsort = topsort(order)
            intra_region_order[idx] = [region_lines[idx][i][0] for i in lsort]
            reg = reg.bounds
            bounds.append((slice(reg[1], reg[3]), slice(reg[0], reg[2])))
            indizes[line_idx+idx+1] = ('region', idx)
    # order unassigned lines and regions
    order = reading_order(bounds, text_direction)
    lsort = topsort(order)
    sidz = sorted(indizes.keys())
    lsort = [sidz[i] for i in lsort]
    ordered_lines = []
    for i in lsort:
        if indizes[i][0] == 'line':
            ordered_lines.append(indizes[i][1])
        else:
            ordered_lines.extend(lines[x] for x in intra_region_order[indizes[i][1]])
    return ordered_lines def vectorize_lines(im: np.ndarray, threshold: float = 0.17, min_length=5):
    """
    Vectorizes lines from a binarized array.

    Args:
        im (np.ndarray): Array of shape (3, H, W) with the first dimension
                         being probabilities for (start_separators,
                         end_separators, baseline).
        threshold (float): Threshold for baseline blob detection.
        min_length (int): Minimal length of output baselines.

    Returns:
        [[x0, y0, ... xn, yn], [xm, ym, ..., xk, yk], ... ]
        A list of lists containing the points of all baseline polylines.
    """
    # split into baseline and separator map
    st_map = im[0]
    end_map = im[1]
    bl_map = im[2]
    bl_map = filters.sato(bl_map, black_ridges=False, mode='constant')
    bin_bl_map = bl_map > threshold
    # skeletonize
    line_skel = skeletonize(bin_bl_map)
    # find end points
    kernel = np.array([[1, 1, 1], [1, 10, 1], [1, 1, 1]])
    line_extrema = np.transpose(np.where((convolve2d(line_skel, kernel, mode='same') == 11) * line_skel))

    mcp = LineMCP(~line_skel)
    try:
        mcp.find_costs(line_extrema)
    except ValueError:
        return []

    lines = [approximate_polygon(line, 3).tolist() for line in mcp.get_connections()]
    # extend baselines to blob boundary
    lines = _extend_boundaries(lines, bin_bl_map)

    # orient lines
    f_st_map = maximum_filter(st_map, size=20)
    f_end_map = maximum_filter(end_map, size=20)

    oriented_lines = []
    for bl in lines:
        l_end = tuple(bl[0])
        r_end = tuple(bl[-1])
        if f_st_map[l_end] - f_end_map[l_end] > 0.2 and f_st_map[r_end] - f_end_map[r_end] < -0.2:
            pass
        elif f_st_map[l_end] - f_end_map[l_end] < -0.2 and f_st_map[r_end] - f_end_map[r_end] > 0.2:
            bl = bl[::-1]
        else:
            logger.debug('Insufficient marker confidences in output. Defaulting to upright line.')
        if bl[0][1] > bl[-1][1]:
            bl = bl[::-1]
        if geom.LineString(bl).length >= min_length:
            oriented_lines.append([x[::-1] for x in bl])
    return oriented_lines def vectorize_regions(im: np.ndarray, threshold: float = 0.5):
    """
    Vectorizes lines from a binarized array.

    Args:
        im (np.ndarray): Array of shape (H, W) with the first dimension
                         being a probability distribution over the region.
        threshold (float): Threshold for binarization

    Returns:
        [[x0, y0, ... xn, yn], [xm, ym, ..., xk, yk], ... ]
        A list of lists containing the region polygons.
    """
    bin = im > threshold
    labelled = label(bin)
    boundaries = []
    for x in regionprops(labelled):
        boundary = boundary_tracing(x)
        if len(boundary) > 2:
            boundaries.append(geom.Polygon(boundary).simplify(10))
    # merge regions that overlap after simplification
    boundaries = unary_union(boundaries)
    if boundaries.type == 'Polygon':
        boundaries = [boundaries.boundary]
    else:
        boundaries = [x.boundary for x in unary_union(boundaries)]
    return [np.array(x.coords, dtype=np.uint)[:, [1, 0]].tolist() for x in boundaries] def scale_polygonal_lines(lines: Sequence[Tuple[List, List]], scale: Union[float, Tuple[float, float]]) -> Sequence[Tuple[List, List]]:
    """
    Scales baselines/polygon coordinates by a certain factor.

    Args:
        lines (Sequence): List of tuples containing the baseline and it's
                          polygonization.
        scale (float or tuple of floats): Scaling factor
    """
    if isinstance(scale, float):
        scale = (scale, scale)
    scaled_lines = []
    for line in lines:
        bl, pl = line
        scaled_lines.append(((np.array(bl) * scale).astype('int').tolist(),
                             (np.array(pl) * scale).astype('int').tolist()))
    return scaled_lines def calculate_polygonal_environment(im: PIL.Image.Image = None,
                                    baselines: Sequence[Sequence[Tuple[int, int]]] = None,
                                    suppl_obj: Sequence[Sequence[Tuple[int, int]]] = None,
                                    im_feats: np.ndarray = None,
                                    scale: Tuple[int, int] = None,
                                    topline: bool = False):
    """
    Given a list of baselines and an input image, calculates a polygonal
    environment around each baseline.

    Args:
        im (PIL.Image): grayscale input image (mode 'L')
        baselines (sequence): List of lists containing a single baseline per
                              entry.
        suppl_obj (sequence): List of lists containing additional polylines
                              that should be considered hard boundaries for
                              polygonizaton purposes. Can be used to prevent
                              polygonization into non-text areas such as
                              illustrations or to compute the polygonization of
                              a subset of the lines in an image.
        im_feats (numpy.array): An optional precomputed seamcarve energy map.
                                Overrides data in `im`. The default map is
                                `gaussian_filter(sobel(im), 2)`.
        scale (tuple): A 2-tuple (h, w) containing optional scale factors of
                       the input. Values of 0 are used for aspect-preserving
                       scaling. `None` skips input scaling.
        topline (bool): Switch to change default baseline location for offset
                        calculation purposes. If set to False, baselines are
                        assumed to be on the bottom of the text line and will
                        be offset upwards, if set to True, baselines are on the
                        top and will be offset downwards. If set to None, no
                        offset will be applied.
    Returns:
        List of lists of coordinates. If no polygonization could be compute for
        a baseline `None` is returned instead.
    """
    if scale is not None and (scale[0] > 0 or scale[1] > 0):
        w, h = im.size
        oh, ow = scale
        if oh == 0:
            oh = int(h * ow/w)
        elif ow == 0:
            ow = int(w * oh/h)
        im = im.resize((ow, oh))
        scale = np.array((ow/w, oh/h))
        # rescale baselines
        baselines = [(np.array(bl) * scale).astype('int').tolist() for bl in baselines]
        # rescale suppl_obj
        if suppl_obj is not None:
            suppl_obj = [(np.array(bl) * scale).astype('int').tolist() for bl in suppl_obj]

    if im_feats is None:
        bounds = np.array(im.size, dtype=float) - 1
        im = np.array(im.convert('L'))
        # compute image gradient
        im_feats = gaussian_filter(sobel(im), 0.5)
    else:
        bounds = np.array(im_feats.shape[::-1], dtype=float) - 1

    polygons = []
    if suppl_obj is None:
        suppl_obj = []

    for idx, line in enumerate(baselines):
        try:
            end_points = (line[0], line[-1])
            line = geom.LineString(line)
            offset = default_specs.SEGMENTATION_HYPER_PARAMS['line_width'] if topline is not None else 0
            offset_line = line.parallel_offset(offset, side='left' if topline else 'right')
            line = np.array(line, dtype=float)
            offset_line = np.array(offset_line, dtype=float)

            # parallel_offset on the right reverses the coordinate order
            if not topline:
                offset_line = offset_line[::-1]
            # calculate magnitude-weighted average direction vector
            lengths = np.linalg.norm(np.diff(line.T), axis=0)
            p_dir = np.mean(np.diff(line.T) * lengths/lengths.sum(), axis=1)
            p_dir = (p_dir.T / np.sqrt(np.sum(p_dir**2, axis=-1)))

            env_up, env_bottom = _calc_roi(line, bounds, baselines[:idx] + baselines[idx+1:], suppl_obj, p_dir)

            polygons.append(_extract_patch(env_up,
                                           env_bottom,
                                           line.astype('int'),
                                           offset_line.astype('int'),
                                           end_points,
                                           p_dir,
                                           topline,
                                           offset,
                                           im_feats))
        except Exception as e:
            logger.warning(f'Polygonizer failed on line {idx}: {e}')
            polygons.append(None)

    if scale is not None:
        polygons = [(np.array(pol)/scale).astype('uint').tolist() if pol is not None else None for pol in polygons]
    return polygons def scale_regions(regions: Sequence[Tuple[List[int], List[int]]],
                  scale: Union[float, Tuple[float, float]]) -> Sequence[Tuple[List, List]]:
    """
    Scales baselines/polygon coordinates by a certain factor.

    Args:
        lines (Sequence): List of tuples containing the baseline and it's
                          polygonization.
        scale (float or tuple of floats): Scaling factor
    """
    if isinstance(scale, float):
        scale = (scale, scale)
    scaled_regions = []
    for region in regions:
        scaled_regions.append((np.array(region) * scale).astype('uint').tolist())
    return scaled_regions
import PIL
import torch
import logging
import numpy as np
import pkg_resources
import shapely.geometry as geom
import torch.nn.functional as F
import torchvision.transforms as tf
from typing import Optional, Dict, Callable, Union, List, Any, Tuple
from scipy.ndimage.filters import gaussian_filter
from skimage.filters import sobel
from kraken.lib import vgsl, dataset
from kraken.lib.util import is_bitonal, get_im_str
from kraken.lib.exceptions import KrakenInputException, KrakenInvalidModelException
from kraken.lib.segmentation import (polygonal_reading_order,
                                     vectorize_lines, vectorize_regions,
                                     scale_polygonal_lines,
                                     calculate_polygonal_environment,
                                     scale_regions)
    logger.debug('Upsampling network output')
    o = F.interpolate(o, size=scal_im.shape)
    o = o.squeeze().cpu().numpy()
    scale = np.divide(im.size, o.shape[:0:-1])
    bounding_regions = model.user_metadata['bounding_regions'] if 'bounding_regions' in model.user_metadata else None
    return {'heatmap': o,
            'cls_map': model.user_metadata['class_mapping'],
            'bounding_regions': bounding_regions,
            'scale': scale,
            'scal_im': scal_im}


def vec_regions(heatmap: torch.Tensor, cls_map: Dict, scale: float, **kwargs) -> Dict[str, List[List[Tuple[int, int]]]]:
    """
    Computes regions from a stack of heatmaps, a class mapping, and scaling
    factor.

    Args:
        heatmap: A stack of heatmaps of shape `NxHxW` output from the network.
        cls_map: Dictionary mapping string identifiers to indices on the stack
                 of heatmaps.
        scale: Scaling factor between heatmap and unscaled input image.

    Returns:
        A dictionary containing a key for each region type with a list of
        regions inside.
    """
    logger.info('Vectorizing regions')
    regions = {}
    for region_type, idx in cls_map['regions'].items():
        logger.debug(f'Vectorizing regions of type {region_type}')
        regions[region_type] = vectorize_regions(heatmap[idx])
    for reg_id, regs in regions.items():
        regions[reg_id] = scale_regions(regs, scale)
    return regions


def vec_lines(heatmap: torch.Tensor,
              cls_map: Dict[str, Dict[str, int]],
              scale: float,
              text_direction: str = 'horizontal-lr',
              reading_order_fn: Callable = polygonal_reading_order,
              regions: List[np.ndarray] = None,
              scal_im: np.ndarray = None,
              suppl_obj: List[np.ndarray] = None,
              topline: Optional[bool] = False,
              **kwargs) -> List[Dict[str, Any]]:
    r"""
    Computes lines from a stack of heatmaps, a class mapping, and scaling
    factor.

    Args:
        heatmap: A stack of heatmaps of shape `NxHxW` output from the network.
        cls_map: Dictionary mapping string identifiers to indices on the stack
                 of heatmaps.
        scale: Scaling factor between heatmap and unscaled input image.
        text_direction: Text directions used as hints in the reading order
                        algorithm.
        reading_order_fn: Reading order calculation function.
        regions: Regions to be used as boundaries during polygonization and
                 atomic blocks during reading order determination for lines
                 contained within.
        scal_im: A numpy array containing the scaled input image.
        suppl_obj: Supplementary objects which are used as boundaries during
                   polygonization.
        topline: True for a topline, False for baseline, or None for a
                 centerline.

    Returns:
        A list of dictionaries containing the baselines, bounding polygons, and
        line type in reading order:

        .. code-block::
           :force:

            [{'script': '$baseline_type', baseline': [[x0, y0], [x1, y1], ..., [x_n, y_n]], 'boundary': [[x0, y0, x1, y1], ... [x_m, y_m]]},
             {'script': '$baseline_type', baseline': [[x0, ...]], 'boundary': [[x0, ...]]},
             {'script': '$baseline_type', baseline': [[x0, ...]], 'boundary': [[x0, ...]]},
             ...
            ]
    """
    st_sep = cls_map['aux']['_start_separator']
    end_sep = cls_map['aux']['_end_separator']

    logger.info('Vectorizing baselines')
    baselines = []
    for bl_type, idx in cls_map['baselines'].items():
        logger.debug(f'Vectorizing lines of type {bl_type}')
        baselines.extend([(bl_type, x) for x in vectorize_lines(heatmap[(st_sep, end_sep, idx), :, :])])
    logger.debug('Polygonizing lines')

    im_feats = gaussian_filter(sobel(scal_im), 0.5)

    lines = []
    reg_pols = [geom.Polygon(x) for x in regions]
    for bl_idx in range(len(baselines)):
        bl = baselines[bl_idx]
        mid_point = geom.LineString(bl[1]).interpolate(0.5, normalized=True)

        suppl_obj = [x[1] for x in baselines[:bl_idx] + baselines[bl_idx+1:]]
        for reg_idx, reg_pol in enumerate(reg_pols):
            if reg_pol.contains(mid_point):
                suppl_obj.append(regions[reg_idx])

        pol = calculate_polygonal_environment(baselines=[bl[1]], im_feats=im_feats, suppl_obj=suppl_obj, topline=topline)
        if pol[0] is not None:
            lines.append((bl[0], bl[1], pol[0]))

    logger.debug('Scaling vectorized lines')
    sc = scale_polygonal_lines([x[1:] for x in lines], scale)
    lines = list(zip([x[0] for x in lines], [x[0] for x in sc], [x[1] for x in sc]))
    logger.debug('Reordering baselines')
    lines = reading_order_fn(lines=lines, regions=regions, text_direction=text_direction[-2:])
    return [{'tags': {'type': bl_type}, 'baseline': bl, 'boundary': pl} for bl_type, bl, pl in lines]


def segment(im: PIL.Image.Image,
            text_direction: str = 'horizontal-lr',
            mask: Optional[np.ndarray] = None,
            reading_order_fn: Callable = polygonal_reading_order,

            model: Union[List[vgsl.TorchVGSLModel], vgsl.TorchVGSLModel] = None,