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

Ensure GovernExclusion and ConfigureGUI work together #80

Merged
merged 23 commits into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
ab38ef9
use the motor signal movement_finished() to update the Axis control d…
rocco8773 Oct 21, 2024
1151aa1
disable configuration controls while the probe drive is moving
rocco8773 Oct 22, 2024
4f2599f
if the status of one axis changes make the other AxisControlWidgets u…
rocco8773 Oct 22, 2024
cddf83b
make the drive widget update its display when the configuration is ch…
rocco8773 Oct 22, 2024
d2bba71
fix over indent
rocco8773 Jan 14, 2025
6caafb3
move all connected functions to MGWidget.configChanged into the _conf…
rocco8773 Jan 14, 2025
b186886
when _update_drive_control_widget() execute _refresh_drive_control() …
rocco8773 Jan 14, 2025
b725894
Merge branch 'main' into gui_movement_modifications
rocco8773 Jan 14, 2025
0b3ba89
change default colors to be more color-blind friendly
rocco8773 Jan 14, 2025
ed2a9e3
Make sure MGWidget is seeded with a mg name if none is given, and the…
rocco8773 Jan 14, 2025
66886a2
add tooltips for the MotionBuilder gear btn
rocco8773 Jan 14, 2025
2ee22fd
do not false invalidate drive if we are editing an existing motion group
rocco8773 Jan 14, 2025
37500a7
Merge branch 'main' into fix_configuregui_tooltips
rocco8773 Jan 14, 2025
fa4b033
Merge branch 'main' into configuregui_handle_governexclusion
rocco8773 Jan 15, 2025
174a310
only compare dictionaries if mg_config is not None
rocco8773 Jan 15, 2025
533b5d0
move all connected functionality to signal configChanged into method …
rocco8773 Jan 15, 2025
dd8a794
GovernExclusions must regenerate their mask during a global mask update
rocco8773 Jan 15, 2025
f4a7912
remove unused import
rocco8773 Jan 15, 2025
9279489
make sure we do not index self.exclusions if no exclusion have been a…
rocco8773 Jan 15, 2025
24aa199
make MBItem._determine_name() an abstract method to children can bett…
rocco8773 Jan 15, 2025
87d0c2e
simplify plotting of global mask
rocco8773 Jan 15, 2025
0ea8507
update Shadow2DExclusion._paint_mask to handle triangles where all po…
rocco8773 Jan 15, 2025
8d7d899
remove govern exclusion from dropdown if one is already defined
rocco8773 Jan 15, 2025
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
33 changes: 22 additions & 11 deletions bapsf_motion/gui/configure/motion_builder_overlay.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
)
from bapsf_motion.motion_builder import MotionBuilder
from bapsf_motion.motion_builder.layers import layer_registry
from bapsf_motion.motion_builder.exclusions import exclusion_registry
from bapsf_motion.motion_builder.exclusions import exclusion_registry, GovernExclusion
from bapsf_motion.utils import _deepcopy_dict
from bapsf_motion.utils import units as u

Expand Down Expand Up @@ -124,10 +124,7 @@ def __init__(self, mg: MotionGroup, parent: "mgw.MGWidget" = None):
def _connect_signals(self):
super()._connect_signals()

self.configChanged.connect(self.update_canvas)
self.configChanged.connect(self.update_exclusion_list_box)
self.configChanged.connect(self.update_layer_list_box)
self.configChanged.connect(self._validate_mb)
self.configChanged.connect(self._config_changed_handler)

self.add_ex_btn.clicked.connect(self._exclusion_configure_new)
self.remove_ex_btn.clicked.connect(self._exclusion_remove_from_mb)
Expand Down Expand Up @@ -612,6 +609,16 @@ def _define_params_field_widget(self, ex_or_ly, _type):

# -- WIDGET INTERACTION FUNCTIONALITY --

def _config_changed_handler(self):
# Note: none of the methods executed here should cause a
# configChanged event
self._validate_mb()

# now update displays
self.update_exclusion_list_box()
self.update_layer_list_box()
self.update_canvas()

def _exclusion_configure_new(self):
if not self._params_widget.isHidden():
self._hide_and_clear_params_widget()
Expand All @@ -621,6 +628,13 @@ def _exclusion_configure_new(self):
_available = self.exclusion_registry.get_names_by_dimensionality(
self.dimensionality
)
if self.mb.exclusions and isinstance(self.mb.exclusions[0], GovernExclusion):
# remove govern exclusion since we can only have one defined
for name in tuple(_available):
ex = self.exclusion_registry.get_exclusion(name)
if issubclass(ex, GovernExclusion):
_available.remove(name)

self._refresh_params_combo_box(_available)
self.params_combo_box.setObjectName("exclusion")

Expand Down Expand Up @@ -969,12 +983,9 @@ def update_canvas(self):
self.logger.info(f"MB config = {self.mb.config}")

self.mpl_canvas.figure.clear()
ax = self.mpl_canvas.figure.add_subplot(111)
self.mb.mask.plot(
x=self.mb.mask.dims[0],
y=self.mb.mask.dims[1],
ax=ax,
)
ax = self.mpl_canvas.figure.gca()
xdim, ydim = self.mb.mspace_dims
self.mb.mask.plot(x=xdim, y=ydim, ax=ax)

pts = self.mb.motion_list
if pts is not None:
Expand Down
2 changes: 1 addition & 1 deletion bapsf_motion/gui/configure/motion_group_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -586,7 +586,7 @@ def __init__(
deployed_ips = []
if isinstance(self._parent.rm, RunManager):
for mg in self._parent.rm.mgs.values():
if dict_equal(mg_config, mg.config):
if mg_config is not None and dict_equal(mg_config, mg.config):
# assume we are editing an existing motion group
continue

Expand Down
8 changes: 7 additions & 1 deletion bapsf_motion/motion_builder/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,9 @@ def _build_initial_ds(self):

return ds

def _determine_name(self):
return self.base_name

def add_layer(self, ly_type: str, **settings):
"""
Add a "point" layer to the motion builder.
Expand Down Expand Up @@ -279,7 +282,10 @@ def add_exclusion(self, ex_type: str, **settings):

if not isinstance(exclusion, GovernExclusion):
self._exclusions.append(exclusion)
elif not isinstance(self.exclusions[0], GovernExclusion):
elif (
len(self.exclusions) == 0
or not isinstance(self.exclusions[0], GovernExclusion)
):
self._exclusions.insert(0, exclusion)
else:
warnings.warn(
Expand Down
41 changes: 27 additions & 14 deletions bapsf_motion/motion_builder/exclusions/base.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
"""Module that defines the `BaseExclusion` abstract class."""
__all__ = ["BaseExclusion", "GovernExclusion"]

import ast
import numpy as np
import re
import xarray as xr

from abc import ABC, abstractmethod
from typing import Any, Dict, List, Union
from typing import Any, Dict, Union

from bapsf_motion.motion_builder.item import MBItem


class BaseExclusion(ABC, MBItem):
class BaseExclusion(MBItem):
"""
Abstract base class for :term:`motion exclusion` classes.

Expand Down Expand Up @@ -133,18 +134,6 @@ def inputs(self) -> Dict[str, Any]:
"""
return self._inputs

@MBItem.name.setter
def name(self, name: str):
if not self.skip_ds_add:
# The exclusion name is a part of the Dataset management,
# so we can NOT/ should NOT rename it
return
elif not isinstance(name, str):
return

self._name = name
self._name_pattern = re.compile(rf"{name}(?P<number>[0-9]+)")

@abstractmethod
def _generate_exclusion(self) -> Union[np.ndarray, xr.DataArray]:
"""
Expand All @@ -161,6 +150,27 @@ def _validate_inputs(self) -> None:
"""
...

def _determine_name(self):
try:
return self.name
except AttributeError:
# self._name has not been defined yet
pass

names = set(self._ds.data_vars.keys())
ids = []
for name in names:
_match = self.name_pattern.fullmatch(name)
if _match is not None:
ids.append(
ast.literal_eval(_match.group("number"))
)

ids = list(set(ids))
_id = 0 if not ids else ids[-1] + 1

return f"{self.base_name}{_id:d}"

def is_excluded(self, point):
"""
Check if ``point`` resides in an excluded region defined by
Expand Down Expand Up @@ -230,4 +240,7 @@ def update_global_mask(self):
f"the exclusion can not be merged into the global maks."
)

# Since GovernExclusion use the existing mask to generate its own
# mask, the exclusion must be regenerated during every global update
self.regenerate_exclusion()
self.mask[...] = self.exclusion[...]
10 changes: 10 additions & 0 deletions bapsf_motion/motion_builder/exclusions/shadow.py
Original file line number Diff line number Diff line change
Expand Up @@ -654,6 +654,16 @@ def _paint_mask(self, rays: np.ndarray) -> xr.DataArray:
triangles[:, 2, :] - triangles[:, 0, :],
triangles[:, 1, :] - triangles[:, 0, :]
)

zero_mask = denominator == 0
if np.any(zero_mask):
# denominator can be zero if all points on the triangle lie
# on a line
not_zero_mask = np.logical_not(zero_mask)
triangles = triangles[not_zero_mask, ...]
numerator = numerator[..., not_zero_mask]
denominator = denominator[not_zero_mask]

lambda_3 = numerator / denominator[None, None, ...]

# calculate lambda_2
Expand Down
20 changes: 5 additions & 15 deletions bapsf_motion/motion_builder/item.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import re
import xarray as xr

from abc import ABC, abstractmethod
from typing import Hashable, Tuple

try:
Expand All @@ -16,7 +17,7 @@
ErrorOptions = str


class MBItem:
class MBItem(ABC):
r"""
A base class for any :term:`motion builder` class that will interact
with the `xarray` `~xarray.Dataset` containing the
Expand Down Expand Up @@ -155,7 +156,8 @@ def _validate_ds(ds: xr.Dataset) -> xr.Dataset:

return ds

def _determine_name(self):
@abstractmethod
def _determine_name(self) -> str:
"""
Determine the name for the motion builder item that will be used
in the `~xarray.Dataset`. This is generally the name of the
Expand All @@ -165,19 +167,7 @@ def _determine_name(self):
:attr:`name_pattern` and generate a unique :attr:`name` for
the motion builder item.
"""
try:
return self.name
except AttributeError:
# self._name has not been defined yet
pass

names = set(self._ds.data_vars.keys())
n_existing = 0
for name in names:
if self.name_pattern.fullmatch(name) is not None:
n_existing += 1

return f"{self.base_name}{n_existing + 1:d}"
...

def drop_vars(self, names: str, *, errors: ErrorOptions = "raise"):
new_ds = self._ds.drop_vars(names, errors=errors)
Expand Down
26 changes: 24 additions & 2 deletions bapsf_motion/motion_builder/layers/base.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
"""Module that defines the `BaseLayer` abstract class."""
__all__ = ["BaseLayer"]

import ast
import re
import numpy as np
import xarray as xr

from abc import ABC, abstractmethod
from abc import abstractmethod
from typing import Any, Dict, List, Union

from bapsf_motion.motion_builder.item import MBItem


class BaseLayer(ABC, MBItem):
class BaseLayer(MBItem):
"""
Abstract base class for :term:`motion layer` classes.

Expand Down Expand Up @@ -142,6 +143,27 @@ def _validate_inputs(self) -> None:
"""
...

def _determine_name(self):
try:
return self.name
except AttributeError:
# self._name has not been defined yet
pass

names = set(self._ds.data_vars.keys())
ids = []
for name in names:
_match = self.name_pattern.fullmatch(name)
if _match is not None:
ids.append(
ast.literal_eval(_match.group("number"))
)

ids = list(set(ids))
_id = 0 if not ids else ids[-1] + 1

return f"{self.base_name}{_id:d}"

def _generate_point_matrix_da(self):
"""
Generate the :term:`motion layer` array/matrix and add it to
Expand Down
10 changes: 10 additions & 0 deletions docs/notebooks/motion_list/Shadow2DExclusion.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -955,6 +955,16 @@
" triangles[:, 2, :] - triangles[:, 0, :],\n",
" triangles[:, 1, :] - triangles[:, 0, :]\n",
" )\n",
" \n",
" zero_mask = denominator == 0\n",
" if np.any(zero_mask):\n",
" # denominator can be zero if all points on the triangle lie\n",
" # on a line\n",
" not_zero_mask = np.logical_not(zero_mask)\n",
" triangles = triangles[not_zero_mask, ...]\n",
" numerator = numerator[..., not_zero_mask]\n",
" denominator = denominator[not_zero_mask]\n",
"\n",
" lambda_3 = numerator / denominator[None, None, ...]\n",
"\n",
" # calculate lambda_2\n",
Expand Down
Loading