Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
fdrgsp committed Nov 26, 2024
1 parent 16292dd commit cb4bffd
Showing 1 changed file with 75 additions and 67 deletions.
142 changes: 75 additions & 67 deletions x.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ def __init__(

self._current_scale: int = 1

self._auto_reset_view: bool = True

self.canvas = scene.SceneCanvas(keys="interactive", show=True)
self.view = cast(ViewBox, self.canvas.central_widget.add_view())
self.view.camera = scene.PanZoomCamera(aspect=1, flip=(0, 1))
Expand All @@ -68,17 +70,22 @@ def __init__(
# this is to check if the scale has changed and update the scene accordingly
self.canvas.events.draw.connect(self._on_draw_event)

# --------------------PUBLIC METHODS--------------------
# --------------------PUBLIC METHODS--------------------\

@property
def auto_reset_view(self) -> bool:
"""Return the auto reset view property."""
return self._auto_reset_view

def reset_widget(self) -> None:
"""Reset the widget and the variables."""
self.clear_scene()
self._image_store = DataStore()
self._current_scale = 1
self._info_text = None
@auto_reset_view.setter
def auto_reset_view(self, value: bool) -> None:
"""Set the auto reset view property."""
self._auto_reset_view = value

def clear_scene(self) -> None:
"""Clear the scene."""
self._image_store.store.clear()

if self._info_text is not None:
self._info_text.parent = None

Expand All @@ -93,56 +100,54 @@ def _move_to_clicked_position(self, event: MouseEvent) -> None:
x, y, _, _ = self.view.camera.transform.imap(event.pos)
self._mmc.setXYPosition(x, y)

def _on_draw_event(self, event: MouseEvent | None) -> None:
def _on_draw_event(self, event: MouseEvent) -> None:
"""Handle the draw event.
Update the scene based on the scale.
Update the scene if the scale has changed.
"""
scale = self._get_scale()
if scale != self._current_scale:
self._current_scale = scale
self._update_scene_by_scale(scale)
self._draw_scale_info()
if scale == self._current_scale:
return
self._current_scale = scale
self._update_scene_by_scale(scale)
self._draw_scale_info()

def _get_scale(self) -> int:
"""Return the scale based on the zoom level."""
# this just maps the camera to the scene.
coords = self.view.camera.transform.imap([[0, 0], [1, 0]])
pixel_ratio = coords[1][0] - coords[0][0]
# get breakpoints for zoom levels of pixel_ratio of powers of 2
# # this just maps the camera to the scene.
# coords = self.view.camera.transform.imap([[0, 0], [1, 0]])
# # get the pixel ratio
# pixel_ratio = coords[1][0] - coords[0][0]

# same as:
# get the transform from the camera
transform = self.view.camera.transform
# calculate the zoom level as the inverse of the scale factor in the transform
pixel_ratio = 1 / transform.scale[0]

# Calculate the scale as the inverse of the zoom level
scale = 1
while pixel_ratio / scale > 1:
scale *= 2
return scale

def _reset_view(self) -> None:
# TODO: fix this, it is not correctly centering the view
"""Recenter the view to the center of all images."""
# get the max and min (x, y) values from _store_images
min_x, max_x = None, None
min_y, max_y = None, None
for (x, y), img in self._image_store.store.items():
height, width = img.shape
x, y = round(x), round(y)
min_x = x if min_x is None else min(min_x, x)
max_x = x + width if max_x is None else max(max_x, x + width)
min_y = y if min_y is None else min(min_y, y)
max_y = y + height if max_y is None else max(max_y, y + height)

for child in self.view.scene.children:
if isinstance(child, Image):
x, y = child.transform.translate[:2]
height, width = child.size

x, y = round(x), round(y) # Normalize coordinates

# Update bounds
min_x = x if min_x is None else min(min_x, x)
max_x = x + width if max_x is None else max(max_x, x + width)
min_y = y if min_y is None else min(min_y, y)
max_y = y + height if max_y is None else max(max_y, y + height)

if (
min_x is not None
and max_x is not None
and min_y is not None
and max_y is not None
):
self.view.camera.set_range(
x=(round(min_x), round(max_x)),
y=(round(min_y), round(max_y)),
)
if min_x is None or max_x is None or min_y is None or max_y is None:
return

self.view.camera.set_range(x=(min_x, max_x), y=(min_y, max_y))

self._draw_scale_info()

Expand All @@ -155,25 +160,29 @@ def _on_image_snapped(self) -> None:
# move the coordinates to the center of the image
self._add_image(img, x, y)

def _on_frame_ready(self, image: np.ndarray, event: useq.MDAEvent) -> None:
"""Add the image to the scene when frameReady event is emitted."""
x = event.x_pos if event.x_pos is not None else self._mmc.getXPosition()
y = event.y_pos if event.y_pos is not None else self._mmc.getYPosition()
self._add_image(image, x, y)

def _add_image(self, img: np.ndarray, x: float, y: float) -> None:
h, w = img.shape
"""Add an image to the scene and to the _image_store."""
# move the coordinates to the center of the image
h, w = np.array(img.shape)
x, y = round(x - w / 2), round(y - h / 2)
# store the image in the _image_store
self._image_store.add_image((x, y), img)
# get the current scale
scale = self._get_scale()
self._current_scale = scale = self._get_scale()
# add the image to the scene with the current scale
img = img[::scale, ::scale]
frame = Image(img, cmap="grays", parent=self.view.scene)
frame.transform = scene.STTransform(scale=(scale, scale), translate=(x, y))
# TODO: make self._reset_view() optional
self._reset_view()
self._draw_scale_info()

def _on_frame_ready(self, image: np.ndarray, event: useq.MDAEvent) -> None:
x = event.x_pos if event.x_pos is not None else self._mmc.getXPosition()
y = event.y_pos if event.y_pos is not None else self._mmc.getYPosition()
self._add_image(image, x, y)
if self._auto_reset_view:
self._reset_view()
else:
self._draw_scale_info()

def _update_scene_by_scale(self, scale: int) -> None:
"""Update the images in the scene based on the scale."""
Expand All @@ -182,7 +191,9 @@ def _update_scene_by_scale(self, scale: int) -> None:
x, y = child.transform.translate[:2]
img = self._image_store.store[(x, y)]
img_scaled = img[::scale, ::scale]
# update the image data
child.set_data(img_scaled)
# update the scale
child.transform.scale = (scale, scale)

def _draw_scale_info(self) -> None:
Expand All @@ -191,12 +202,9 @@ def _draw_scale_info(self) -> None:
if self._info_text is not None:
self._info_text.parent = None

# txt = f"Scale: {self._current_scale}"
txt = f"Scale: {self._get_scale()}"

# create the text visual only once
self._info_text = scene.Text(
txt,
f"Scale: {self._get_scale()}",
color="white",
parent=self.canvas.scene,
anchor_x="right",
Expand All @@ -209,33 +217,33 @@ def _draw_scale_info(self) -> None:


if __name__ == "__main__":
from qtpy.QtWidgets import QApplication, QPushButton
from qtpy.QtWidgets import QApplication

from pymmcore_widgets import StageWidget
from pymmcore_widgets import MDAWidget, StageWidget

app = QApplication([])

mmc = CMMCorePlus.instance()
mmc.loadSystemConfiguration()
# set camera size to 2048x2048
mmc.setProperty("Camera", "OnCameraCCDXSize", 2048)
mmc.setProperty("Camera", "OnCameraCCDYSize", 2048)
mmc.setExposure(100)
# TODO: fix for pixel size =! 1
# mmc.setProperty("Objective", "Label", "Nikon 20X Plan Fluor ELWD")
# print(mmc.getPixelSizeUm())
# TODO: fix considering the camera size
size = 2048
mmc.setProperty("Camera", "OnCameraCCDXSize", size)
mmc.setProperty("Camera", "OnCameraCCDYSize", size)
# TODO: fix considering pixel size
mmc.setProperty("Objective", "Label", "Nikon 20X Plan Fluor ELWD")
print("pixel size", mmc.getPixelSizeUm())

exp = StageExplorer()
exp.show()

stage = StageWidget("XY")
stage.setStep(512)
stage.setStep(size)
stage.snap_checkbox.setChecked(True)
stage._invert_y.setChecked(True)
stage.show()

btn = QPushButton("Reset")
btn.clicked.connect(exp.reset_widget)
btn.show()
m = MDAWidget()
m.show()

app.exec()

0 comments on commit cb4bffd

Please sign in to comment.