img_to_path / img_to_path_dots.pyon commit add dlw module; clean up for handover (d7cac70)
   1"""
   2img_to_path_dots.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.
   9Uses an array of dots instead of line scanning. For use with 
  10LabView script.
  11"""
  12
  13import configparser as cp
  14import cv2
  15import numpy as np
  16import sys
  17
  18CONFIG_SECTION = "Main"
  19DEFAULTS = {CONFIG_SECTION: {'speed': '10',
  20            'dimensions': '100, 100',
  21            'z': '0',
  22            'beam_size':'0.1'}}
  23DEFAULT_CONFIG_PATH = "img_to_path.ini"
  24
  25# TODO make proper config values
  26padding = 10    # um
  27resolution = 0.5
  28
  29def row(x, y, z, on, v):
  30    """
  31    Format a data row. Note on/off value defines whether the laser turns on/off 
  32    at the end of the line.
  33    """
  34    return "1\t{:.3f}\t{:.3f}\t{:.3f}\t{:.3f}\t{:d}\n".format(x, y, z, on, v)
  35
  36def im_to_real(x, y):
  37    """Convert (x, y) image coordinates (px) to real world coordinates (um)"""
  38    return np.array((x, y)) / im_dim[0] * real_dim
  39
  40
  41if __name__ == "__main__":
  42    
  43    # Get config file path from arguments or use default path
  44    if len(sys.argv) > 1 and sys.argv[1]:
  45        config_path = sys.argv[1]
  46    else:
  47        config_path = DEFAULT_CONFIG_PATH
  48
  49    # Set up config parser
  50    config = cp.ConfigParser()
  51    config['DEFAULT'] = DEFAULTS
  52    config.read_file(open(config_path))
  53
  54    # Set config values
  55    speed = config.getint(CONFIG_SECTION, 'speed')
  56    real_dim = np.fromstring(config.get(CONFIG_SECTION, 'dimensions'), dtype=float, sep=",")
  57    z = config.getfloat(CONFIG_SECTION, 'z')
  58    beam_size = config.getfloat(CONFIG_SECTION, 'beam_size')
  59    input_path = config.get(CONFIG_SECTION, 'input')
  60    output_path = config.get(CONFIG_SECTION, 'output')
  61
  62    # Read image
  63    im = cv2.imread(input_path, cv2.IMREAD_GRAYSCALE)
  64    im_dim = im.shape
  65
  66    # Check if aspect ratios match
  67    if (im_dim[0] / im_dim[1] != real_dim[0]/real_dim[1]):
  68        print("Warning: input and output aspect ratios do not match - result will be distorted")
  69
  70    # Resize image to produce the required dot resolution
  71    im = cv2.resize(im, (int(real_dim[0]/resolution), int(real_dim[1]/resolution)))
  72    im_dim = im.shape
  73
  74    # Threshold image to binary
  75    _, im = cv2.threshold(im, 127, 255, cv2.THRESH_BINARY)
  76
  77    # Convert to 0 or 1
  78    im = im / 255.0
  79    if np.median(im) > 0.5:
  80        im = -(im - 1)
  81
  82    cv2.imshow("display", im)
  83    cv2.waitKey(0)
  84
  85
  86    # Initialise output
  87    output = ""
  88
  89    # Go to origin point with laser off
  90    output += row(0.0, 0.0, z, 0.0, speed)
  91
  92    # Iterate through pixels
  93    direction = 0
  94    current_pos = (0, 0)
  95    for i in range(im_dim[0]):
  96        direction = current_pos[1] > im_dim[1]*resolution
  97        # if direction == 0:
  98        start = 0
  99        stop = im_dim[0]
 100        step = 1
 101        # else:
 102        #     start = im_dim[0]
 103        #     stop = 0
 104        #     step = -1
 105        for j in range(start, stop, step):
 106            if (im[i,j] == 1):
 107                output += row(i*resolution, j*resolution, z, 0.0, speed)
 108                output += row(i*resolution, j*resolution, z, 1.0, speed)
 109                output += row(i*resolution, j*resolution, z, 0.0, speed)
 110                current_pos = (i*resolution, j*resolution)
 111
 112    # Turn laser off at current position at go back to origin
 113    output += row(0.0, 0.0, z, 0.0, speed)
 114
 115    # Write output
 116    with open(output_path, 'w') as f:
 117        f.write(output)