-
Notifications
You must be signed in to change notification settings - Fork 2
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
Add FlatVoxelViewer for DREAM #90
Conversation
observer(change) | ||
|
||
|
||
def _flat_voxel_figure( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does the flattening scheme only apply to dream, or could we make this a generic tool for other instruments like the MaskingTool in scippneutron?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It can be generalised easily. Which other instruments have multi-logical-dimensional detectors?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Most of them? I remember when Morten was talking about the EFU, all instruments seems to have some logical ordering. Loki for sure has banks, tubes, straws, pixels.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would suggest, we merge this here and ask for feedback. If that is positive, we can generalise the viewer and move it into ESSreduce. What do you think?
src/ess/dream/diagnostics.py
Outdated
|
||
|
||
class FlatVoxelViewer(ipw.VBox): | ||
def __init__(self, data: sc.DataGroup) -> None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add docstring?
src/ess/dream/diagnostics.py
Outdated
if names != 'value': | ||
super().observe(handler, names, *args, **kwargs) | ||
else: | ||
self._value_observers.append(handler) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure I understand the mechanism with the value_observers
. Maybe you can explain to me?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is about updating only once when dimensions are swapped between x and y. I tried to find a way to stop widgets from emitting messages. But I couldn't get this to work. So instead, I put a custom layer in between so that I can control what messages make it to the user.
All observers with names='value
are registered in self._value_observers
. And the actual handler for the toggle buttons is self._observe_values
. This function calls the user handlers when appropriate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about having a lock?
Add a self._lock = False
.
Then in the function that gets called on update:
def update(change):
if self._lock:
return
if other.value == clicked.value:
self._lock = True
other.value = change["old"]
self._lock = False
...
Would that work? It would simplify a lot of the logic I think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this would work. This would prevent updating within _DimensionSelector
while the lock is held. But the observers of the button that control the actual figure would still get called on other.value = change["old"]
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the implementation I'm suggesting:
class _DimensionSelector(ipw.VBox):
def __init__(self, dims: tuple[str, ...], callback: Callable) -> None:
self._lock = False
self._callback = callback
style = {'button_width': '10em'}
options = {dim.capitalize(): dim for dim in dims}
default_h, default_v = self._default_dims(dims)
self._horizontal_buttons = ipw.ToggleButtons(
options=options, value=default_h, style=style
)
self._vertical_buttons = ipw.ToggleButtons(
options=options, value=default_v, style=style
)
self._horizontal_buttons.observe(self.update, names='value')
self._vertical_buttons.observe(self.update, names='value')
super().__init__(
[
ipw.HBox([ipw.Label('X'), self._horizontal_buttons]),
ipw.HBox([ipw.Label('Y'), self._vertical_buttons]),
]
)
def set_dims(self, new_dims: tuple[str, ...]) -> None:
default_h, default_v = self._default_dims(new_dims)
options = {dim.capitalize(): dim for dim in new_dims}
self._lock = True
self._horizontal_buttons.options = options
self._vertical_buttons.options = options
self._horizontal_buttons.value = default_h
self._vertical_buttons.value = default_v
self._lock = False
@staticmethod
def _default_dims(dims: tuple[str, ...]) -> tuple[str, str]:
return dims[math.ceil(len(dims) / 2)], dims[0]
@property
def value(self):
return {
'horizontal': self._horizontal_buttons.value,
'vertical': self._vertical_buttons.value,
}
def update(self, change: dict) -> None:
if self._lock:
return
clicked = change['owner']
other = (
self._vertical_buttons
if clicked is self._horizontal_buttons
else self._horizontal_buttons
)
if other.value == clicked.value:
self._lock = True
other.value = change["old"]
self._lock = False
self._callback(change)
and then in the FlatVoxelViewer
you would simply have
self._dim_selector = _DimensionSelector(
self._bank.dims, callback=self._update_view
)
(note that in the _DimensionSelector
you don't need to put the togglebuttons in boxes to change their options, you can just set togglebuttons.options = new_dims
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems to work. Thanks!
I don't think this solution is valid for multi-threaded applications. But I am not 100% confident about my old implementation either. And it should be fine because ipywidgets does not spawn threads, right?
src/ess/dream/diagnostics.py
Outdated
h_labels = [str(value) for value in h_coord.values] | ||
v_labels = [str(value) for value in v_coord.values] | ||
|
||
fig = flat.plot(rasterized=True) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would probably make rasterized
a kwarg for creating the viewer? (with True
as default).
The code may need to be updated when #92 is merged. So I propose to wait for that PR. |
92f8f1f
to
4c370b3
Compare
fd2db06
to
980a14a
Compare
Simplifies the logic of suppressing duplicate and invalid updates. Courtesy of Neil Vaytet.
980a14a
to
de94bc6
Compare
Simple voxel diagnostics tool that can show all voxels and does not require summing or slicing.