Skip to content

Commit

Permalink
[feat] minor gui/model changes for fibsem tab
Browse files Browse the repository at this point in the history
  • Loading branch information
patrickcleeve2 committed Jan 22, 2025
1 parent f071337 commit 0c8b6cf
Show file tree
Hide file tree
Showing 11 changed files with 340 additions and 26 deletions.
3 changes: 1 addition & 2 deletions src/odemis/acq/stitching/_tiledacq.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,7 @@
SpectrumStream, FluoStream, MultipleDetectorStream, util, executeAsyncTask, \
CLStream
from odemis.model import DataArray
from odemis.util import dataio as udataio, img, linalg
from odemis.util import rect_intersect
from odemis.util import dataio as udataio, img, linalg, rect_intersect
from odemis.util.img import assembleZCube
from odemis.util.linalg import generate_triangulation_points
from odemis.util.raster import point_in_polygon
Expand Down
14 changes: 11 additions & 3 deletions src/odemis/gui/comp/overlay/rectangle.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ class RectangleOverlay(EditableShape, RectangleEditingMixin, WorldOverlay):
The selected rectangle can be manipulated by dragging its edges or rotating it.
"""
def __init__(self, cnvs, colour=gui.SELECTION_COLOUR, center=(0, 0)):
def __init__(self, cnvs, colour=gui.SELECTION_COLOUR, center=(0, 0), show_selection_points: bool = True):
EditableShape.__init__(self, cnvs)
RectangleEditingMixin.__init__(self, colour, center)
# RectangleOverlay has attributes and methods of the "WorldOverlay" interface.
Expand Down Expand Up @@ -151,6 +151,9 @@ def __init__(self, cnvs, colour=gui.SELECTION_COLOUR, center=(0, 0)):
background=None
)

# draw selection points on shape
self._draw_selection_points = show_selection_points

def to_dict(self) -> dict:
"""
Convert the necessary class attributes and its values to a dict.
Expand Down Expand Up @@ -478,7 +481,10 @@ def draw_edges(self, ctx, b_point1: Vec, b_point2: Vec, b_point3: Vec, b_point4:
b_rotation = Vec(self.cnvs.view_to_buffer(self.v_rotation))
ctx.set_dash([])
ctx.set_line_width(1)
ctx.set_source_rgba(0.1, 0.5, 0.8, 0.8) # Dark blue-green
if self._draw_selection_points:
ctx.set_source_rgba(0.1, 0.5, 0.8, 0.8) # Dark blue-green
else:
ctx.set_source_rgba(*self.colour) # face colour
ctx.arc(b_rotation.x, b_rotation.y, 4, 0, 2 * math.pi)
ctx.fill()
ctx.arc(mid_point12.x, mid_point12.y, 4, 0, 2 * math.pi)
Expand Down Expand Up @@ -533,7 +539,9 @@ def draw(self, ctx, shift=(0, 0), scale=1.0, line_width=4, dash=True):
ctx.stroke()

self._calc_edges()
self.draw_edges(ctx, b_point1, b_point2, b_point3, b_point4)

if self._draw_selection_points:
self.draw_edges(ctx, b_point1, b_point2, b_point3, b_point4)

# Side labels
if self.selected.value:
Expand Down
117 changes: 117 additions & 0 deletions src/odemis/gui/conf/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -771,6 +771,123 @@
},
},
},
"meteor" : {
"e-beam":
OrderedDict((
("accelVoltage", {
"label": "Accel. Voltage",
"tooltip": "Accelerating voltage",
"event": wx.EVT_SCROLL_CHANGED # only affects when it's a slider
}),
("probeCurrent", {
"label": "Beam Current",
"control_type": odemis.gui.CONTROL_SLIDER,
"type": "float",
"scale": "linear",
"event": wx.EVT_SCROLL_CHANGED
}),
("horizontalFoV", {
"label": "HFW",
"tooltip": "Horizontal Field Width",
"control_type": odemis.gui.CONTROL_COMBO,
"choices": util.hfw_choices,
}),
("dwellTime", {
"control_type": odemis.gui.CONTROL_SLIDER,
"tooltip": "Pixel integration time",
"type": "float",
"accuracy": 3,
"event": wx.EVT_SCROLL_CHANGED
}),
("scale", {
# same as binning (but accepts floats)
"control_type": odemis.gui.CONTROL_NONE,
}),
("resolution", {
"label": "Resolution",
"control_type": odemis.gui.CONTROL_COMBO,
"tooltip": "Number of pixels in the image",
"choices": None,
"accuracy": None, # never simplify the numbers
}),
)),
"ion-beam":
OrderedDict((
("accelVoltage", {
"label": "Accel. Voltage",
"tooltip": "Accelerating voltage",
"event": wx.EVT_SCROLL_CHANGED # only affects when it's a slider
}),
("probeCurrent", {
"label": "Beam Current",
"control_type": odemis.gui.CONTROL_SLIDER,
"type": "float",
"scale": "linear",
"event": wx.EVT_SCROLL_CHANGED
}),
("resolution", {
"label": "Resolution",
"control_type": odemis.gui.CONTROL_COMBO,
"tooltip": "Number of pixels in the image",
"choices": None,
"accuracy": None, # never simplify the numbers
}),
("dwellTime", {
"control_type": odemis.gui.CONTROL_SLIDER,
"tooltip": "Pixel integration time",
# "range": (1e-9, 1),
# "scale": "log",
"type": "float",
"accuracy": 3,
"event": wx.EVT_SCROLL_CHANGED
}),
("horizontalFoV", {
"label": "HFW",
"tooltip": "Horizontal Field Width",
"control_type": odemis.gui.CONTROL_COMBO,
"choices": util.hfw_choices,
# "accuracy": 3,
}),
("scale", {
# same as binning (but accepts floats)
"control_type": odemis.gui.CONTROL_NONE,
# "tooltip": "Pixel resolution preset",
# means will make sure both dimensions are treated as one
# "choices": util.binning_1d_from_2d,
}),

)),
"se-detector":
OrderedDict((
("brightness", {
"label": "Brightness",
}),
("contrast", {
"label": "Contrast",
}),
("detector_mode", {
"label": "Detector Mode",
}),
("detector_type", {
"label": "Detector Type",
}),
)),
"se-detector-ion":
OrderedDict((
("brightness", {
"label": "Brightness",
}),
("contrast", {
"label": "Contrast",
}),
("detector_mode", {
"label": "Detector Mode",
}),
("detector_type", {
"label": "Detector Type",
}),
)),
}
}

# The sparc-simplex is identical to the sparc
Expand Down
100 changes: 98 additions & 2 deletions src/odemis/gui/cont/stream_bar.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,23 @@
import gc
import logging
import os
from collections import OrderedDict
import threading
import time
from collections import OrderedDict

import wx

import odemis.acq.stream as acqstream
import odemis.gui.conf.file
import odemis.gui.model as guimodel
from odemis import model
from odemis.acq.stream import FastEMOverviewStream, StaticSEMStream, StaticStream
from odemis.acq.stream_settings import StreamSettingsConfig
from odemis.acq.stream import StaticStream, FastEMOverviewStream
from odemis.gui.conf.data import get_local_vas
from odemis.gui.cont.stream import StreamController
from odemis.gui.model import TOOL_NONE, TOOL_SPOT
from odemis.gui.util import call_in_wx_main
from odemis.util.dataio import data_to_static_streams

# There are two kinds of controllers:
# * Stream controller: links 1 stream <-> stream panel (cont/stream/StreamPanel)
Expand Down Expand Up @@ -2185,3 +2186,98 @@ def clear(self, clear_model=True):
# Force a check of what can be garbage collected, as some of the streams
# could be quite big, that will help to reduce memory pressure.
gc.collect()


class CryoFIBAcquiredStreamsController(CryoStreamsController):
"""
StreamBarController to display the acquired reference image for automated milling workflow.
The only role of the controller is to display the streams in the
acquired view when the feature is selected.
"""

def __init__(self, tab_data, feature_view, *args, **kwargs):
"""
feature_view (StreamView): the view to show the feature streams
"""
super().__init__(tab_data, *args, **kwargs)
self._feature_view = feature_view

tab_data.main.currentFeature.subscribe(self._on_current_feature_changes)

def showFeatureStream(self, stream) -> StreamController:
"""
Shows an Feature stream (in the Acquired view)
Must be run in the main GUI thread.
"""
self._feature_view.addStream(stream)
sc = self._add_stream_cont(stream, show_panel=True, static=self.static_mode,
view=self._feature_view)
return sc

@call_in_wx_main
def _on_current_feature_changes(self, feature):
"""
Handle switching the acquired streams appropriate to the current feature
:param feature: (CryoFeature or None) the newly selected current feature
"""
self.clear_feature_streams()
# show the feature streams on the acquired view

acquired_streams = []
if feature:
if feature.reference_image is not None:
acquired_streams = data_to_static_streams([feature.reference_image])
for stream in acquired_streams:
self.showFeatureStream(stream)
self.stream = stream # should only ever be 1 stream
# refit the selected feature in the acquired view
self._view_controller.viewports[3].canvas.fit_view_to_content()

def clear_feature_streams(self):
"""
Remove from display all feature streams (but leave the overview and live streams)
But DO NOT REMOVE the streams from the model
"""
# Remove the panels, and indirectly it will clear the view
v = self._feature_view
for sc in self.stream_controllers.copy():
logging.warning(f"attempting to remove stream: {sc.stream}")
if not isinstance(sc.stream, StaticSEMStream):
logging.warning("Unexpected non static stream: %s", sc.stream)
continue

self._stream_bar.remove_stream_panel(sc.stream_panel)
if hasattr(v, "removeStream"):
v.removeStream(sc.stream)
if sc in self.stream_controllers:
self.stream_controllers.remove(sc)

self.stream = None
self._stream_bar.fit_streams()

# Force a check of what can be garbage collected, as some of the streams
# could be quite big, that will help to reduce memory pressure.
gc.collect()

def clear(self, clear_model=True):
"""
Remove all the streams, from the GUI (view, stream panels)
Must be called in the main GUI thread.
:param clear_model: unused, but required because of external api
"""
# clear the graphical part
self._stream_bar.clear()

# Clean up the views
for stream in self._tab_data_model.streams.value:
if isinstance(stream, StaticStream):
for v in (self._feature_view, self._ov_view):
if hasattr(v, "removeStream"):
v.removeStream(stream)

# Clear the stream controller
self.stream_controllers = []

# Force a check of what can be garbage collected, as some of the streams
# could be quite big, that will help to reduce memory pressure.
gc.collect()
2 changes: 1 addition & 1 deletion src/odemis/gui/cont/tabs/localization_tab.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ def __init__(self, name, button, panel, main_frame, main_data):
elif self.main_data.role == "meteor":
# The stage is in the FM referential, but we care about the stage-bare
# in the SEM referential to move between positions
self._allowed_targets = [FM_IMAGING, SEM_IMAGING]
self._allowed_targets = [FM_IMAGING]
self._stage = self.tab_data_model.main.stage_bare
elif self.main_data.role == "mimas":
# Only useful near the active positions: milling (FIB) or FLM
Expand Down
4 changes: 2 additions & 2 deletions src/odemis/gui/model/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@
FixedOverviewView, MicroscopeView, StreamView, View)
from .tab_gui_data import (AcquisitionWindowData, ActuatorGUIData,
AnalysisGUIData, ChamberGUIData, CryoChamberGUIData,
CryoCorrelationGUIData, CryoGUIData,
CryoCorrelationGUIData, CryoGUIData, CryoFIBSEMGUIData,
CryoLocalizationGUIData, EnzelAlignGUIData,
FastEMAcquisitionGUIData, FastEMMainTabGUIData,
FastEMSetupGUIData, LiveViewGUIData, MicroscopyGUIData,
SecomAlignGUIData, Sparc2AlignGUIData,
SparcAcquisitionGUIData, SparcAlignGUIData)
SparcAcquisitionGUIData, SparcAlignGUIData, AcquiMode)
8 changes: 5 additions & 3 deletions src/odemis/gui/model/stream_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import queue
import threading
import time
from typing import Tuple, Dict

from odemis import model
from odemis.acq.stream import DataProjection, RGBSpatialProjection, Stream, StreamTree
Expand Down Expand Up @@ -431,7 +432,7 @@ def moveStageToView(self):
shift = (view_pos[0] - prev_pos["x"], view_pos[1] - prev_pos["y"])
return self.moveStageBy(shift)

def moveStageTo(self, pos):
def moveStageTo(self, pos: Tuple[float, float]):
"""
Request an absolute move of the stage to a given position
Expand All @@ -441,7 +442,8 @@ def moveStageTo(self, pos):
if not self._stage:
return None

move = self.clipToStageLimits({"x": pos[0], "y": pos[1]})
if isinstance(pos, tuple):
move = self.clipToStageLimits({"x": pos[0], "y": pos[1]})

logging.debug("Requesting stage to move to %s mm in x direction and %s mm in y direction",
move["x"] * 1e3, move["y"] * 1e3)
Expand All @@ -450,7 +452,7 @@ def moveStageTo(self, pos):
f.add_done_callback(self._on_stage_move_done)
return f

def clipToStageLimits(self, pos):
def clipToStageLimits(self, pos: Tuple[float, float]) -> Dict[str, float]:
"""
Clip current position in x/y direction to the maximum allowed stage limits.
Expand Down
Loading

0 comments on commit 0c8b6cf

Please sign in to comment.