text: import argparse
  import logging
  import os
  from argparse import ArgumentError
  
  import cv2
  import numpy as np
  import pandas as pd
  import tqdm
  from matplotlib.path import Path
  from plyfile import PlyData
  
  try:
      from . import concave_hull
  except:
      import concave_hull
  try:
      from . import transformations
  except:
      import transformations
  
  
  def config():
  
      parser = argparse.ArgumentParser()
      parser.add_argument('mesh_plyfile')
      parser.add_argument('-k', required=False, type=int, default=10, help='Increase for smoother hull')
      parser.add_argument('-dx',
                          required=False,
                          type=float,
                          nargs=3,
                          metavar=('dx_min', 'dx_max', '#points'),
                          default=[-.4, .4, 20.],
                          help='Define config for x translation')
      parser.add_argument('-dy',
                          required=False,
                          type=float,
                          nargs=3,
                          metavar=('dy_min', 'dy_max', '#points'),
                          default=[-.4, .4, 20.],
                          help='Define config for y translation')
      parser.add_argument('-angle',
                          required=False,
                          type=float,
                          nargs=3,
                          metavar=('angle_min', 'angle_max', '#points'),
                          default=[-180, 180, 72],
                          help='Define config for z rotation (in degrees)')
      parser.add_argument('-o', '--output_folder', type=str, required=True, help='Define output folder')
      parser.add_argument('-tts', required=False, default=True, type=bool, help='Enable/Disable train/test split')
      parser.add_argument('-split',
                          required=False,
                          default=5,
                          type=int,
                          help='Define total/test ratio.'\
                               'Example: -split 5 implies for 100 samples, 80 in train and 20 in test')
      parser.add_argument('--cam_calib_file',
                          required=True,
                          type=str,
                          help='Path to camera calibration file. '\
                               'Must be a dictionary in a npy file with following keys: Intrinsic, dist_coeff, r, t')
      parser.add_argument('--z_filter',
                          required=False,
                          type=float,
                          default=0.,
                          help='Filter points to project accordint to z axis')
      parser.add_argument('--mesh_cam_unit_ratio',
                          required=False,
                          type=float,
                          default=1,
                          help='Unit ratio between mesh and camera calibration extrinsinc parameters units. '\
                               'Example: if camera is in centimeters, while mesh is in meters, ratio = 100')
      parser.add_argument('--img_height',
                          required=False,
                          type=int,
                          default=600,
                          help='Define height of the camera image (in pixels)')
      parser.add_argument('--img_width',
                          required=False,
                          type=int,
                          default=800,
                          help='Define width of the camera image (in pixels)')
  
      return parser.parse_args()
  
  
  def set_directory(path: str, name: str) -> str:
      """Set directory path and create if non-existent
  
      Args:
          path (str): path to the directory location
          name (str): name of the directory
  
      Returns:
          str: path to the directory
      """
      new_path = os.path.join(path, name)
      if not os.path.isdir(new_path):
          os.makedirs(new_path)
      return new_path
  
  
  def world_to_2D(xyz: np.ndarray, cam_calibration: dict) -> np.ndarray:
      xyz = xyz.astype(np.float32)
      uv, _ = cv2.projectPoints(xyz, cam_calibration['r'], cam_calibration['t'], cam_calibration['Intrinsic'],
                                cam_calibration['dist_coeff'])
      return uv.reshape(uv.shape[0], 2)
  
  
  def generate_dataset(mesh_path: str, output_folder: str, k: int, dx: list, dy: list, angle: int,
                       cam_calibration_file: str, z_filter: float, mesh_cam_unit_ratio: float, height: float,
                       width: float, train_test_split: bool, split_ratio: int) -> None:
  
      train_folder, test_folder = None, None
      if train_test_split:
          train_folder = set_directory(path=output_folder, name='train')
          test_folder = set_directory(path=output_folder, name='test')
  
      mesh = PlyData.read(mesh_path)
  
      original_vertices = transformations.format_vertices(mesh['vertex'].data)
  
      try:
          cam_calibration = np.load(cam_calibration_file, allow_pickle=True)[()]
          assert isinstance(cam_calibration, dict)
          assert set(cam_calibration) == {'Intrinsic', 'dist_coeff', 'r', 't'}
      except FileNotFoundError:
          raise FileNotFoundError(f'No such camera calibration file: {cam_calibration_file}')
      except AssertionError:
          raise TypeError(
              'Unexpected cam calibration format. np.load(cam_calibration_file, allow_pickle=True)[()] must return '\
              'a dictionary with the following keys: Intrinsic, dist_coeff, r, t'
          )
  
      angles = np.linspace(angle[0], angle[1] - (angle[1] - angle[0]) / angle[2], int(angle[2])) * np.pi / 180.
      dxs = np.linspace(dx[0], dx[1], int(dx[2]))
      dys = np.linspace(dy[0], dy[1], int(dy[2]))
  
      train_table, test_table, total_table = {}, {}, {}
      count, train_count, test_count = 0, 0, 0
  
      for dx in tqdm.tqdm(dxs):
          for dy in dys:
              for angle in angles:
                  vertices = transformations.transform_vertices(points=original_vertices,
                                                                angle=angle,
                                                                direction=[0, 0, 1],
                                                                translation=[dx, dy, 0])
  
                  if z_filter != 0.:
                      vertices = vertices[vertices[:, 2] > z_filter]
  
                  projection = world_to_2D(mesh_cam_unit_ratio * vertices, cam_calibration)
  
                  # False if a point is out of the frame
                  if all(((0 < p[0] < 800) and (0 < p[1] < 600)) for p in projection):
                      hull = concave_hull.concaveHull(projection, k)
                      poly_path = Path(hull)
  
                      x, y = np.mgrid[:height, :width]
                      coors = np.hstack((y.reshape(-1, 1), x.reshape(-1, 1)))
  
                      mask = poly_path.contains_points(coors).reshape(height, width)
  
                      if train_test_split:
                          if (count + 1) % split_ratio:
                              cv2.imwrite(os.path.join(train_folder, f'{train_count}.png'), mask.astype(np.uint8) * 255)
                              train_table[train_count] = {'dx': dx, 'dy': dy, 'angle': angle}
                              train_count += 1
                          else:
                              cv2.imwrite(os.path.join(test_folder, f'{test_count}.png'), mask.astype(np.uint8) * 255)
                              test_table[test_count] = {'dx': dx, 'dy': dy, 'angle': angle}
                              test_count += 1
                      else:
                          cv2.imwrite(os.path.join(output_folder, f'{count}.png'), mask.astype(np.uint8) * 255)
                          total_table[count] = {'dx': dx, 'dy': dy, 'angle': angle}
  
                      count += 1
  
      if train_test_split:
          pd.DataFrame(train_table).T.to_hdf(os.path.join(train_folder, 'data.h5'), key='table', mode='w')
          pd.DataFrame(test_table).T.to_hdf(os.path.join(test_folder, 'data.h5'), key='table', mode='w')
      else:
          pd.DataFrame(total_table).T.to_hdf(os.path.join(output_folder, 'data.h5'), key='table', mode='w')
  
  
  if __name__ == "__main__":
  
      args = config()
  
      if not os.path.exists(args.output_folder):
          os.mkdir(args.output_folder)
  
      logging.basicConfig(level=logging.INFO,
                          filename=f'{args.output_folder}/dataset.log',
                          format='[%(asctime)s] :: %(levelname)s :: %(message)s')
  
      logging.info(f'Input args: {args}')
  
      if args.angle[2] <= 0:
          raise ArgumentError('-angle #points has to be a non-zero positive integer')
      if args.dx[2] <= 0:
          raise ArgumentError('-dx #points has to be a non-zero positive integer')
      if args.dy[2] <= 0:
          raise ArgumentError('-dy #points has to be a non-zero positive integer')
      if args.split <= 1:
          raise ArgumentError('-split has to be a positive integer greater than 1')
  
      generate_dataset(mesh_path=args.mesh_plyfile,
                       output_folder=args.output_folder,
                       k=args.k,
                       dx=args.dx,
                       dy=args.dy,
                       angle=args.angle,
                       cam_calibration_file=args.cam_calib_file,
                       z_filter=args.z_filter,
                       mesh_cam_unit_ratio=args.mesh_cam_unit_ratio,
                       height=args.img_height,
                       width=args.img_width,
                       train_test_split=args.tts,
                       split_ratio=args.split)
  