class_docstr: Implements a document builder
  
  Args:
  ----
      resolve_lines: whether words should be automatically grouped into lines
      resolve_blocks: whether lines should be automatically grouped into blocks
      paragraph_break: relative length of the minimum space separating paragraphs
      export_as_straight_boxes: if True, force straight boxes in the export (fit a rectangle
          box to all rotated boxes). Else, keep the boxes format unchanged, no matter what it is.
class_docstr_tok: ["Implements","a","document","builder","Args",":","--","--","resolve_lines",":","whether","words","should","be","automatically","grouped","into","lines","resolve_blocks",":","whether","lines","should","be","automatically","grouped","into","blocks","paragraph_break",":","relative","length","of","the","minimum","space","separating","paragraphs","export_as_straight_boxes",":","if","True",",","force","straight","boxes","in","the","export","(","fit","a","rectangle","box","to","all","rotated","boxes",")",".","Else",",","keep","the","boxes","format","unchanged",",","no","matter","what","it","is","."]
docstr: Order lines to group them in blocks
  
  Args:
  ----
      boxes: bounding boxes of shape (N, 4) or (N, 4, 2)
      lines: list of lines, each line is a list of idx
  
  Returns:
  -------
      nested list of box indices
docstr_tok: ["Order","lines","to","group","them","in","blocks","Args",":","--","--","boxes",":","bounding","boxes","of","shape","(","N",",","4",")","or","(","N",",","4",",","2",")","lines",":","list","of","lines",",","each","line","is","a","list","of","idx","Returns",":","--","--","--","-","nested","list","of","box","indices"]
code: def _resolve_blocks(
      boxes: np.ndarray, lines: List[List[int]]
  ) -> List[List[List[int]]]:
      """Order lines to group them in blocks
  
      Args:
      ----
          boxes: bounding boxes of shape (N, 4) or (N, 4, 2)
          lines: list of lines, each line is a list of idx
  
      Returns:
      -------
          nested list of box indices
      """
      # Resolve enclosing boxes of lines
      if boxes.ndim == 3:
          box_lines: np.ndarray = np.asarray(
              [
                  resolve_enclosing_rbbox([tuple(boxes[idx, :, :]) for idx in line])  # type: ignore[misc]
                  for line in lines
              ]
          )
      else:
          _box_lines = [
              resolve_enclosing_bbox(
                  [
                      (tuple(boxes[idx, :2]), tuple(boxes[idx, 2:]))
                      for idx in line
                  ]
              )
              for line in lines
          ]
          box_lines = np.asarray(
              [(x1, y1, x2, y2) for ((x1, y1), (x2, y2)) in _box_lines]
          )
  
      # Compute geometrical features of lines to clusterize
      # Clusterizing only with box centers yield to poor results for complex documents
      if boxes.ndim == 3:
          box_features: np.ndarray = np.stack(
              (
                  (box_lines[:, 0, 0] + box_lines[:, 0, 1]) / 2,
                  (box_lines[:, 0, 0] + box_lines[:, 2, 0]) / 2,
                  (box_lines[:, 0, 0] + box_lines[:, 2, 1]) / 2,
                  (box_lines[:, 0, 1] + box_lines[:, 2, 1]) / 2,
                  (box_lines[:, 0, 1] + box_lines[:, 2, 0]) / 2,
                  (box_lines[:, 2, 0] + box_lines[:, 2, 1]) / 2,
              ),
              axis=-1,
          )
      else:
          box_features = np.stack(
              (
                  (box_lines[:, 0] + box_lines[:, 3]) / 2,
                  (box_lines[:, 1] + box_lines[:, 2]) / 2,
                  (box_lines[:, 0] + box_lines[:, 2]) / 2,
                  (box_lines[:, 1] + box_lines[:, 3]) / 2,
                  box_lines[:, 0],
                  box_lines[:, 1],
              ),
              axis=-1,
          )
      # Compute clusters
      clusters = fclusterdata(
          box_features,
          t=0.1,
          depth=4,
          criterion="distance",
          metric="euclidean",
      )
  
      _blocks: Dict[int, List[int]] = {}
      # Form clusters
      for line_idx, cluster_idx in enumerate(clusters):
          if cluster_idx in _blocks.keys():
              _blocks[cluster_idx].append(line_idx)
          else:
              _blocks[cluster_idx] = [line_idx]
  
      # Retrieve word-box level to return a fully nested structure
      blocks = [
          [lines[idx] for idx in block] for block in _blocks.values()
      ]
  
      return blocks
  
file_path: doctr/doctr/models/builder.py
imports: from typing import Any, Dict, List, Optional, Tuple
  import numpy
  from scipy.cluster.hierarchy import fclusterdata
  from doctr.io.elements import Block, Document, KIEDocument, KIEPage, Line, Page, Prediction, Word
  from doctr.utils.geometry import estimate_page_angle, resolve_enclosing_bbox, resolve_enclosing_rbbox, rotate_boxes
  from doctr.utils.repr import NestedObject
label_desc: Use image node_id 5 for calling the DocumentBuilder obj's underlying member method code with example usage: obj._resolve_blocks(boxes, lines) and returns: blocks