-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathFiberFinderV3
719 lines (600 loc) · 33.2 KB
/
FiberFinderV3
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
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
import tkinter as tk
from tkinter import filedialog, messagebox
from PIL import Image, ImageTk
import cv2
import numpy as np
import os
import zwoasi as asi
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from time import time
import pycromanager
import sys
from scipy import optimize
from time import sleep
import serial
import serial.tools.list_ports
import csv
# env_filename=os.getenv('ZWO_ASI_LIB') #initialize camera and find its directory where it is located
# asi.init('C:\\Users\\ASE\\Desktop\\Ari Lab-2023\\Pics\\ASIStudio\\ASICamera2.dll') #directory of camera
ports=serial.tools.list_ports.comports() #lists all available ports on the system
#application that can connect the camera, connect the XY Preamps, controlls the xy position of the fiber tip,
#takes a photo of the fiber tip, processes the image, and plots the center of the fiber tip
class ImageProcessorApp:
def __init__(self, root):
self.root = root #stores reference to tkinter root window, i should have made this shorter in hindsight
self.root.title("Image Processor")
self.x_coord = 0
self.y_coord = 0
self.coord_min = 0
self.coord_max = 4095 #create max values for the corrdinates of the fiber tip, use later when making actuator function
# Frame for holding the images
self.image_frame = tk.Frame(root)
self.image_frame.pack(fill=tk.BOTH, expand=True)
# Frame for original image
self.frame_original = tk.Frame(self.image_frame)
self.frame_original.pack(side=tk.LEFT, padx=10, pady=10)
# Frame for processed image
self.frame_processed = tk.Frame(self.image_frame)
self.frame_processed.pack(side=tk.LEFT, padx=10, pady=10)
# Frame for XY controls
self.coord_frame = tk.Frame(self.image_frame)
self.coord_frame.pack(side=tk.RIGHT, padx=10, pady=10)
# Element Number Controls
# Define element number options
self.element_options = [str(i) for i in range(1, 9)] #options from 1 to 8
self.selected_element = tk.StringVar(root)
self.selected_element.set(self.element_options[0]) # Default value
# Element Number Dropdown
self.element_label = tk.Label(self.coord_frame, text="FTA Number")
self.element_label.pack()
self.element_dropdown = tk.OptionMenu(self.coord_frame, self.selected_element, *self.element_options)
self.element_dropdown.pack()
#PREAMPS enable/disable button
self.amps_var = tk.BooleanVar(value=False) #create boolean value to make sure the preamps always starts disabled
self.preamps = tk.Checkbutton(self.coord_frame, text = 'Enable XY Preamps', variable=self.amps_var, command=self.toggle_amp)
self.preamps.pack(padx=10,pady=10)
#Buttons for x anf y coordinates
# X Coordinate Controls
self.xcoord_label = tk.Label(self.coord_frame, text="X Coordinate")
self.xcoord_label.pack()
self.xcoord_entry = tk.Entry(self.coord_frame) #create entry places for the xy coordinates
self.xcoord_entry.pack()
self.xcoord_entry.insert(0, "0") # Set default value
# Y Coordinate Controls
self.ycoord_label = tk.Label(self.coord_frame, text="Y Coordinate")
self.ycoord_label.pack()
self.ycoord_entry = tk.Entry(self.coord_frame) #create entry places for the xy coordinates
self.ycoord_entry.pack()
self.ycoord_entry.insert(0, "0") # Set default value
# Coordinates Button
self.update_coords_button = tk.Button(self.coord_frame, text="Update Coordinates", command=self.update_coordinates)
self.update_coords_button.pack(pady=5)
# Buttons for load and process
self.button_frame = tk.Frame(root)
self.button_frame.pack(fill=tk.X, side=tk.BOTTOM)
self.load_button = tk.Button(self.button_frame, text="Connect Camera", command=self.connect_camera) #button for caturing image
self.load_button.pack(side=tk.LEFT, padx=5, pady=5)
self.load_button = tk.Button(self.button_frame, text="Capture Image", command=self.capture_image_from_camera) #button for caturing image
self.load_button.pack(side=tk.LEFT, padx=5, pady=5)
self.process_button = tk.Button(self.button_frame, text="Process Image", command=self.process_image) #button for processing image
self.process_button.pack(side=tk.LEFT, padx=5, pady=5)
self.automate_button = tk.Button(self.button_frame, text="Start Automation", command=self.automate_process_rough) # button for the automation process
self.automate_button.pack(side=tk.LEFT, padx=5, pady=5)
self.clear_data_button = tk.Button(self.button_frame, text="Clear All Data", command=self.clear_all_data)
self.clear_data_button.pack(side=tk.RIGHT, padx=5, pady=5)
self.save_button = tk.Button(self.button_frame, text="Save Data", command=self.save_data) #button to save the automation process
self.save_button.pack(side=tk.RIGHT,padx= 10, pady=10)
# Initialize images and circle centers
self.original_image = None
self.processed_image = None
self.circle_centers = [] #create a dicitonary for the coordninates (in pixles) to be stored and plotted
self.dac_values = [] #create a diciotnary for the DAC values so they can be compared to the change in xy
self.movement_in_x = []
self.movement_in_y = []
self.microns_moved_in_x = []
self.microns_moved_in_y = []
self.microns_per_ADU_in_x = []
self.microns_per_ADU_in_y = []
self.circle_radii = []
self.pixle_size = []
self.element = []
self.data = [] # this dictionary is to append all of the data to so it can be saved to a csv file
#self.circle_radius = []
# Initialize plot, label axis
self.fig, self.ax = plt.subplots()
self.ax.set_title("Fiber Tip Centers")
self.ax.set_xlabel("X")
self.ax.set_ylabel("Y")
self.canvas = FigureCanvasTkAgg(self.fig, master=self.root) #creates widget that allows for matplotlib
self.canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
def connect_camera(self):
# Initialize camera
env_filename = os.getenv('ZWO_ASI_LIB')
asi.init('C:\\Users\\ASE\\Desktop\\Ari Lab-2023\\Pics\\ASIStudio\\ASICamera2.dll')
self.num_cameras = asi.get_num_cameras() #ask how many cameras are connected
print('Camera connected')
if self.num_cameras == 0: #if no cameras are connected, return this message
messagebox.showerror("Error", "No cameras found")
self.root.destroy() # Closes the Tkinter window and exits the application
return
self.camera = asi.Camera(0) #initializes the camera and saves it to this variable, first camera selected
self.camera.set_control_value(asi.ASI_HIGH_SPEED_MODE, 1) #sets the contol value to enable high speed mode, 1 enables the mode
self.camera.set_roi() #sets range of intrest, roi is set for the entire image
self.camera_initialized = True # Set camera initialization flag
print('Camera Ready')
def connect_actuator(portname=ports[0]): #connects the actuator, sets a default value for the portname. selects the first available port
selected_port = portname #setting the serial port that will be used for connection
ser = serial.Serial( selected_port.device, 115200, timeout=1) #create connection, in this case portname was COM7
#self.ser.isOpen()
ser.flushInput() #clears any data that has been received but not yet read
ser.flushOutput() #clears any data that has been written but not yet transmitted
return ser #object can now be used to read and write to the serial port
ser=connect_actuator()
def amp_on(self): #turns on the preamp
test_str = 'amp_enable\r\n' #test string that will be sent to the piboard, formatted with newline character
print("OUT: " + test_str) # prints the command is being sent, debug
self.ser.write( test_str.encode('ascii') ) # convert the sting to bytes since serial connection requires this format
line = self.ser.readline().decode('utf-8') # reads a line of text from the serial port and decodes it. now we wait
while not line: #wait for pyboard to respond
line = self.ser.readline().decode('utf-8') #once a non-empty resonce is given, print
print( "In : " + line )
def amp_off(self): #turns off the preamp
test_str = 'amp_disable\r\n' #test string that will be sent to the piboard, formatted with newline character
print("OUT: " + test_str) # prints the command is being sent, debug
self.ser.write( test_str.encode('ascii') ) # convert the sting to bytes since serial connection requires this format
line = self.ser.readline().decode('utf-8') # reads a line of text from the serial port and decodes it. now we wait
while not line: #wait for pyboard to respond
line = self.ser.readline().decode('utf-8') #once a non-empty resonce is given, print
print( "In : " + line )
def set_DAC(self,x,y): #basically same thing as above, but different strings are inputed to the fta controller to oget back differnt outputs. there are both x and y inputs though
test_str = f'DAC_set,{x},{y}\r\n' #DAC is responcoble for the xy inputs of the FTA. can be values 0-4095
print("OUT: " + test_str)
self.ser.write( test_str.encode('ascii') )
line = self.ser.readline().decode('utf-8')
while not line: #wait for pyboard to respond
line = self.ser.readline().decode('utf-8')
print( "In : " + line )
element = int(self.selected_element.get()) # Get the element number from dropdown
self.dac_values.append((x, y)) # append the element and dac inputs
def update_coordinates(self): #reads the x and y values from the input and passes it throught the set_DAC function
try:
x = int(self.xcoord_entry.get()) #gets user input, which is from the text from the xy coordinate entry button, converts to integer so nothing gets messed up
y = int(self.ycoord_entry.get())
element = int(self.selected_element.get()) # Get the element number from dropdown
if self.coord_min <= x <= self.coord_max and self.coord_min <= y <= self.coord_max:
print(f"Setting DAC to X: {x}, Y: {y}") # make sure the values are in the accepted range
self.set_DAC(x,y) #if they are, call the DAC function to set the new coordinates
else:
messagebox.showerror("Error", f"Coordinates must be between {self.coord_min} and {self.coord_max}.") #return error if integer is too big or too small
except ValueError: #return error if xy values cannot be converted to integers
messagebox.showerror("Error", "Invalid input! Please enter numeric values.")
def toggle_amp(self, *args): # toggle function for the enable/disable preamps button. *args accepts any number of arguements
if self.amps_var.get(): #retrieves the current value of self.amps_var, since the boolean is originally false, it should start out disabled
self.amp_on() #if returns true, preamp will be enabled
else:
self.amp_off() #if returns false, preamp will be disabled
return
def dummy_command(self): # Placeholder for future functionality, debug
print("Button clicked")
def capture_image_from_camera(self, max_retries=5, retry_delay=2): # this function is what tells the camera to taka a photo and return the caputred image
"""Capture an image from the camera with retry mechanism if the first attempt fails."""
if not self.camera_initialized:
messagebox.showerror("Error", "Camera is not connected.")
self.connect_camera()
return
attempt = 0
while attempt < max_retries:
try:
# Print status and attempt capture
print(f'Attempt {attempt + 1} of {max_retries} to capture image.')
# Set exposure time (in microseconds)
exposure_time = 340000
self.camera.set_control_value(asi.ASI_EXPOSURE, exposure_time)
# Define the gain value (adjust as needed, example value)
gain_value = 75 # This could be a value between 0 and 600 for this camera
self.camera.set_control_value(asi.ASI_GAIN, gain_value)
print('Capturing Image...')
sleep(1)
# Capture image
original_image = self.camera.capture()
self.original_image = original_image
self.display_image(original_image, self.frame_original)
print("Image captured successfully.")
return # Exit if image capture was successful
except Exception as e:
# Handle exceptions and retry
print(f"Error capturing image: {e}")
attempt += 1
if attempt < max_retries:
print(f"Retrying in {retry_delay} seconds...")
sleep(retry_delay)
else:
messagebox.showerror("Error", "Failed to capture image from camera after multiple attempts.")
def process_image(self): #function that find circles in the image as well as their centers
if self.original_image is None: #return error if no image is present to be processed
messagebox.showerror("Error", "No image captured")
return
#start_time = time.time()
output = self.original_image.copy() #creates a copy to be worked on so the original stays the same
# Gaussian Blurring of the image, (5,5) is the blur size and 0 is the standard deviation
blur_image = cv2.GaussianBlur(self.original_image, (5, 5), 0)
# Using OpenCV Canny Edge detector to detect edges, 75 and 150 are thresholds
edged_image = cv2.Canny(blur_image, 75, 150)
height, width = edged_image.shape
radii = 100
acc_array = np.zeros(((height, width, radii))) #this stores an array of zeros, the array is used to vote for potential circles
filter3D = np.zeros((30, 30, radii)) #creates a 3d filter array used to porcess and detect circles based on the accumulator array
filter3D[:, :, :] = 1 #prepares arrays for the circle detection function
#This function fills an accumulator array to keep track of possible circle centers based on the detected edges
def fill_acc_array(x0, y0, radius):
x = radius
y = 0
decision = 1 - x
while y < x:
if x + x0 < height and y + y0 < width:
acc_array[x + x0, y + y0, radius] += 1
if y + x0 < height and x + y0 < width:
acc_array[y + x0, x + y0, radius] += 1
if -x + x0 < height and y + y0 < width:
acc_array[-x + x0, y + y0, radius] += 1
if -y + x0 < height and x + y0 < width:
acc_array[-y + x0, x + y0, radius] += 1
if -x + x0 < height and -y + y0 < width:
acc_array[-x + x0, -y + y0, radius] += 1
if -y + x0 < height and -x + y0 < width:
acc_array[-y + x0, -x + y0, radius] += 1
if x + x0 < height and -y + y0 < width:
acc_array[x + x0, -y + y0, radius] += 1
if y + x0 < height and -x + y0 < width:
acc_array[y + x0, -x + y0, radius] += 1
y += 1
if decision <= 0:
decision += 2 * y + 1
else:
x = x - 1
decision += 2 * (y - x) + 1
edges = np.where(edged_image == 255)
global circle_radius #make value global so it can be used in another function
circle_radius = int()
for i in range(len(edges[0])):
x = edges[0][i]
y = edges[1][i]
for radius in range(20, 55): # seeks range of potential cirles with a raduis between 20 and 55
fill_acc_array(x, y, radius)
i = 0 #this block analyzes the accumulator array to find and draw circles
j = 0
while i < height - 30: #move a 30 by 30 window over the image, identifies the maximum value in filter3D
while j < width - 30:
filter3D = acc_array[i:i+30, j:j+30, :] * filter3D
max_pt = np.where(filter3D == filter3D.max())
a = int(max_pt[0][0])
b = int(max_pt[1][0])
c = int(max_pt[2][0])
b = b + j
a = a + i
if filter3D.max() > 90:
if (b, a) not in self.circle_centers:
cv2.circle(output, (b, a), c, (0, 255, 0), 2)
cv2.circle(output, (b, a), 3, (0, 0, 255), -1)
self.circle_centers.append((b, a)) # Store detected circle centers
circle_radius = c # this code is breaking at the moment and i dont know why
#print(f"Circle Centers After Append: {self.circle_centers}") #debugging check
j = j + 30
filter3D[:, :, :] = 1
j = 0
i = i + 30
# Add to data storage with element number
element = int(self.selected_element.get()) #get element number
self.element.append(element)
print(f'FTA #{element}')
print(f"Recorded circle centers: {self.circle_centers}")
# sanity check
fiber_size = 125 #microns
pixel_size = fiber_size / (circle_radius * 2)
print("Detected circle radii:",circle_radius, 'Pixels') #prints the radius of detected cirles in pixles
print("Detected pixel size", pixel_size , 'microns/pixel') #prints how large earch pixle is in microns
self.circle_radii.append(circle_radius)
self.pixle_size.append(pixel_size)
color_mapped_image = cv2.applyColorMap(output, cv2.COLORMAP_PLASMA) # changes the output photo to plasma color map(looks cool)
self.processed_image = color_mapped_image #assign value to new image so it can be displayed. idk why i did this i realize now that this is not needed
self.display_image(self.processed_image, self.frame_processed)
self.update_plot() # Update the existing plot with new data
if len(self.circle_centers) > 1:
previous_center = self.circle_centers[-2] # second most recent input in the list
current_center = self.circle_centers[-1] #most recent input in the list
movement_x = abs(current_center[0] - previous_center[0]) # movement will be the current center minus the previous ceneter
movement_y = abs(current_center[1] - previous_center[1]) # same but take the second value of the 2d matrix
microns_movedx = movement_x * pixel_size # use calculated pixel size to find the amount of microns moved in the x and y dir
microns_movedy = movement_y * pixel_size
self.movement_in_x.append(movement_x) #save values of x and y that the fiber has moved (pixels)
self.movement_in_y.append(movement_y)
self.microns_moved_in_x.append(microns_movedx) # save values for the amount of microns moved, using the conversion between microns and pixels
self.microns_moved_in_y.append(microns_movedy)
print(f"Movement -- X: {movement_x} pixels, Y: {movement_y} pixels")
print(f'Microns Moved X: {self.microns_moved_in_x}')
print(f'Microns Moved Y: {self.microns_moved_in_y}')
if len(self.dac_values) > 1: # this is to use the saved values from the circle centers and the xy coords
previous_DAC = self.dac_values[-2] # use the second to last input to find the previous dac count
current_DAC = self.dac_values[-1] # use the last input to find the current dac count
delta_DACx = abs(current_DAC[0]-previous_DAC[0]) # subtract these for both the x and y to find the change in dac
delta_DACy = abs(current_DAC[1]-previous_DAC[1])
print(f'Dac values{self.dac_values}')
print(f'Change in DAC {delta_DACx} in X and {delta_DACy} in Y')
if delta_DACx != 0:
microns_per_ADUx = microns_movedx / delta_DACx
else:
microns_per_ADUx = 0
if delta_DACy != 0:
microns_per_ADUy = microns_movedy / delta_DACy
else:
microns_per_ADUy = 0
self.microns_per_ADU_in_x.append(microns_per_ADUx)
self.microns_per_ADU_in_y.append(microns_per_ADUy)
print(f'Microns/ADU X: {self.microns_per_ADU_in_x}')
print(f'Microns/ADU Y: {self.microns_per_ADU_in_y}')
def clear_all_data(self):
# Reset all lists and variables
self.original_image = None
self.processed_image = None
self.circle_centers = []
self.dac_values = []
self.movement_in_x = []
self.movement_in_y = []
self.microns_moved_in_x = []
self.microns_moved_in_y = []
self.microns_per_ADU_in_x = []
self.microns_per_ADU_in_y = []
self.DACx = []
self.data = []
self.circle_radii = []
self.pixle_size = []
self.element = []
# Optionally clear the plots
self.ax.clear()
self.ax.set_title("Fiber Tip Centers")
self.ax.set_xlabel("X")
self.ax.set_ylabel("Y")
self.canvas.draw()
print("All data cleared.")
def display_image(self, image, frame):
scale = 0.25 #allows you to change the size of the image in the tkinter window
h, w = image.shape[:2]
new_w = int(w * scale)
new_h = int(h * scale)# Resizes image based on mulitplier
resized_image = cv2.resize(image, (new_w, new_h), interpolation=cv2.INTER_AREA)
resized_image = cv2.cvtColor(resized_image, cv2.COLOR_BGR2RGB)
image_pil = Image.fromarray(resized_image)
image_tk = ImageTk.PhotoImage(image_pil)
# Create image on canvas
if hasattr(frame, 'canvas'):
frame.canvas.delete("all")
else:
frame.canvas = tk.Canvas(frame, width=new_w, height=new_h, bg='white')
frame.canvas.pack()
frame.canvas.create_image(0, 0, anchor=tk.NW, image=image_tk)
frame.canvas.image = image_tk
def update_plot(self): #actively updates the plot as the new images are processed
self.ax.set_title("Fiber Tip Centers")
self.ax.set_xlabel("X")
self.ax.set_ylabel("Y")
plt.gca().invert_xaxis()
plt.gca().invert_yaxis()
# Extract X and Y coordinates of circle centers
if self.circle_centers:
xs, ys = zip(*self.circle_centers)
self.ax.scatter(xs, ys, color='red')
#self.ax.invert_yaxis() #able to flip y axis if the picture and plot dont match
# Draw the updated plot
self.canvas.draw()
def automate_process_rough(self): # manual brute force way of moving the fiber tip
#min is 559 for x and y dac values
#max is 3537 for x and y dac values
print('Automation Started...')
print('Taking photo')
self.capture_image_from_camera()
print('Processing Image')
sleep(2)
self.process_image()
print('Image processed, waiting for next round')
sleep(10)
print('Turning Amp On')
self.amp_on()
print('Taking photo')
self.capture_image_from_camera()
print('Processing Image')
sleep(2)
self.process_image()
print('Image processed, waiting for next round')
sleep(10)
self.set_DAC(2047, 2047)
print('Waiting for Fiber Tip to Move... ')
sleep(10)
print('Taking photo')
self.capture_image_from_camera()
print('Processing Image')
sleep(2)
self.process_image()
print('Image processed, waiting for next round')
sleep(10)
self.set_DAC(559, 559)
print('Waiting for Fiber Tip to Move... ')
sleep(20)
print('Taking photo')
self.capture_image_from_camera()
print('Processing Image')
sleep(2)
self.process_image()
print('Image processed, waiting for next round')
sleep(10)
self.set_DAC(3537,559)
print('Waiting for Fiber Tip to Move... ')
sleep(20)
print('Taking photo')
self.capture_image_from_camera()
print('Processing Image')
sleep(2)
self.process_image()
print('Image processed, waiting for next round')
sleep(10)
self.set_DAC(3537, 3537)
print('Waiting for Fiber Tip to Move... ')
sleep(20)
print('Taking photo')
self.capture_image_from_camera()
print('Processing Image')
sleep(2)
self.process_image()
sleep(10)
self.set_DAC(559, 3537)
print('Waiting for Fiber Tip to Move... ')
sleep(20)
print('Taking photo')
self.capture_image_from_camera()
print('Processing Image')
sleep(2)
self.process_image()
sleep(10)
self.set_DAC(2047,2047)
print('Waiting for Fiber Tip to Move... ')
sleep(12)
print('Taking photo')
self.capture_image_from_camera()
print('Processing Image')
sleep(2)
self.process_image()
sleep(8)
for i in range(600,3500,200):
self.set_DAC(i,2047)
sleep(5)
print('Taking photo')
self.capture_image_from_camera()
print('Processing Image')
self.process_image()
sleep(8)
for i in range(600,3500,200):
self.set_DAC(2047,i)
sleep(5)
print('Taking photo')
self.capture_image_from_camera()
print('Processing Image')
self.process_image()
sleep(8)
self.amp_off()
self.capture_image_from_camera()
print('Processing Image')
sleep(2)
self.process_image()
print('Automation Done')
def save_data(self):
# Initialize Tkinter root (necessary for the file dialog)
root = tk.Tk()
root.withdraw() # Hide the root window
# Open a file dialog to choose the save location and filename
filename = filedialog.asksaveasfilename(
defaultextension=".csv",
filetypes=[("CSV files", "*.csv"), ("All files", "*.*")],
title="Save Data As"
)
# Check if the user canceled the dialog
if not filename:
messagebox.showinfo("Save Data", "Save canceled.")
return
# Prepare the data for CSV
headers = [
'X Coord', 'Y Coord', 'X DAC Value', 'Y DAC Value',
'Movement X (pixels)', 'Movement Y (pixel)',
'Microns Moved X', 'Microns Moved Y',
'Microns Per ADU X', 'Microns Per ADU Y',
'Detected Circle Radii (pixel)', 'Detected Pixel Size (microns/pixel)'
]
# Prepare the data for CSV
data_to_save = []
num_entries = len(self.circle_centers)
x_values = [x for x, y in self.circle_centers]
y_values = [y for x, y in self.circle_centers]
# Initialize DAC values with "N/A" for the first two entries
x_dacvalue = ["N/A", "N/A"] + [x for x, y in self.dac_values]
y_dacvalue = ["N/A", "N/A"] + [y for x, y in self.dac_values]
# Initialize movement metrics with "N/A" for the first values
movement_x = ["N/A"] + self.movement_in_x
movement_y = ["N/A"] + self.movement_in_y
# Initialize microns moved metrics with "N/A" for the first values
microns_moved_x = ["N/A"] + self.microns_moved_in_x
microns_moved_y = ["N/A"] + self.microns_moved_in_y
# Initialize Microns Per ADU with "N/A" for the first three entries
microns_per_ADU_x = ["N/A", "N/A"] + self.microns_per_ADU_in_x
microns_per_ADU_y = ["N/A", "N/A"] + self.microns_per_ADU_in_y
for i in range(num_entries):
# Collect data for each row
data_row = {
'X Coord': x_values[i] if i < len(x_values) else "N/A",
'Y Coord': y_values[i] if i < len(y_values) else "N/A",
'Movement X (pixels)': movement_x[i] if i < len(movement_x) else "N/A",
'Movement Y (pixel)': movement_y[i] if i < len(movement_y) else "N/A",
'Microns Moved X': microns_moved_x[i] if i < len(microns_moved_x) else "N/A",
'Microns Moved Y': microns_moved_y[i] if i < len(microns_moved_y) else "N/A",
'X DAC Value': x_dacvalue[i] if i < len(x_dacvalue) else "N/A",
'Y DAC Value': y_dacvalue[i] if i < len(y_dacvalue) else "N/A",
'Microns Per ADU X': microns_per_ADU_x[i] if i < len(microns_per_ADU_x) else "N/A",
'Microns Per ADU Y': microns_per_ADU_y[i] if i < len(microns_per_ADU_y) else "N/A",
'Detected Circle Radii (pixel)': self.circle_radii[i] if i < len(self.circle_radii) else "N/A",
'Detected Pixel Size (microns/pixel)': self.pixle_size[i] if i < len(self.pixle_size) else "N/A"
}
data_to_save.append(data_row)
# Write the data to the CSV file
if data_to_save:
try:
with open(filename, 'w', newline='') as file:
writer = csv.DictWriter(file, fieldnames=headers)
writer.writeheader()
writer.writerows(data_to_save)
messagebox.showinfo("Save Data", f"Data successfully saved to {filename}")
except Exception as e:
messagebox.showerror("Save Data", f"Error saving data: {e}")
root.destroy() # Destroy the hidden root window after saving
# Make sure to include the rest of your class methods and initialization here
def __del__(self):
# Cleanup the camera on application close
if hasattr(self, 'ser'):
self.amp_off() # failsafe to make sure the preamp is always turned off when closing
self.ser.close()
if hasattr(self, 'camera'):
self.camera.close()
#asi.exit()
self.root.destroy() # Closes the Tkinter window and exits the application
if __name__ == "__main__":
root = tk.Tk()
app = ImageProcessorApp(root)
root.mainloop() #runs the app
# Problems
# the update plot does not keep the axis flipped after updating them
# the camera does not capture a photo on the first try and sometimes code needs to be rerun ----> patched but not fixed
# still have not gotten the DAC x and y values to go through, 4 arguements instead of three ----> fixed
# DAC values are not updated when the dac changes
# Zwo ASI mini specs
#Resolution : 1936 x 1096
# 2.9 micron pixel size or 0.0029mm or 2.9e-6 m
# 32 microsecond to 2000s exposure time
# Focal Distace : 8.5mm or 0.0084m
# Thin Lens Equation
#we know the focal length
# we dont know how far away the object is from the camera
# but if we know that the size of the pixles from the camera are 2.9 microns/pixel
# we can find the magnification of the image
# the magninfiaction of the image should tell us how far the object is away fron the lense
# Gameplan
#find the calibration of the microns to pixles. we know the size of the clading, and cointed the pixle raduis
#have the DAC go through a set of steps . X and Y values, take a step, wait, then take a picture and process it.\
# continue this and save the x and y coordinates that are accociated with the DAC values
#find how much microns the fiber has moved from each step. find the microns/ADU(which is the 0-4095 number we inputted) for both x and y
# How do we want to store data?
# definetly in a csv file
# I think it should keep track of all of the raw data it can
# we want: what element we are testing, x and y coordinates, DAC values set, x and y movements coorelated to the DAC set (both in pixles and in microns)
# size_of_fiber = 125e-6 # meters
# magnification = camera_fiber_size / size_of_fiber
# image_distance = 2 # just putting in random numbers
# focal_length = .0084
# object_distance = (1/focal_length) * (1-(size_of_fiber/camera_fiber_size))
# print(object_distance)