"""
Stream-file reader and pattern generator
"""
import math
import numpy as np
MAX_UNIT = 65535 # 16-bit stream-fle resolution
[docs]def pixel_pitch(hfw):
"""
Get pixel pitch for a target HFW
:param hfw: half field width
:return:
"""
return hfw*1000/MAX_UNIT
[docs]def open_stream_file(file, hfw, offset=200, collapse=False):
"""
Open stream file, convert to nm and define enclosing volume dimensions.
A valid stream-file should consist of 3 columns and start with 's16' line.
:param file: path to the stream-file
:param hfw: target half field width
:param offset: determines a margin around the printing path
:param collapse: if True, summ dwell time of consecutive instructions with identical coordinates
:return: normalized directives in nm and s, dimensions of the enclosing volume [z, y, x] in nm
"""
if not file:
raise FileNotFoundError
# print('Specify a stream file:')
# file = fd.askopenfilename(title='Open stream-file')
data = None
# Opening file and parsing text
with open(file, mode='r+', encoding='utf-8', errors='ignore') as f:
text = f.readlines()
if text[0] != 's16\n':
raise Exception('Not a valid stream file!')
delim = ' ' # delimiter between the columns
header = 3 # number of lines to skip in the beginning
columns = (0, 1, 2) # numbers of columns to get
# Defult columns:
# 0 – Dwell time
# 1 – x-position
# 2 – y-position
with open(file, mode='r+', encoding='utf-8', errors='ignore') as f:
print(f'Reading stream-file {file} ...')
data = np.genfromtxt(f, dtype=np.float64, comments='#', delimiter=delim, skip_header=header, usecols=columns,
invalid_raise=False)
print('Done!')
# Converting to simulation units
unit_pitch = pixel_pitch(hfw) # nm/pixel
data[:, 0] /= 1E7 # converting [0.1 µs] to [s]
data[:, 1] *= unit_pitch # converting stream-file units to [nm]
data[:, 2] *= unit_pitch
# Determining volume dimensions with an offset
x_max, x_min = data[:, 1].max(), data[:, 1].min()
x_dim = (x_max - x_min) + offset
x_delta = offset / 2
y_max, y_min = data[:, 2].max(), data[:, 2].min()
y_dim = (y_max - y_min) + offset
y_delta = offset / 2
z_dim = 200
if x_dim < 100 or y_dim < 100: # checking if both dimensions are at least 100 nm
if x_dim < 100:
x_dim = 200
x_delta = x_dim/2
if y_dim < 100:
# y_dim = ((x_dim/2)//10)*10Y
y_dim = 200
y_delta = y_dim/2
# Getting local coordinates
data[:, 1] -= x_min - x_delta # shifting path center to the center of the volume
data[:, 2] -= y_min - y_delta # shifting path center to the center of the volume
data = np.roll(data, -1, 1)
# Summing dwell time of consecutive points with same coordinates
if collapse:
p_d = np.diff(data[:, (0, 1)], axis=0).astype(bool)
p_d = p_d[:, 0] + p_d[:, 1]
collapsed_arr = []
i = 0
while i < p_d.shape[0] - 1:
b = np.copy(data[i])
while i < p_d.shape[0] and not p_d[i]:
i += 1
b[2] += data[i, 2]
i += 1
collapsed_arr.append(b)
# else:
# b = np.copy(data[i])
# try: # Sometimes loop exits with i equal to data.shape, if the last line is unique
# b[2] += data[i+1, 2]
# except IndexError:
# pass
# collapsed_arr.append(b)
data = np.asarray(collapsed_arr)
return data, np.array([z_dim, y_dim, x_dim], dtype=int)
[docs]def analyze_pattern(file, hfw):
"""
Parse stream-file and split it into stages
"""
unit_pitch = pixel_pitch(hfw)
data, shape = open_stream_file(file, hfw=hfw, collapse=True)
stages = []
total_time = data[:, 2].sum()
delta = data[1:] - data[:-1]
unique = np.unique(delta, axis=0, return_counts=True)
i = 0
while i < delta.shape[0]:
if data[i, 2] > 1: # considering 1 nm/s the lowest patterning speed and everything above is stationary
stages.append(('Stationary', data[i, 2]))
i += 1
continue
else:
t = 0
pos0 = np.array([data[i,0], data[i, 1]]) # x, y distance
pos = np.array([0, 0])
flag_end = False
while i < delta.shape[0]-1:
t += data[i, 2]
if np.isclose(delta[i, 0:2], delta[i + 1, 0:2], atol=unit_pitch).all() and delta[i, 2] == delta[i+1, 2]:
i += 1
else:
t += data[i+1, 2]
flag_end = True # telling the loop, that termination was due to the end of a stage
break
else:
if not flag_end: # if the loop exited due to array bounds, collect remaining cells
t += data[i, 2]
i += 1
t += data[i, 2]
flag_end = False
pos[:] = data[i, 0], data[i, 1]
lp = pos - pos0
l = math.sqrt(lp[0] ** 2 + lp[1] ** 2)
speed = round(l / t)
stages.append((f'{speed} nm/s', t))
i += 1
print(f'Total patterining time: {total_time:.4f} s')
print(f'Stages: |', end='')
for stage, d in stages:
print(f'{stage}, {d:.4f} | ', end='')
else:
print(' ')
[docs]def generate_pattern(pattern, loops, dwell_time, x, y, params, step=1):
"""
Generate a stream-file for a simple figure.
:param pattern: name of a shape: point, line, square, rectangle, circle
:param loops: amount of passes
:param dwell_time: time spent on each point, s
:param x: center x position of the figure, nm
:param y: center y position of the figure, nm
:param params: figure parameters, nm;
(length) for line, (diameter) for circle, (edge length) for cube
:param step: distance between each point, nm
:return: array(x positions[nm], y positions[nm], dwell time[s])
"""
pattern = pattern.casefold()
path = None
if pattern == 'point':
path = generate_point(loops, dwell_time, x, y)
if pattern == 'line':
path = generate_line(loops, dwell_time, x, y, *params, step=step)
if pattern == 'circle':
path = generate_circle(loops, dwell_time, x, y, *params, step=step)
if pattern == 'square':
path = generate_square(loops, dwell_time, x, y, *params, step=step)
if pattern == 'rectangle':
path = generate_square(loops, dwell_time, x, y, *params, step=step)
return path
[docs]def generate_point(loops, dwell_time, x, y):
# loop = np.asarray([x, y, dwell_time]).reshape(1, 3)
# path = np.tile(loop, (loops, 1))
path = np.asarray([x, y, loops * dwell_time]).reshape(1, 3)
return path
[docs]def generate_circle(loops, dwell_time, x, y, diameter, _, step=1):
angle_step = step / diameter / 2
n = int(np.pi * 2 // angle_step)
loop = np.zeros((n, 3))
stub = np.arange(angle_step, np.pi * 2, angle_step)
loop[:, 0] = diameter / 2 * np.sin(stub) + y
loop[:, 1] = diameter / 2 * np.cos(stub) + x
loop[:, 2] = dwell_time
path = np.tile(loop, (loops, 1))
a = 0
return path
[docs]def generate_square(loops, dwell_time, x, y, side_a, side_b=None, step=1):
if side_b is None:
side_b = side_a
top_left = (y + side_b / 2, x - side_a / 2)
top_right = (y + side_b / 2, x + side_a / 2)
low_right = (y - side_b / 2, x + side_a / 2)
low_left = (y - side_b / 2, x - side_a / 2)
steps_a = int(side_a / step) - 1
steps_b = int(side_b / step) - 1
edge_top = np.empty((steps_a, 3))
edge_top[:, 1] = np.arange(top_left[1] + step, top_right[1], step)
edge_top[:, 0] = top_left[0]
edge_right = np.empty((steps_b, 3))
edge_right[:, 0] = np.arange(top_right[0] - step, low_right[0], -step)
edge_right[:, 1] = top_right[1]
edge_bottom = np.empty((steps_a, 3))
edge_bottom[:, 1] = np.arange(low_right[1] - step, low_left[1], -step)
edge_bottom[:, 0] = low_right[0]
edge_left = np.empty((steps_b, 3))
edge_left[:, 0] = np.arange(low_left[0] + step, top_left[0], step)
edge_left[:, 1] = low_left[1]
path = np.concatenate([edge_top, edge_right, edge_bottom, edge_left])
path[:, 2] = dwell_time
path = np.tile(path, (loops, 1))
return path
[docs]def generate_line(loops, dwell_time, x, y, line, _, step=1):
start = x - line / 2
end = x + line / 2
loop1 = np.arange(start, end+1e-8, step)
loop2 = np.arange(loop1[-1], start-1e-8, -step)
loop = np.concatenate([loop1, loop2])
path = np.empty((loop1.shape[0]+loop2.shape[0], 3))
path[:, 0] = loop
path[:, 1] = y
path[:, 2] = dwell_time
path = np.tile(path, (loops, 1))
return path