code: def graph_params_from_definition_string(s: str) -> List[LayerParams]:
      layers = []
      cnn_matcher = re.compile(
          r"^([\d]+)(:([\d]+)x([\d]+))?(:([\d]+)x([\d]+))?$"
      )
      db_matcher = re.compile(
          r"^([\d]+):([\d]+)(:([\d]+)(x([\d]+))?)?$"
      )
      concat_matcher = re.compile(r"^([\-\d]+):([\-\d]+)$")
      pool_matcher = re.compile(
          r"^([\d]+)(x([\d]+))?(:([\d]+)x([\d]+))?$"
      )
      str_params = s.split(",")
      lstm_appeared = False
      for param in str_params:
          label, value = tuple(param.split("="))
          if label == "dropout":
              layers.append(DropoutLayerParams(rate=float(value)))
          elif label == "lstm":
              lstm_appeared = True
              layers.append(BiLSTMLayerParams(hidden_nodes=int(value)))
          elif label == "concat":
              if lstm_appeared:
                  raise Exception(
                      "LSTM layers must be placed proceeding to CNN/Pool"
                  )
  
              match = concat_matcher.match(value)
              if match is None:
                  raise Exception(
                      "Concat structure needs: concat=[index0]:[index1] but got concat={}".format(
                          value
                      )
                  )
  
              match = match.groups()
              layers.append(
                  ConcatLayerParams(
                      concat_indices=list(map(int, match))
                  )
              )
          elif label == "db":
              if lstm_appeared:
                  raise Exception(
                      "LSTM layers must be placed proceeding to CNN/Pool"
                  )
  
              match = db_matcher.match(value)
              if match is None:
                  raise Exception(
                      "Dilated block structure needs: db=[filters]:[depth>0]:[h]x[w]"
                  )
  
              match = match.groups()
              kernel_size = [3, 3]
              if match[2] is not None:
                  kernel_size = [int(match[3])] * 2
              if match[4] is not None:
                  kernel_size = [int(match[3]), int(match[5])]
  
              layers.append(
                  DilatedBlockLayerParams(
                      filters=int(match[0]),
                      dilated_depth=int(match[1]),
                      kernel_size=IntVec2D(*kernel_size),
                      strides=IntVec2D(1, 1),
                  )
              )
          elif label in {"cnn", "conv", "conv2d"}:
              if lstm_appeared:
                  raise Exception(
                      "LSTM layers must be placed proceeding to CNN/Pool"
                  )
  
              match = cnn_matcher.match(value)
              if match is None:
                  raise Exception(
                      f"CNN structure needs: cnn=[filters]:[h]x[w]:[sx]x[sy] but got {value}"
                  )
  
              match = match.groups()
              kernel_size = [3, 3]
              stride = [1, 1]
              if match[1] is not None:
                  kernel_size = [int(match[2]), int(match[3])]
              if match[4] is not None:
                  stride = [int(match[5]), int(match[6])]
  
              layers.append(
                  Conv2DLayerParams(
                      filters=int(match[0]),
                      kernel_size=IntVec2D(*kernel_size),
                      strides=IntVec2D(*stride),
                  )
              )
          elif label in {"tcnn", "tconv", "tconv2d"}:
              if lstm_appeared:
                  raise Exception(
                      "LSTM layers must be placed proceeding to CNN/Pool"
                  )
  
              match = cnn_matcher.match(value)
              if match is None:
                  raise Exception(
                      "Transposed CNN structure needs: tcnn=[filters]:[sx]x[sy]"
                  )
  
              match = match.groups()
              kernel_size = [3, 3]
              stride = [2, 2]
              if match[1] is not None:
                  stride = [int(match[2]), int(match[3])]
              if match[4] is not None:
                  kernel_size = [int(match[5]), int(match[6])]
  
              layers.append(
                  TransposedConv2DLayerParams(
                      filters=int(match[0]),
                      kernel_size=IntVec2D(*kernel_size),
                      strides=IntVec2D(*stride),
                  )
              )
          elif label in {"pool", "max_pool", "pool2d"}:
              if lstm_appeared:
                  raise Exception(
                      "LSTM layers must be placed proceeding to CNN/Pool"
                  )
              match = pool_matcher.match(value)
              if match is None:
                  raise Exception("Pool structure needs: pool=[h];[w]")
  
              match = match.groups()
              kernel_size = [int(match[0])] * 2
              if match[1] is not None:
                  kernel_size = [int(match[0]), int(match[2])]
  
              if match[3] is not None:
                  stride = IntVec2D(int(match[4]), int(match[5]))
              else:
                  stride = IntVec2D(-1, -1)
  
              layers.append(
                  MaxPool2DLayerParams(
                      pool_size=IntVec2D(*kernel_size), strides=stride
                  )
              )
          else:
              raise Exception(
                  "Unknown layer with name: {}".format(label)
              )
  
      return layers
  
imports: import json
  import logging
  import re
  from dataclasses import dataclass, field
  from typing import Optional, List
  from paiargparse import pai_meta, pai_dataclass
  from tfaip import TrainerParams, TrainerPipelineParamsBase
  from tfaip.util.tfaipargparse import post_init
  from calamari_ocr import calamari_ocr_dir
  from calamari_ocr.ocr import SavedCalamariModel
  from calamari_ocr.ocr.dataset.codec import CodecConstructionParams
  from calamari_ocr.ocr.model.layers.bilstm import BiLSTMLayerParams
  from calamari_ocr.ocr.model.layers.concat import ConcatLayerParams
  from calamari_ocr.ocr.model.layers.conv2d import Conv2DLayerParams
  from calamari_ocr.ocr.model.layers.dilatedblock import DilatedBlockLayerParams
  from calamari_ocr.ocr.model.layers.dropout import DropoutLayerParams
  from calamari_ocr.ocr.model.layers.pool2d import MaxPool2DLayerParams
  from calamari_ocr.ocr.model.layers.transposedconv2d import TransposedConv2DLayerParams
  from calamari_ocr.ocr.model.params import LayerParams, IntVec2D
  from calamari_ocr.ocr.scenario import CalamariScenarioParams
  from calamari_ocr.ocr.training.pipeline_params import CalamariDefaultTrainerPipelineParams, CalamariTrainOnlyPipelineParams, CalamariSplitTrainerPipelineParams
label_desc: Use image node_id 3 for calling a global function with example usage: graph_params_from_definition_string(s) and returns: layers