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

Non live display #148

Merged
merged 5 commits into from
Jul 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ pandas==1.3.5
pandastable==0.12.2.post1
opencv-python==4.5.5.62
numpy==1.22.0; sys_platform != "darwin"
numpy==1.21.6; sys_platform == "Darwin"
numpy==1.21.6; sys_platform == "darwin"
scikit-image==0.19.1
tensorflow==2.9.1
tensorflow==2.9.1; sys_platform != "darwin"
cupy-cuda112==10.2.0; sys_platform != "darwin"
zarr==2.11.3
fsspec
1 change: 0 additions & 1 deletion src/aslm/config/configuration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ CameraParameters:
delay_percent: 10
pulse_percent: 1
line_interval: 0.000075
display_live_subsampling: 4
display_acquisition_subsampling: 4
average_frame_rate: 4.969
frames_to_average: 1
Expand Down
6 changes: 1 addition & 5 deletions src/aslm/controller/aslm_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,7 @@ def __init__(self,
self.initialize_menus()

# Set view based on model.experiment
self.experiment = session(experiment_path,
args.verbose)
self.experiment = session(experiment_path, args.verbose)
self.populate_experiment_setting()

# Camera View Tab
Expand Down Expand Up @@ -691,8 +690,6 @@ def capture_image(self,
'z-stack', ...
"""
self.camera_view_controller.image_count = 0
active_channels = [channel[-1] for channel in self.experiment.MicroscopeState['channels'].keys()]
num_channels = len(active_channels)

# Start up Progress Bars
images_received = 0
Expand Down Expand Up @@ -722,7 +719,6 @@ def capture_image(self,
self.camera_view_controller.display_image(
image=self.data_buffer[image_id],
microscope_state=self.experiment.MicroscopeState,
channel_id=active_channels[image_id % num_channels],
images_received=images_received)
images_received += 1

Expand Down
208 changes: 140 additions & 68 deletions src/aslm/controller/sub_controllers/camera_view_controller.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
"""
ASLM sub-controller for the camera image display.

Copyright (c) 2021-2022 The University of Texas Southwestern Medical Center.
"""Copyright (c) 2021-2022 The University of Texas Southwestern Medical Center.
All rights reserved.

Redistribution and use in source and binary forms, with or without
Expand Down Expand Up @@ -89,6 +86,9 @@ def __init__(self,
# Left Click Binding
self.canvas.bind("<Button-1>", self.left_click)

# Slider Binding
self.view.slider.slider_widget.bind("<Button-1>", self.slider_update)

# Mouse Wheel Binding
if platform.system() == 'Windows':
self.canvas.bind("<MouseWheel>", self.mouse_wheel)
Expand Down Expand Up @@ -130,7 +130,6 @@ def __init__(self,
self.image_count = 0
self.temp_array = None
self.rolling_frames = 1
self.live_subsampling = self.parent_controller.configuration.CameraParameters['display_live_subsampling']
self.bit_depth = 8 # bit-depth for PIL presentation.
self.zoom_value = 1
self.zoom_x_pos = 0
Expand All @@ -139,13 +138,24 @@ def __init__(self,
self.original_image_width = None
self.number_of_slices = 0
self.image_volume = None
self.total_images_per_volume = None
self.number_of_channels = None
self.total_images_per_volume = 0
self.number_of_channels = 0
self.image_counter = 0
self.slice_index = 0
self.channel_index = 0

def slider_update(self, event):
slider_index = self.view.slider.slider_widget.get()
channel_display_index = 0
self.retrieve_image_slice_from_volume(slider_index=slider_index,
channel_display_index=channel_display_index)
self.reset_display()

def update_display_state(self, event):
r"""Image Display Combobox Called.

Sets self.display_state to desired display format.
Sets self.display_state to desired display format. Toggles state of slider widget. Sets number of
positions.

Parameters
----------
Expand All @@ -154,7 +164,30 @@ def update_display_state(self, event):

"""
self.display_state = self.view.live_frame.live.get()

# Slice in the XY Dimension.
if self.display_state == 'XY Slice':
print("XY Slice")
try:
slider_length = np.shape(self.image_volume)[2] - 1
except IndexError:
slider_length = self.parent_controller.experiment.MicroscopeState['number_z_steps'] - 1
if self.display_state == 'YZ Slice':
try:
slider_length = np.shape(self.image_volume)[0] - 1
except IndexError:
slider_length = self.parent_controller.experiment.CameraParameters['y_pixels'] - 1
if self.display_state == 'YZ Slice':
try:
slider_length = np.shape(self.image_volume)[1] - 1
except IndexError:
slider_length = self.parent_controller.experiment.CameraParameters['x_pixels'] - 1

if self.display_state.find('Slice') != -1:
self.view.slider.slider_widget.configure(to=slider_length,
tickinterval=(slider_length / 5),
state='normal')
else:
self.view.slider.slider_widget.configure(state='disabled')

def get_absolute_position(self):
x = self.parent_controller.view.winfo_pointerx()
Expand Down Expand Up @@ -224,7 +257,7 @@ def set_mode(self,
Parameters
----------
mode : str
camera_view_controller modde.
camera_view_controller mode.
"""
self.mode = mode

Expand Down Expand Up @@ -370,13 +403,7 @@ def update_max_counts(self):
self.image_metrics['Image'].set(np.max(self.temp_array))

def down_sample_image(self):
r"""Down-sample the data for image display according to the configuration file."""
# if self.live_subsampling != 1:
# self.image = cv2.resize(self.image,
# (int(np.shape(self.image)[0] / self.live_subsampling),
# int(np.shape(self.image)[1] / self.live_subsampling)))

"""Down-sample the data for image display according to widget size.."""
r"""Down-sample the data for image display according to widget size.."""
self.down_sampled_image = cv2.resize(self.zoom_image, (512, 512))

def scale_image_intensity(self):
Expand All @@ -396,14 +423,90 @@ def scale_image_intensity(self):
self.down_sampled_image[self.down_sampled_image > scaling_factor] = scaling_factor

def populate_image(self):
"""Converts image to an ImageTk.PhotoImage and populates the Tk Canvas"""
r"""Converts image to an ImageTk.PhotoImage and populates the Tk Canvas"""
self.tk_image = ImageTk.PhotoImage(Image.fromarray(self.cross_hair_image.astype(np.uint8)))
self.canvas.create_image(0, 0, image=self.tk_image, anchor='nw')

def initialize_non_live_display(self,
microscope_state):
r"""Starts image and slice counter, number of channels, number of slices, images per volume, and image volume.

Parameters
----------
microscope_state : dict
State of the microscope
"""
self.image_counter = 0
self.slice_index = 0
self.number_of_channels = len([channel[-1] for channel in microscope_state['channels'].keys()])
self.number_of_slices = int(microscope_state['number_z_steps'])
self.total_images_per_volume = self.number_of_channels * self.number_of_slices
self.image_volume = np.zeros((self.original_image_width,
self.original_image_height,
self.number_of_slices,
self.number_of_channels))

def identify_channel_index_and_slice(self,
microscope_state):
R"""As images arrive, identify channel index and slice.

Parameters
----------
microscope_state : dict
State of the microscope
"""
# Reset the image counter after the full acquisition of an image volume.
if self.image_counter == self.total_images_per_volume:
self.image_counter = 0

# Store each image to the pre-allocated memory.
if microscope_state['stack_cycling_mode'] == 'per_stack':
if 0 * self.number_of_slices <= self.image_counter < 1 * self.number_of_slices:
self.channel_index = 0
elif 1 * self.number_of_slices <= self.image_counter < 2 * self.number_of_slices:
self.channel_index = 1
elif 2 * self.number_of_slices <= self.image_counter < 3 * self.number_of_slices:
self.channel_index = 2
elif 3 * self.number_of_slices <= self.image_counter < 4 * self.number_of_slices:
self.channel_index = 3
elif 4 * self.number_of_slices <= self.image_counter < 5 * self.number_of_slices:
self.channel_index = 4
else:
self.channel_index = 0
print("Camera View Controller - Cannot identify proper channel for per_stack imaging mode.")

self.slice_index = self.image_counter - (self.channel_index * self.number_of_slices)
self.image_volume[:, :, self.slice_index, self.channel_index] = self.image
self.image_counter += 1

if microscope_state['stack_cycling_mode'] == 'per_z':
# Every image that comes in will be the next channel.
self.channel_index = images_received % self.number_of_channels
self.image_volume[:, :, self.slice_index, self.channel_index] = self.image
if self.channel_index == (self.number_of_channels - 1):
self.slice_index += 1
if self.slice_index == self.total_images_per_volume:
self.slice_index = 0

def retrieve_image_slice_from_volume(self,
slider_index,
channel_display_index):
if self.display_state == 'XY MIP':
self.image = np.max(self.image_volume[:, :, :, channel_display_index], axis=2)
if self.display_state == 'YZ MIP':
self.image = np.max(self.image_volume[:, :, :, channel_display_index], axis=0)
if self.display_state == 'ZY MIP':
self.image = np.max(self.image_volume[:, :, :, channel_display_index], axis=1)
if self.display_state == 'XY Slice':
self.image = self.image_volume[:, :, slider_index, channel_display_index]
if self.display_state == 'YZ Slice':
self.image = self.image_volume[slider_index, :, :, channel_display_index]
if self.display_state == 'ZY Slice':
self.image = self.image_volume[:, slider_index, :, channel_display_index]

def display_image(self,
image,
microscope_state,
channel_id=1,
images_received=0):
r"""Displays a camera image using the Lookup Table specified in the View.

Expand All @@ -414,8 +517,10 @@ def display_image(self,
----------
image: ndarray
Acquired image.
channel_id : int
Channel ID.
microscope_state : dict
State of the microscope
images_received : int
Number of channels received.
"""

# Place image in memory
Expand All @@ -427,66 +532,33 @@ def display_image(self,
# Save image dimensions to memory.
self.original_image_height, self.original_image_width = self.image.shape

# For first image received, pre-allocate memory/arrays.
# For first image received, pre-allocate memory/arrays, initialize counters.
if images_received == 0:
self.number_of_channels = len([channel[-1] for channel in microscope_state['channels'].keys()])
self.number_of_slices = int(microscope_state['number_z_steps'])
# print(self.original_image_height, self.original_image_width, self.number_of_slices)
self.total_images_per_volume = self.number_of_channels * self.number_of_slices

# TODO: Switch CXYZ to XYZC?
# self.image_volume = np.zeros((self.number_of_channels,
# self.original_image_height,
# self.original_image_width,
# self.number_of_slices))

# Store each image to the pre-allocated memory. Requires knowledge of how images are received.
if microscope_state['stack_cycling_mode'] == 'per_stack':
pass
self.initialize_non_live_display(microscope_state=microscope_state)

if microscope_state['stack_cycling_mode'] == 'per_z':
# Every image that comes in will be the next channel.
pass
# Identify image identity (e.g., slice #, channel #).
self.identify_channel_index_and_slice(microscope_state=microscope_state)

# MIP Display Mode
# Place image in XYZC numpy array.
self.image_volume[:, :, self.slice_index, self.channel_index] = self.image

# MIP and Slice Mode TODO: Consider channels
if self.display_state != 'Live':
if self.display_state == 'XY MIP':
pass
if self.display_state == 'YZ MIP':
pass
if self.display_state == 'ZY MIP':
pass

# Live Display Mode
slider_index = self.view.slider.slider_widget.get()
channel_display_index = 0
self.retrieve_image_slice_from_volume(slider_index=slider_index,
channel_display_index=channel_display_index)

else:
# Digital zoom.
self.digital_zoom()

# Detect saturated pixels
self.detect_saturation()

# Down-sample Image for display
self.down_sample_image()

# Scale image to [0, 1] values
self.scale_image_intensity()

# Update the GUI according to the instantaneous or rolling average max counts.
self.update_max_counts()

# Add Cross-Hair
self.add_crosshair()

# Apply Lookup Table
self.apply_LUT()

# Create ImageTk.PhotoImage
self.populate_image()

# Update Channel Index
self.image_metrics['Channel'].set(channel_id)

# Iterate Image Count for Rolling Average
self.image_metrics['Channel'].set(self.channel_index)
self.image_count = self.image_count + 1

def add_crosshair(self):
Expand Down
1 change: 0 additions & 1 deletion src/aslm/model/devices/camera/camera_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,5 @@ def __init__(self, camera_id, configuration, experiment, verbose=False):
# Initialize Exposure and Display Information - Convert from milliseconds to seconds.
self.camera_line_interval = self.configuration.CameraParameters['line_interval']
self.camera_exposure_time = self.configuration.CameraParameters['exposure_time'] / 1000
self.camera_display_live_subsampling = self.configuration.CameraParameters['display_live_subsampling']
self.camera_display_acquisition_subsampling = self.configuration.CameraParameters['display_acquisition_subsampling']

Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ def __init__(self, settings_tab, *args, **kwargs):
# Formatting
Grid.columnconfigure(content_frame, 'all', weight=1)
Grid.rowconfigure(content_frame, 'all', weight=1)


#Dictionary for all the variables, this will be used by the controller
self.inputs = {}
Expand Down
Loading