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 23defrow(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 """ 28return"1\t{:.3f}\t{:.3f}\t{:.3f}\t{:.3f}\t{:d}\n".format(x, y, z, on, v) 29 30defim_to_real(x, y): 31"""Convert (x, y) image coordinates (px) to real world coordinates (um)""" 32return 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 38iflen(sys.argv) >1and sys.argv[1]: 39 config_path = sys.argv[1] 40else: 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 61if(im_dim[0] / im_dim[1] != real_dim[0]/real_dim[1]): 62print("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 69if 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] 74if 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)) 77else: 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 89for i inrange(im_dim[0]): 90for k inrange(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) 94if(len(indices_0_to_1) ==0andlen(indices_1_to_0) ==0): 95continue 96 97if(im[i,0] ==1): 98# Start at left pixel 99 point_real =im_to_real(i,0) 100else: 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 107while True: 108iflen(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 114iflen(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 119else: 120break 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 127withopen(output_path,'w')as f: 128 f.write(output)