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 Feb 10, 2025
1 parent 6aa49ee commit fbbd837
Show file tree
Hide file tree
Showing 13 changed files with 300 additions and 24 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
9 changes: 7 additions & 2 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 @@ -533,7 +536,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
87 changes: 86 additions & 1 deletion src/odemis/gui/conf/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,11 @@
"event": wx.EVT_SCROLL_CHANGED # only affects when it's a slider
}),
("probeCurrent", {
"event": wx.EVT_SCROLL_CHANGED # only affects when it's a slider
"label": "Beam Current",
"control_type": odemis.gui.CONTROL_SLIDER,
"type": "float",
"scale": "linear",
"event": wx.EVT_SCROLL_CHANGED
}),
("spotSize", {
"tooltip": "Electron-beam Spot size",
Expand Down Expand Up @@ -235,6 +239,52 @@
"control_type": odemis.gui.CONTROL_NONE,
}),
)),
"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,
}),

)),
"ebeam-blanker":
OrderedDict((
("period", {
Expand Down Expand Up @@ -594,6 +644,27 @@
("brightness", {
"control_type": odemis.gui.CONTROL_SLIDER,
}),
("mode", {
"label": "Detector Mode",
}),
("type", {
"label": "Detector Type",
}),
)),
"se-detector-ion":
OrderedDict((
("brightness", {
"label": "Brightness",
}),
("contrast", {
"label": "Contrast",
}),
("mode", {
"label": "Detector Mode",
}),
("type", {
"label": "Detector Type",
}),
)),
}

Expand Down Expand Up @@ -771,6 +842,20 @@
},
},
},
"meteor" : {
"e-beam": {
"scale": {
"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
}
},
}
}

# The sparc-simplex is identical to the sparc
Expand Down
99 changes: 97 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,97 @@ 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():
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 @@ -191,7 +191,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
3 changes: 2 additions & 1 deletion src/odemis/gui/main_xrc.py
Original file line number Diff line number Diff line change
Expand Up @@ -779,6 +779,7 @@ def __init__(self, parent):
self.btn_create_move_feature = xrc.XRCCTRL(self, "btn_create_move_feature")
self.cmb_feature_status = xrc.XRCCTRL(self, "cmb_feature_status")
self.btn_go_to_feature = xrc.XRCCTRL(self, "btn_go_to_feature")
self.label_feature_z = xrc.XRCCTRL(self, "label_feature_z")
self.ctrl_feature_z = xrc.XRCCTRL(self, "ctrl_feature_z")
self.btn_use_current_z = xrc.XRCCTRL(self, "btn_use_current_z")
self.menu_localization_streams = xrc.XRCCTRL(self, "menu_localization_streams")
Expand Down Expand Up @@ -9111,7 +9112,7 @@ def __init_resources():
<object class="sizeritem">
<object class="wxBoxSizer">
<object class="sizeritem">
<object class="wxStaticText">
<object class="wxStaticText" name="label_feature_z">
<label>Feature Z</label>
<fg>#DDDDDD</fg>
</object>
Expand Down
2 changes: 1 addition & 1 deletion src/odemis/gui/model/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
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,
Expand Down
7 changes: 7 additions & 0 deletions src/odemis/gui/model/_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
Odemis. If not, see http://www.gnu.org/licenses/.
"""

from enum import Enum

# The different states of a microscope
STATE_OFF = 0
STATE_ON = 1
Expand Down Expand Up @@ -96,3 +99,7 @@
CALIBRATION_1 = "Calibration 1"
CALIBRATION_2 = "Calibration 2"
CALIBRATION_3 = "Calibration 3"

class AcquisitionMode(Enum):
FLM = 1
FIBSEM = 2
6 changes: 4 additions & 2 deletions src/odemis/gui/model/stream_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
import queue
import threading
import time
from concurrent.futures import Future
from typing import Dict, Optional, Tuple

from odemis import model
from odemis.acq.stream import DataProjection, RGBSpatialProjection, Stream, StreamTree
Expand Down Expand Up @@ -431,7 +433,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]) -> Optional[Future]:
"""
Request an absolute move of the stage to a given position
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: Dict[str, float]) -> Dict[str, float]:
"""
Clip current position in x/y direction to the maximum allowed stage limits.
Expand Down
Loading

0 comments on commit fbbd837

Please sign in to comment.