Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
106 changes: 71 additions & 35 deletions src/navigate/controller/sub_controllers/camera_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,12 @@ def __init__(self, view, parent_controller=None):
#: tkinter.Canvas: The tkinter canvas that displays the image.
self.canvas = self.view.canvas

#: int: The width of the window
self.width = 663

#: int: The height of the window
self.height = 597

#: int: The height of the canvas.
self.canvas_height = 512

Expand Down Expand Up @@ -261,6 +267,22 @@ def __init__(self, view, parent_controller=None):
command=lambda: self.update_transpose_state(display=True)
)

#: int: The x position of the mouse.
self.move_to_x = None

#: int: The y position of the mouse.
self.move_to_y = None

#: float: Percentage of crosshair in x
self.crosshair_x = 0.5

#: float: Percentage of crosshair in y
self.crosshair_y = 0.5

self.menu = tk.Menu(self.canvas, tearoff=0)
self.menu.add_command(label="Reset Display", command=self.reset_display)
self.menu.add_command(label="Move Crosshair", command=self.move_crosshair)

def initialize(self, name, data):
"""Sets widgets based on data given from main controller/config.

Expand Down Expand Up @@ -504,19 +526,25 @@ def initialize_non_live_display(self, microscope_state, camera_parameters):
][self.microscope_name]["camera"]
self.flip_flags = {
"x": camera_config.get("flip_x", False),
"y": camera_config.get("flip_y", False)
"y": camera_config.get("flip_y", False),
}

self.update_canvas_size()
self.reset_display(False)

def reset_display(self, display_flag=True):
def reset_display(self, display_flag=True, reset_crosshair=True):
"""Set the display back to the original digital zoom.

Parameters
----------
display_flag : bool
Flag for refreshing the image display. Default True.
reset_crosshair : bool
Flag for resetting the crosshair. Default True.
"""
if reset_crosshair:
self.crosshair_x = 0.5
self.crosshair_y = 0.5
self.zoom_width = self.canvas_width
self.zoom_height = self.canvas_height
self.zoom_rect = np.array([[0, self.zoom_width], [0, self.zoom_height]])
Expand All @@ -526,6 +554,14 @@ def reset_display(self, display_flag=True):
if display_flag:
self.process_image()

def move_crosshair(self):
"""Move the crosshair to a non-default position."""
width = (self.zoom_rect[0][1] - self.zoom_rect[0][0]) / self.zoom_scale
height = (self.zoom_rect[1][1] - self.zoom_rect[1][0]) / self.zoom_scale
self.crosshair_x = self.move_to_x / width
self.crosshair_y = self.move_to_y / height
self.process_image()

def update_canvas_size(self):
"""Update the canvas size."""
r_canvas_width = int(self.view.canvas["width"])
Expand All @@ -548,9 +584,13 @@ def update_canvas_size(self):
def digital_zoom(self):
"""Apply digital zoom.

The x and y positions are between 0
and the canvas width and height respectively.
The x and y positions are between 0 and the canvas width and height
respectively.

Returns
-------
image : np.array
Image after digital zoom applied
"""
self.zoom_rect = self.zoom_rect - self.zoom_offset
self.zoom_rect = self.zoom_rect * self.zoom_value
Expand All @@ -559,7 +599,7 @@ def digital_zoom(self):
self.zoom_value = 1

if self.zoom_rect[0][0] > 0 or self.zoom_rect[1][0] > 0:
self.reset_display(False)
self.reset_display(False, False)

x_start_index = int(-self.zoom_rect[0][0] / self.zoom_scale)
x_end_index = int(x_start_index + self.zoom_width)
Expand Down Expand Up @@ -657,16 +697,36 @@ def add_crosshair(self, image):
Image data with cross-hair.
"""
if self.apply_cross_hair:
crosshair_x = (self.zoom_rect[0][0] + self.zoom_rect[0][1]) / 2
crosshair_y = (self.zoom_rect[1][0] + self.zoom_rect[1][1]) / 2
crosshair_x = (
self.zoom_rect[0][1] - self.zoom_rect[0][0]
) * self.crosshair_x + self.zoom_rect[0][0]
crosshair_y = (
self.zoom_rect[1][1] - self.zoom_rect[1][0]
) * self.crosshair_y + self.zoom_rect[1][0]

if crosshair_x < 0 or crosshair_x >= self.canvas_width:
crosshair_x = -1
if crosshair_y < 0 or crosshair_y >= self.canvas_height:
crosshair_y = -1
image[:, int(crosshair_x)] = 1
image[int(crosshair_y), :] = 1

return image

def get_absolute_position(self):
"""Gets the absolute position of the computer mouse.

Returns
-------
x : int
The x position of the mouse.
y : int
The y position of the mouse.
"""
x = self.parent_controller.view.winfo_pointerx()
y = self.parent_controller.view.winfo_pointery()
return x, y

def array_to_image(self, image):
"""Convert a numpy array to a PIL Image

Expand Down Expand Up @@ -712,6 +772,8 @@ def process_image(self):
image intensity, adds a crosshair, applies the lookup table, and populates the
image.
"""
if self.image is None:
return
image = self.digital_zoom()
self.detect_saturation(image)
image = self.down_sample_image(image)
Expand Down Expand Up @@ -827,7 +889,7 @@ def mouse_wheel(self, event):
self.zoom_height /= self.zoom_value

if self.zoom_width > self.canvas_width or self.zoom_height > self.canvas_height:
self.reset_display(False)
self.reset_display(False, False)
elif self.zoom_width < 5 or self.zoom_height < 5:
return

Expand Down Expand Up @@ -871,22 +933,10 @@ def __init__(self, view, parent_controller=None):
if platform.system() == "Windows":
self.resize_event_id = self.view.bind("<Configure>", self.resize)

self.width, self.height = 663, 597
self.canvas_width, self.canvas_height = 512, 512

# Right-Click Binding
#: tkinter.Menu: The tkinter menu that pops up on right click.
self.menu = tk.Menu(self.canvas, tearoff=0)
# Right-Click Popup Menu
self.menu.add_command(label="Move Here", command=self.move_stage)
self.menu.add_command(label="Reset Display", command=self.reset_display)
self.menu.add_command(label="Mark Position", command=self.mark_position)

#: int: The x position of the mouse.
self.move_to_x = None

#: int: The y position of the mouse.
self.move_to_y = None

#: str: The display state.
self.display_state = "Live"

Expand Down Expand Up @@ -1030,20 +1080,6 @@ def update_display_state(self, *args):
if self.view.live_frame.channel.get() not in self.selected_channels:
self.view.live_frame.channel.set(self.selected_channels[0])

def get_absolute_position(self):
"""Gets the absolute position of the computer mouse.

Returns
-------
x : int
The x position of the mouse.
y : int
The y position of the mouse.
"""
x = self.parent_controller.view.winfo_pointerx()
y = self.parent_controller.view.winfo_pointery()
return x, y

def popup_menu(self, event):
"""Right-Click Popup Menu

Expand Down
1 change: 1 addition & 0 deletions test/controller/sub_controllers/test_camera_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,7 @@ def mock_process_image():
assert process_image_called is True

def test_process_image(self):
self.camera_view.image = np.random.randint(0, 256, (600, 800))
self.camera_view.digital_zoom = MagicMock()
self.camera_view.detect_saturation = MagicMock()
self.camera_view.down_sample_image = MagicMock()
Expand Down
Loading