img_to_path / img_to_path.pyon commit add dlw module; clean up for handover (d7cac70)
   1"""
   2img_to_path.py
   3Andrew Lorimer, November 2024
   4Monash University
   5
   6Converts a path in a monochrome image to a series of points 
   7in a defined format for laser writing with the confocal 
   8setup. Configuration is done in img_to_path.ini. This version
   9for use with LabView script.
  10"""
  11
  12import configparser as cp
  13import cv2
  14import numpy as np
  15import sys
  16
  17DEFAULTS = {'speed': '10',
  18            'dimensions': '100, 100',
  19            'z': '0',
  20            'beam_size':'0.1'}
  21DEFAULT_CONFIG_PATH = "img_to_path.ini"
  22
  23def row(x, y, z, on, v):
  24    """
  25    Format a data row. Note on/off value defines whether the laser turns on/off 
  26    at the end of the line.
  27    """
  28    return "1\t{:.3f}\t{:.3f}\t{:.3f}\t{:.3f}\t{:d}\n".format(x, y, z, on, v)
  29
  30def im_to_real(x, y):
  31    """Convert (x, y) image coordinates (px) to real world coordinates (um)"""
  32    return np.array((x, y)) / im_dim[0] * real_dim
  33
  34
  35if __name__ == "__main__":
  36    
  37    # Get config file path from arguments or use default path
  38    if len(sys.argv) > 1 and sys.argv[1]:
  39        config_path = sys.argv[1]
  40    else:
  41        config_path = DEFAULT_CONFIG_PATH
  42
  43    # Set up config parser
  44    config = cp.ConfigParser(allow_unnamed_section=True)
  45    config['DEFAULT'] = DEFAULTS
  46    config.read_file(open(config_path))
  47
  48    # Set config values
  49    speed = config.getint(cp.UNNAMED_SECTION, 'speed')
  50    real_dim = np.fromstring(config.get(cp.UNNAMED_SECTION, 'dimensions'), dtype=float, sep=",")
  51    z = config.getfloat(cp.UNNAMED_SECTION, 'z')
  52    beam_size = config.getfloat(cp.UNNAMED_SECTION, 'beam_size')
  53    input_path = config.get(cp.UNNAMED_SECTION, 'input')
  54    output_path = config.get(cp.UNNAMED_SECTION, 'output')
  55
  56    # Read image
  57    im = cv2.imread(input_path, cv2.IMREAD_GRAYSCALE)
  58    im_dim = im.shape
  59
  60    # Check if aspect ratios match
  61    if (im_dim[0] / im_dim[1] != real_dim[0]/real_dim[1]):
  62        print("Warning: input and output aspect ratios do not match - result will be distorted")
  63
  64    # Threshold image to binary
  65    _, im = cv2.threshold(im, 127, 255, cv2.THRESH_BINARY)
  66
  67    # Convert to 0 or 1
  68    im = im / 255.0
  69    if np.median(im) > 0.5:
  70        im = -(im - 1)
  71
  72    # Find required scan interval based on beam size
  73    line_height = real_dim[1] / im_dim[1]
  74    if line_height > beam_size:
  75        # Need to interpolate (do scans in between each row of pixels)
  76        number_scans = int(np.round(line_height/beam_size))
  77    else:
  78        # Need to remove some scans
  79        number_scans = 1
  80
  81    # Initialise output
  82    output = ""
  83
  84    # Go to origin point with laser off
  85    output += row(0.0, 0.0, z, 0.0, speed)
  86    current = (0.0, 0.0)
  87
  88    # Iterate through rows of pixels
  89    for i in range(im_dim[0]):
  90        for k in range(number_scans):
  91            diff = np.diff(im[i])
  92            indices_0_to_1 = list(np.where(diff == 1)[0] + 1)
  93            indices_1_to_0 = list(np.where(diff == -1)[0] + 1)
  94            if (len(indices_0_to_1) == 0 and len(indices_1_to_0) == 0):
  95                continue
  96
  97            if (im[i, 0] == 1):
  98                # Start at left pixel
  99                point_real = im_to_real(i, 0)
 100            else:
 101                # Start at first transition to 1
 102                j = indices_0_to_1.pop(0)
 103                point_real = im_to_real(i, j)
 104            output += row(point_real[0]+k*beam_size, point_real[1], z, 1.0, speed)
 105            current = point_real
 106
 107            while True:
 108                if len(indices_1_to_0) != 0:
 109                    j = indices_1_to_0.pop(0)
 110                    point_real = im_to_real(i, j)
 111                    output += row(point_real[0]+k*beam_size, point_real[1], z, 0.0, speed)
 112                    current = point_real
 113
 114                if len(indices_0_to_1) != 0:
 115                    j = indices_0_to_1.pop(0)
 116                    point_real = im_to_real(i, j)
 117                    output += row(point_real[0]+k*beam_size, point_real[1], z, 1.0, speed)
 118                    current = point_real
 119                else:
 120                    break
 121
 122    # Turn laser off at current position at go back to origin
 123    output += row(current[0], current[1], z, 0.0, speed)
 124    output += row(0.0, 0.0, z, 0.0, speed)
 125
 126    # Write output
 127    with open(output_path, 'w') as f:
 128        f.write(output)