Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Photom_v2 #183

Open
wants to merge 62 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
1315c25
adding registration and initial yml config files
edyoshikun Nov 5, 2023
b06911a
saving function using the model
edyoshikun Nov 5, 2023
022beb8
demo moving window and reading yml to make gui
edyoshikun Nov 7, 2023
384f3c8
adding transparency and label power values
edyoshikun Nov 7, 2023
b1357c4
making mirror widget be based on the number of mirror objects in config
edyoshikun Nov 7, 2023
d9e1d5e
switch from calibration to shooting mode
edyoshikun Nov 8, 2023
3c7b185
add mock laser positions
edyoshikun Nov 8, 2023
57f83fa
calibration procedure with demo
edyoshikun Nov 8, 2023
76e5429
demo calibration with mock mirror
edyoshikun Nov 30, 2023
a512171
basic connections between laser control UI and laser marker
edyoshikun Dec 1, 2023
7f17fd4
add cancel calibration button and fix saving of matrix
edyoshikun Dec 1, 2023
60071cd
updating mock laser and mirror for debugging to react to UI
edyoshikun Dec 1, 2023
95853f0
mock laser initializiation bug and calibratoin buttons fix to not app…
edyoshikun Dec 1, 2023
012b039
add demo config for lasers
edyoshikun Dec 1, 2023
ac7f824
initial fixes to in-person test
edyoshikun Dec 7, 2023
a8249a6
adding calibration and movable demo
edyoshikun Dec 8, 2023
b6cf5e0
moving calculating_rectangle to scanning utils
edyoshikun Dec 12, 2023
76b6dab
fixing mirror and position mapping.
edyoshikun Dec 12, 2023
3cccddd
added a center offset for the calibration
edyoshikun Dec 13, 2023
8d25d46
added a center offset for the calibration
edyoshikun Dec 13, 2023
a0918c3
Merge branch 'photom_v2' of https://github.com/czbiohub-sf/coPylot in…
edyoshikun Dec 13, 2023
dce6e32
reverting comming and adding proper centering
edyoshikun Dec 13, 2023
858b5e7
connected the mirror to the GUI
edyoshikun Dec 13, 2023
74de070
add qtthread
edyoshikun Jan 5, 2024
e6203c6
Merge pull request #184 from czbiohub-sf/calibration_thread
edyoshikun Jan 5, 2024
ba31102
pseudocode for testing calibration
edyoshikun Jan 5, 2024
a6edde2
mouse tracking fixed using qeventfilter
edyoshikun Jan 18, 2024
56a487e
recentering the marker and the coordinates fixed
edyoshikun Jan 18, 2024
2879bce
calibration and mouse events fixed
edyoshikun Jan 19, 2024
abd9c24
adding partial laser controls with gui
edyoshikun Jan 25, 2024
0adf31f
adding flipping of camera sensor and pixel format for the flir camera.
edyoshikun Feb 7, 2024
312b722
adding demo auto-calibration for testing
edyoshikun Feb 7, 2024
e403c52
addin functiongs for auto calibration
edyoshikun Feb 7, 2024
6fa5f47
adding minor fixes to bugs from the remote pesudocoding
edyoshikun Feb 7, 2024
e8fabb5
fixed the autocalibration scanning pattern
edyoshikun Feb 7, 2024
2ffb7aa
adding arduino PWM
edyoshikun Feb 8, 2024
042b139
adding ability to set the laser power
edyoshikun Feb 8, 2024
ac47b83
add arduino the GUI
edyoshikun Feb 8, 2024
2203c24
adding code to run the PWM without the CLI
edyoshikun Feb 8, 2024
3f20e74
adding the arduino code for versioning
edyoshikun Feb 8, 2024
34e986f
fixing the bug that was flipping between (xy) to (yx) order dimensions
edyoshikun Feb 8, 2024
38c2072
fxing the laser GUI for custom laser control. adding demo gui for the…
edyoshikun Feb 8, 2024
353112e
fixed the right click laser toggle
edyoshikun Feb 14, 2024
1e152d9
added code to close both windows if one is closed
edyoshikun Feb 14, 2024
2569b30
added the dashed lines around some confidence ROI
edyoshikun Feb 14, 2024
4633b75
-adding more affine_transform functions to get and set matrix
edyoshikun Mar 2, 2024
091b674
removing flir unnecessary id that is nto implemented
edyoshikun Mar 2, 2024
3ab2dac
adding dynamic resizing of the window and update to the affine transform
edyoshikun Mar 2, 2024
87b59a4
- bug on affine_transform returning T_affine
edyoshikun Mar 4, 2024
371c4da
automatically set laser to pulse mode if using arduino
edyoshikun Mar 4, 2024
2b75d83
-fix marker recentering
edyoshikun Mar 4, 2024
165aece
fixing marker recentering issue
edyoshikun Mar 4, 2024
7058745
splittting gui into file tree.
edyoshikun Mar 5, 2024
b020e95
fixing marker offset
edyoshikun Mar 5, 2024
ec822c2
added a game button
edyoshikun Mar 6, 2024
d43081e
-fix bug setting arduino settings
edyoshikun Mar 8, 2024
ffc5c4f
fixing moving marker clickable areas
edyoshikun Mar 9, 2024
349e1b0
-renaming pwm settings for better comprehension
edyoshikun Mar 9, 2024
2a1856f
adding tracing prototype
edyoshikun Mar 9, 2024
c9a982d
relative import based on package structure
edyoshikun Mar 26, 2024
bc79429
photom v2 roi selection for ablation demo (#185)
aaronalvarezcz Jul 26, 2024
0e82ee3
making the demo mode to False
edyoshikun Aug 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -133,4 +133,5 @@ ToDo.txt
defaults.txt

copylot/_version.py
.DS_Store
.DS_Store
.vscode/settings.json
Empty file.
4 changes: 4 additions & 0 deletions copylot/assemblies/photom/demo/affine_T.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
affine_transform_yx:
- [1.0, 0.0, 0.0]
- [0.0, 1.0, 0.0]
- [0.0, 0.0, 1.0]
4 changes: 4 additions & 0 deletions copylot/assemblies/photom/demo/affine_T_2.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
affine_transform_yx:
- [2.0, 0.0, 0.0]
- [0.0, 2.0, 0.0]
- [0.0, 0.0, 1.0]
17 changes: 17 additions & 0 deletions copylot/assemblies/photom/demo/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
lasers:
- name: laser_1
power: 50
- name: laser_2
power: 30

mirrors:
- name: mirror_1_VIS
COM_port: COM8
x_position: 0
y_position: 0
affine_matrix_path: ./copylot/assemblies/photom/demo/affine_T.yml
- name: mirror_2_IR
COM_port: COM9
x_position: 0
y_position: 0
affine_matrix_path: ./copylot/assemblies/photom/demo/affine_T_2.yml
64 changes: 64 additions & 0 deletions copylot/assemblies/photom/demo/demo_affine_scan.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# %%
import sys
import os

sys.path.append(os.path.join(os.pardir, os.pardir, os.pardir))
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt

from copylot.assemblies.photom.utils.scanning_algorithms import ScanAlgorithm
from copylot.assemblies.photom.utils.affine_transform import AffineTransform

mpl.rcParams['agg.path.chunksize'] = 20000

# %%
initial_coord = (549.5, 754.5)
size = (300, 100)
gap = 10
shape = 'disk'
sec_per_cycle = 0.003
sg = ScanAlgorithm(initial_coord, size, gap, shape, sec_per_cycle)

# coord = sg.generate_cornerline()
coord = sg.generate_lissajous()
# coord = sg.generate_sin()

plt.figure()
plt.plot(coord[0], coord[1])
plt.show()

# Load affine matrix
trans_obj = AffineTransform(config_file='../settings/affine_transform.yml')

# %%
# compute affine matrix
xv, yv = np.meshgrid(
np.linspace(1, 10, 10),
np.linspace(1, 10, 10),
)
coord = (list(xv.flatten()), list(yv.flatten()))
pts1 = [
[xv[0, 0], yv[0, 0]],
[xv[-1, -1], yv[-1, -1]],
[xv[0, -1], yv[0, -1]],
]

pts2 = [
[xv[0, 0] + 1, yv[0, 0] + 1],
[xv[-1, -1] + 1, yv[-1, -1] + 1],
[xv[0, -1] + 1, yv[0, -1] + 1],
]
trans_obj.compute_affine_matrix(pts1, pts2)
# %%
trans_obj.save_matrix(config_file='./test.yml')
# %%
data_trans = trans_obj.apply_affine(coord)

plt.figure()
plt.plot(coord[0], coord[1])
plt.plot(data_trans[0], data_trans[1])
plt.legend(['raw', 'transformed'])
plt.show()

# %%
106 changes: 106 additions & 0 deletions copylot/assemblies/photom/demo/demo_arduino_pwm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# %%
from copylot.assemblies.photom.photom import PhotomAssembly
from copylot.assemblies.photom.utils import affine_transform
from copylot.hardware.mirrors.optotune.mirror import OptoMirror

# from copylot.assemblies.photom.photom_mock_devices import MockLaser, MockMirror
from copylot.hardware.lasers.vortran.vortran import VortranLaser
from copylot.hardware.cameras.flir.flir_camera import FlirCamera
import time
from copylot.assemblies.photom.utils.arduino import ArduinoPWM

# %%
config_file = './photom_VIS_config.yml'
# Mock imports for the mirror and the lasers
laser = VortranLaser('Mock Laser', port='COM9')
mirror = OptoMirror(com_port='COM8')
cam = FlirCamera()
cam.open()

# TODO: modify COM port based on the system
arduino = ArduinoPWM(serial_port='COM10', baud_rate=115200)

# %%
# Make the photom device
photom_device = PhotomAssembly(
laser=[laser],
mirror=[mirror],
affine_matrix_path=[r'./affine_T.yml'],
camera=[cam],
)

# %%
# Perform calibration
# Top-left and Bottom-right corners of the mirror ROI
mirror_roi = [
[
0.018,
-0.005,
], # [x,y]
[
0.0,
0.013,
],
] # [x,y]

photom_device.camera[0].exposure = 10_000 # [us]
photom_device.camera[0].gain = 0
# photom_device.camera[0].pixel_format = 'Mono16'

config_file = r'./test_auto_affineT.yml'

photom_device.laser[0].power = 5 # [mW]
photom_device.laser[0].pulse_mode = 0
photom_device.laser[0].toggle_emission = True
photom_device.calibrate_w_camera(
mirror_index=0,
camera_index=0,
rectangle_boundaries=mirror_roi,
grid_n_points=5,
config_file=config_file,
save_calib_stack_path='./calib_stack',
verbose=True,
)
photom_device.laser[0].toggle_emission = False

# %%
# Remove the camera from the photom_device so we can use it in the acquisiton engine
photom_device.camera[0].exposure = 10_000 # [us]
photom_device.camera = []


# %%
# Demo that laser should be in the center
photom_device.laser[0].toggle_emission = True
photom_device.set_position(0, [1024, 1224]) # center [y,x]
time.sleep(3)
photom_device.laser[0].toggle_emission = False
# %%
# Set the ablation parameters
# Test the PWM signal
duty_cycle = 50 # [%] (0-100)
period_ms = 500 # [ms]
total_duration_ms = 5_000 # [ms] total time to run the PWM signal
reps = 2
time_interval_s = 3
photom_device.laser[0].pulse_power = 10 # [%]

# %%
# Ablate the cells
# photom_device.set_position(0, [1024, 1224]) # center [y,x]
photom_device.set_position(0, [0, 0]) # edge [y,x] might not be visble
photom_device.laser[0].toggle_emission = 1
time.sleep(0.2)
photom_device.laser[0].pulse_mode = 1 # True enabled, False disabled
time.sleep(0.2)
frequency = 1000.0 / period_ms # [Hz]
arduino.set_pwm(duty_cycle, frequency, total_duration_ms)
arduino.start_timelapse(repetitions=reps, time_interval_s=time_interval_s)
# %%
photom_device.laser[0].pulse_mode = 0 # True enabled, False disabled
time.sleep(0.2)
photom_device.laser[0].toggle_emission = 0
time.sleep(0.2)
photom_device.laser[0].power = 5.0 # [mW]

# %%
106 changes: 106 additions & 0 deletions copylot/assemblies/photom/demo/demo_calibration_camera.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# %%
import numpy as np
from scipy.ndimage import median_filter
from skimage.feature import peak_local_max
import napari
from skimage import measure, filters
from skimage.filters import gaussian
import os
from skimage.morphology import remove_small_objects


# %%
os.environ["DISPLAY"] = ":1002"
viewer = napari.Viewer()

# %%


def find_objects_centroids(
image, sigma=5, threshold_rel=0.5, min_distance=10, min_area=3
):
"""
Calculate the centroids of blurred objects in an image, excluding hot pixels or small artifacts.

Parameters:
- image: 2D numpy array, single frame from the fluorescence microscopy data.
- sigma: Standard deviation for Gaussian filter to smooth the image.
- threshold_rel: Relative threshold for detecting peaks. Adjust according to image contrast.
- min_distance: The minimum distance between peaks detected.
- min_area: The minimum area of an object to be considered valid.

Returns:
- centroids: (N, 2) array where each row is (row, column) of an object's centroid.
"""
# Smooth the image to enhance peak detection
smoothed_image = gaussian(image, sigma=sigma)

# Thresholding to isolate objects
threshold_value = filters.threshold_otsu(smoothed_image)
binary_image = smoothed_image > threshold_value * threshold_rel

# Remove small objects (hot pixels or small artifacts) from the binary image
cleaned_image = remove_small_objects(binary_image, min_size=min_area)

# Label the cleaned image to identify distinct objects
label_image = measure.label(cleaned_image)
properties = measure.regionprops(label_image)

# Calculate centroids of filtered objects
centroids = [prop.centroid for prop in properties if prop.area >= min_area]

return np.array(centroids)


# %%
# Define the rectangle and grid parameters
n_points = 5 # Number of points per row/column in the grid
rect_start_x, rect_start_y = 20, 20
rect_end_x, rect_end_y = 80, 80
frame_width, frame_height = 100, 100
n_frames = 25 # Total number of frames
px = 4 # Size of the point in the grid
# Calculate intervals between points in the grid
interval_x = (rect_end_x - rect_start_x) // (n_points - 1)
interval_y = (rect_end_y - rect_start_y) // (n_points - 1)

# Initialize frames
frames = np.zeros((n_frames, frame_width, frame_height), dtype=np.float32)
# Store the coordinates of the grid points per frame
center_coords = np.zeros((n_frames, 2), dtype=np.float32)

# Populate frames with the grid points
for i in range(n_points):
for j in range(n_points):
frame_index = i * n_points + j
x = rect_start_x + j * interval_x
y = rect_start_y + i * interval_y
center_coords[frame_index] = [y, x]
# center_coords[frame_index, i, j] = [x, y]
frames[
frame_index, y - px // 2 : y + px // 2, x - px // 2 : x + px // 2
] = 100 # Set the point to white

for i in range(frames.shape[0]):
frames[i] = gaussian(frames[i], sigma=4)

viewer.add_image(frames)

print(center_coords)
# %%
centroids = []
for i in range(frames.shape[0]):
coords = find_objects_centroids(
frames[i], sigma=3, threshold_rel=0.5, min_distance=10
)
centroids.append([i, np.round(coords[0][0]), np.round(coords[0][1])])

centroids = np.array(centroids)
viewer.add_points(centroids, size=5, face_color="red")
print(centroids)

# %%
# compare with the ground truth
assert np.allclose(centroids[:, 1:], center_coords, atol=1)

# %%
59 changes: 59 additions & 0 deletions copylot/assemblies/photom/demo/demo_draw_tetragon.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import sys
from PyQt5.QtWidgets import (
QApplication,
QGraphicsView,
QGraphicsScene,
QGraphicsEllipseItem,
QMainWindow,
)
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPolygonF, QPen


class TetragonEditor(QMainWindow):
def __init__(self):
super().__init__()

self.setWindowTitle("Tetragon Editor")
self.setGeometry(100, 100, 800, 600)

# Create a QGraphicsView to display the scene
self.view = QGraphicsView(self)
self.view.setGeometry(10, 10, 780, 580)

# Create a QGraphicsScene
self.scene = QGraphicsScene()
self.view.setScene(self.scene)

# Create initial vertices for the tetragon
self.vertices = []
for x, y in [(100, 100), (200, 100), (200, 200), (100, 200)]:
vertex = QGraphicsEllipseItem(x - 5, y - 5, 10, 10)
vertex.setBrush(Qt.red)
vertex.setFlag(QGraphicsEllipseItem.ItemIsMovable)
self.vertices.append(vertex)
self.scene.addItem(vertex)

def getCoordinates(self):
return [vertex.pos() for vertex in self.vertices]

def resizeEvent(self, event):
# Resize the view when the main window is resized
self.view.setGeometry(10, 10, self.width() - 20, self.height() - 20)

def updateVertices(self, new_coordinates):
for vertex, (x, y) in zip(self.vertices, new_coordinates):
vertex.setPos(x, y)


if __name__ == '__main__':
import os

os.environ["DISPLAY"] = ":1005"
app = QApplication(sys.argv)
window = TetragonEditor()
# print(window.getCoordinates())
# window.updateVertices([(10, 10), (300, 200), (0, 300), (200, 300)])
# print(window.getCoordinates())
window.show()
sys.exit(app.exec_())
Loading
Loading