-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathspot_position_mod.py
251 lines (210 loc) · 9.85 KB
/
spot_position_mod.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
import datetime
import numpy as np
from scipy import ndimage
import os
import cv2
import easygui as eg
import openpyxl
def rotate_image(array, angle, pivot):
'''Function to rotate an array CCW a given angle around a defined axis
inputs:
array - 2D image structure (numpy array )'''
# Add 0 on both axis such that the pivot is at the centre of the array
padx = [array.shape[1] - pivot[0], pivot[0]]
pady = [array.shape[0] - pivot[1], pivot[1]]
# Add padding to array
array_p = np.pad(array, [pady, padx], 'constant')
array_r = ndimage.rotate(array_p, angle, reshape=False)
return array_r[pady[0]:-pady[1], padx[0]:-padx[1]]
# further info here: https://stackoverflow.com/questions/25458442/rotate-a-2d-image-around-specified-origin-in-python
class ActiveScript:
def __init__(self, image_dir):
actscr_loc = os.path.join(os.path.dirname(image_dir),
'activescript.txt')
for line in open(actscr_loc, 'r'):
if line.startswith('CameraHRa'):
CameraHRatio = float(line.split("=")[1].strip())
if line.startswith('CameraVRa'):
CameraVRatio = float(line.split("=")[1].strip())
if line.startswith('AppXCenter'):
AppXCenter = float(line.split("=")[1].strip())
if line.startswith('AppYCenter'):
AppYCenter = float(line.split("=")[1].strip())
if line.startswith('TextPath'):
if '3' in line:
self.device = '3000'
elif '4' in line:
self.device = '4000'
else:
self.device = 'Unknown'
if self.device == '4000':
self.CameraHRatio = CameraVRatio
self.CameraVRatio = CameraHRatio
self.AppXCenter = AppYCenter
self.AppYCenter = AppXCenter
else:
self.CameraHRatio = CameraHRatio
self.CameraVRatio = CameraVRatio
self.AppXCenter = AppXCenter
self.AppYCenter = AppYCenter
class Output:
'''
The Output class will create an object that contains the information
held in the output.txt file generated by the LOGOS software when
capturing images
'''
def __init__(self, image_dir):
textfile = os.path.join(os.path.dirname(image_dir),
'output.txt')
file = open(textfile, 'r')
full_data = []
for line in file:
full_data.append([x.lstrip().rstrip() for x in line.split(',')])
if line.startswith('Beamspots found'):
no_of_spots = int(line[18:])
self.full_data = full_data
date = full_data[0][3]
self.datetime = datetime.datetime.strptime(date, '%H:%M:%S %m/%d/%Y')
self.center = [float(full_data[0][5]),float(full_data[0][6])]
self.no_of_spots = no_of_spots
self.spots_xy = {}
self.spots_width = {}
self.spots_height = {}
self.spots_diameter = {}
self.spots_quality = {}
for i in range(1, 1+self.no_of_spots):
row = [full_data.index(x) for x in full_data if x[0] == str(i)][0]
self.spots_xy[i] = [full_data[row][3],full_data[row][4]]
self.spots_width[i] = full_data[row][19]
self.spots_height[i] = full_data[row][23]
self.spots_diameter[i] = full_data[row][25]
self.spots_quality[i] = full_data[row][27]
class Profile:
def __init__(self, profile):
self.lgrad, self.rgrad = gradient_fetch(profile, 0.2, 0.8)
self.fwhm = fwhm_fetch(profile)
def __str__(self):
return f'lgrad: {self.lgrad}, rgrad: {self.rgrad}, fwhm: {self.fwhm}'
class Spot:
'''Defines an individual spot (from an array) and it's relevant properties
as obtained from a LOGOS detector with an activescript.txt file
'''
def __init__(self, spot_array, pixel_loc, activescript):
self.pixel_loc = pixel_loc
horprof, vertprof, tl_br, bl_tr = spot_to_profiles(spot_array, activescript)
self.horprof = Profile(horprof)
self.vertprof = Profile(vertprof)
self.tl_br = Profile(tl_br)
self.bl_tr = Profile(bl_tr)
x = self.pixel_loc[0] - activescript.AppXCenter
y = self.pixel_loc[1] - activescript.AppYCenter
x = x / activescript.CameraHRatio
y = y / activescript.CameraVRatio
self.rel_pixel_loc = [x, y]
def __str__(self):
return f'horprof: {self.horprof}\nvertprof: {self.vertprof}\n'\
f'tl_br: {self.tl_br}\n bl_tr: {self.bl_tr}\n'\
f'pixel_loc: {self.pixel_loc}\n'\
f'rel_pixel_loc: {self.rel_pixel_loc}\n'
def list_results(self):
result = [self.rel_pixel_loc[0], self.rel_pixel_loc[1],
self.horprof.lgrad, self.horprof.rgrad, self.horprof.fwhm,
self.vertprof.lgrad, self.vertprof.rgrad, self.vertprof.fwhm,
self.bl_tr.lgrad, self.bl_tr.rgrad, self.bl_tr.fwhm,
self.tl_br.lgrad, self.tl_br.rgrad, self.tl_br.fwhm]
result = [float(i) for i in result]
return result
class SpotPattern:
'''Class to define spot pattern images acquired using the flat LOGOS panels
'''
def __init__(self, image_path):
if not os.path.isfile(image_path):
print('No image selected, code will terminate')
input('Press any key to continue')
raise SystemExit
image_dir = os.path.dirname(image_path)
if not os.path.isfile(os.path.join(image_dir, 'activescript.txt')):
print('No active script detected for this spot pattern')
input('Press any key to continue')
raise SystemExit
self.path = image_path
self.activescriptpath = os.path.join(image_dir, 'activescript.txt')
self.activescript = ActiveScript(self.activescriptpath)
self.outputpath = os.path.join(image_dir, 'output.txt')
self.output = Output(self.outputpath)
self.image = cv2.imread(image_path)
if self.activescript.device == '4000':
self.image = cv2.rotate(self.image, cv2.ROTATE_90_COUNTERCLOCKWISE)
def __str__(self):
return f'path: {self.path}\nact_scr_path: {self.activescriptpath}'
def spot_to_profiles(myimage, activescript):
'''Extract cartesian and diagonal profiles from spot array'''
blurred = cv2.GaussianBlur(myimage, (11, 11), 0)
normed = blurred/blurred.max()
(min_val, max_val, min_loc, max_loc) = cv2.minMaxLoc(blurred)
horprof = [range(len(normed[0])), normed[max_loc[1]]]
vertprof = [range(len(normed)), [x[max_loc[0]] for x in normed]]
spin_ccw = rotate_image(normed, 45, max_loc)
spin_cw = rotate_image(normed, -45, max_loc)
# Top left to bottom right profile
tl_br = [range(len(spin_ccw[0])), spin_ccw[max_loc[1]]]
# Bottom left to top right profile
bl_tr = [range(len(spin_cw[0])), spin_cw[max_loc[1]]]
horprof[0] = [x/activescript.CameraHRatio for x in horprof[0]]
vertprof[0] = [x/activescript.CameraVRatio for x in vertprof[0]]
# Diagonal profile correction to distance from pixel size uses mean of xy
diag_ratio = (activescript.CameraHRatio + activescript.CameraVRatio) / 2
tl_br[0] = [x/diag_ratio for x in tl_br[0]]
bl_tr[0] = [x/diag_ratio for x in bl_tr[0]]
return horprof, vertprof, tl_br, bl_tr
def gradient_fetch(profile, lowthresh, highthresh):
'''Takes a gaussian profile as input and returns the gradient on the left
and right side of the peak between the high threshold and low threshold
'''
left_low = [n for n, i in enumerate(profile[1]) if i > lowthresh][0]
right_low = [n for n, i in enumerate(profile[1]) if i > lowthresh][-1]
left_high = [n for n, i in enumerate(profile[1]) if i > highthresh][0]
right_high = [n for n, i in enumerate(profile[1]) if i > highthresh][-1]
lgrad = 100*(highthresh-lowthresh)/(profile[0][left_high] - profile[0][left_low])
rgrad = 100*(highthresh-lowthresh)/(profile[0][right_high] - profile[0][right_low])
return lgrad, rgrad
def fwhm_fetch(profile):
'''Returns FWHM for a gaussian profile'''
norm_profile = profile/max(profile[1])
fwhml = [n for n, i in enumerate(norm_profile[1]) if i > 0.5][0]
fwhmr = [n for n, i in enumerate(norm_profile[1]) if i > 0.5][-1]
fwhm = profile[0][fwhmr]-profile[0][fwhml]
return fwhm
def select_acquisition_info():
gantry = eg.choicebox('Please select room',
'Gantry',
['Gantry 1', 'Gantry 2',
'Gantry 3', 'Gantry 4'
]
)
energy = eg.integerbox(msg='Energy',
title='Select energy of acquired image',
lowerbound=70,
upperbound=245
)
gantry_angle = eg.integerbox(msg='Gantry Angle',
title='Select angle of acquisition',
lowerbound=0,
upperbound=360
)
operators = ['AB', 'AK', 'AGo', 'AGr', 'AM', 'AJP', 'AT', 'AW',
'CB', 'CG', 'KC', 'PI', 'SC', 'SG', 'TNC', 'VMA', 'VR']
operator = eg.choicebox('Select Operator',
'Operator',
operators
)
return [gantry, energy, gantry_angle, operator]
def write_to_results_wb(results_loc, results):
wb = openpyxl.load_workbook(results_loc)
ws = wb.worksheets[0]
ws.append(results)
wb.save(results_loc)
# output = Output('C:\\Users\\cgillies.UCLH\\Desktop\\Coding\\Test_Data\\LOGOS\\Spot_Grids\\2021_0309_0045\\00000001.tif')
# print(output.datetime)
# spotpat = SpotPattern('C:\\Users\\csmgi\\Desktop\\Work\\Coding\\Test-Data\\LOGOS\\Spot_Grids\\2021_0309_0045\\00000001.tif')
# print(spotpat)