essreduce 24.11.2__py3-none-any.whl → 24.12.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
ess/reduce/live/raw.py CHANGED
@@ -285,7 +285,9 @@ class RollingDetectorView(Detector):
285
285
  wf[RollingDetectorViewWindow] = window
286
286
  if isinstance(projection, LogicalView):
287
287
  wf[LogicalView] = projection
288
- wf[NeXusTransformation[snx.NXdetector, SampleRun]] = sc.scalar(1)
288
+ wf[NeXusTransformation[snx.NXdetector, SampleRun]] = NeXusTransformation[
289
+ snx.NXdetector, SampleRun
290
+ ](sc.scalar(1))
289
291
  wf.insert(RollingDetectorView.from_detector_and_logical_view)
290
292
  elif projection == 'cylinder_mantle_z':
291
293
  wf.insert(make_cylinder_mantle_coords)
ess/reduce/nexus/types.py CHANGED
@@ -78,7 +78,17 @@ RunType = TypeVar(
78
78
  TransmissionRun[BackgroundRun],
79
79
  VanadiumRun,
80
80
  )
81
- """TypeVar used for specifying BackgroundRun, EmptyBeamRun or SampleRun"""
81
+ """TypeVar for specifying what run some data belongs to.
82
+
83
+ Possible values:
84
+
85
+ - :class:`BackgroundRun`
86
+ - :class:`EmptyBeamRun`
87
+ - :class:`SampleRun`
88
+ - :class:`TransmissionRun`
89
+ - :class:`VanadiumRun`
90
+ """
91
+
82
92
 
83
93
  # 1.2 Monitor types
84
94
  Monitor1 = NewType('Monitor1', int)
@@ -91,6 +101,8 @@ Monitor4 = NewType('Monitor4', int)
91
101
  """Identifier for an arbitrary monitor"""
92
102
  Monitor5 = NewType('Monitor5', int)
93
103
  """Identifier for an arbitrary monitor"""
104
+ Monitor6 = NewType('Monitor6', int)
105
+ """Identifier for an arbitrary monitor"""
94
106
  IncidentMonitor = NewType('IncidentMonitor', int)
95
107
  """Incident monitor"""
96
108
  TransmissionMonitor = NewType('TransmissionMonitor', int)
@@ -102,10 +114,24 @@ MonitorType = TypeVar(
102
114
  Monitor3,
103
115
  Monitor4,
104
116
  Monitor5,
117
+ Monitor6,
105
118
  IncidentMonitor,
106
119
  TransmissionMonitor,
107
120
  )
108
- """TypeVar used for specifying the monitor type such as Incident or Transmission"""
121
+ """TypeVar for specifying what monitor some data belongs to.
122
+
123
+ Possible values:
124
+
125
+ - :class:`Monitor1`
126
+ - :class:`Monitor2`
127
+ - :class:`Monitor3`
128
+ - :class:`Monitor4`
129
+ - :class:`Monitor5`
130
+ - :class:`Monitor6`
131
+ - :class:`IncidentMonitor`
132
+ - :class:`TransmissionMonitor`
133
+ """
134
+
109
135
 
110
136
  Component = TypeVar(
111
137
  'Component',
@@ -119,6 +145,7 @@ Component = TypeVar(
119
145
  Monitor3,
120
146
  Monitor4,
121
147
  Monitor5,
148
+ Monitor6,
122
149
  IncidentMonitor,
123
150
  TransmissionMonitor,
124
151
  )
@@ -3,12 +3,12 @@
3
3
 
4
4
  """Workflow and workflow components for interacting with NeXus files."""
5
5
 
6
- from collections.abc import Sequence
6
+ from collections.abc import Iterable
7
7
  from copy import deepcopy
8
8
  from typing import Any
9
9
 
10
- import networkx as nx
11
10
  import sciline
11
+ import sciline.typing
12
12
  import scipp as sc
13
13
  import scippnexus as snx
14
14
  from scipp.constants import g
@@ -649,30 +649,39 @@ def LoadDetectorWorkflow() -> sciline.Pipeline:
649
649
 
650
650
  def GenericNeXusWorkflow(
651
651
  *,
652
- run_types: Sequence[sciline.typing.Key] | None = None,
653
- monitor_types: Sequence[sciline.typing.Key] | None = None,
652
+ run_types: Iterable[sciline.typing.Key] | None = None,
653
+ monitor_types: Iterable[sciline.typing.Key] | None = None,
654
654
  ) -> sciline.Pipeline:
655
655
  """
656
656
  Generic workflow for loading detector and monitor data from a NeXus file.
657
657
 
658
+ It is possible to limit which run types and monitor types
659
+ are supported by the returned workflow.
660
+ This is useful to reduce the size of the workflow and make it easier to inspect.
661
+ Make sure to add *all* required run types and monitor types when using this feature.
662
+
663
+ Attention
664
+ ---------
665
+ Filtering by run type and monitor type does not work with nested type vars.
666
+ E.g., if you have a type like ``Outer[Inner[RunType]]``, this type and its
667
+ provider will be removed.
668
+
658
669
  Parameters
659
670
  ----------
660
671
  run_types:
661
672
  List of run types to include in the workflow. If not provided, all run types
662
- are included. It is recommended to specify run types to avoid creating very
663
- large workflows.
673
+ are included.
674
+ Must be a possible value of :class:`ess.reduce.nexus.types.RunType`.
664
675
  monitor_types:
665
676
  List of monitor types to include in the workflow. If not provided, all monitor
666
- types are included. It is recommended to specify monitor types to avoid creating
667
- very large workflows.
677
+ types are included.
678
+ Must be a possible value of :class:`ess.reduce.nexus.types.MonitorType`.
668
679
 
669
680
  Returns
670
681
  -------
671
682
  :
672
683
  The workflow.
673
684
  """
674
- if monitor_types is not None and run_types is None:
675
- raise ValueError("run_types must be specified if monitor_types is specified")
676
685
  wf = sciline.Pipeline(
677
686
  (
678
687
  *_common_providers,
@@ -685,16 +694,34 @@ def GenericNeXusWorkflow(
685
694
  wf[DetectorBankSizes] = DetectorBankSizes({})
686
695
  wf[PreopenNeXusFile] = PreopenNeXusFile(False)
687
696
 
688
- g = wf.underlying_graph
689
- ancestors = set()
690
- # DetectorData and MonitorData are the "final" outputs, so finding and removing all
691
- # their ancestors is what we need to strip unused run and monitor types.
692
- for rt in run_types or ():
693
- ancestors |= nx.ancestors(g, DetectorData[rt])
694
- ancestors.add(DetectorData[rt])
695
- for mt in monitor_types or ():
696
- ancestors |= nx.ancestors(g, MonitorData[rt, mt])
697
- ancestors.add(MonitorData[rt, mt])
698
- if run_types is not None:
699
- g.remove_nodes_from(set(g.nodes) - ancestors)
697
+ if run_types is not None or monitor_types is not None:
698
+ _prune_type_vars(wf, run_types=run_types, monitor_types=monitor_types)
699
+
700
700
  return wf
701
+
702
+
703
+ def _prune_type_vars(
704
+ workflow: sciline.Pipeline,
705
+ *,
706
+ run_types: Iterable[sciline.typing.Key] | None,
707
+ monitor_types: Iterable[sciline.typing.Key] | None,
708
+ ) -> None:
709
+ # Remove all nodes that use a run type or monitor types that is
710
+ # not listed in the function arguments.
711
+ excluded_run_types = _excluded_type_args(RunType, run_types)
712
+ excluded_monitor_types = _excluded_type_args(MonitorType, monitor_types)
713
+ excluded_types = excluded_run_types | excluded_monitor_types
714
+
715
+ graph = workflow.underlying_graph
716
+ to_remove = [
717
+ node for node in graph if excluded_types & set(getattr(node, "__args__", set()))
718
+ ]
719
+ graph.remove_nodes_from(to_remove)
720
+
721
+
722
+ def _excluded_type_args(
723
+ type_var: Any, keep: Iterable[sciline.typing.Key] | None
724
+ ) -> set[sciline.typing.Key]:
725
+ if keep is None:
726
+ return set()
727
+ return set(type_var.__constraints__) - set(keep)
ess/reduce/parameter.py CHANGED
@@ -71,7 +71,12 @@ class ParamWithOptions(Parameter[T]):
71
71
 
72
72
  @classmethod
73
73
  def from_enum(cls: type[C], t: type[T], default: T) -> C:
74
- return cls(name=str(t), description=t.__doc__, options=t, default=default)
74
+ return cls(
75
+ name=t.__name__,
76
+ description=t.__doc__,
77
+ options=t.__members__,
78
+ default=default,
79
+ )
75
80
 
76
81
 
77
82
  @dataclass
@@ -126,6 +131,11 @@ class StringParameter(Parameter[str]):
126
131
  pass
127
132
 
128
133
 
134
+ @dataclass
135
+ class MultiStringParameter(Parameter[tuple[str, ...]]):
136
+ """Widget for entering multiple strings."""
137
+
138
+
129
139
  @dataclass(kw_only=True)
130
140
  class ParamWithBounds(Parameter[T]):
131
141
  bounds: tuple[T, T]
ess/reduce/ui.py CHANGED
@@ -10,6 +10,7 @@ from ipywidgets import Layout
10
10
 
11
11
  from .parameter import Parameter
12
12
  from .widgets import SwitchWidget, create_parameter_widget, default_layout
13
+ from .widgets._base import get_fields, set_fields
13
14
  from .workflow import (
14
15
  Key,
15
16
  assign_parameter_values,
@@ -81,11 +82,13 @@ class ParameterBox(widgets.VBox):
81
82
  self._input_widgets.clear()
82
83
  self._input_widgets.update(
83
84
  {
84
- node: widgets.HBox([create_parameter_widget(parameter)])
85
- for node, parameter in registry_getter().items()
85
+ node: create_parameter_widget(parameter)
86
+ for node, parameter in new_input_parameters.items()
86
87
  }
87
88
  )
88
- self._input_box.children = list(self._input_widgets.values())
89
+ self._input_box.children = [
90
+ widgets.HBox([widget]) for widget in self._input_widgets.values()
91
+ ]
89
92
 
90
93
  self.parameter_refresh_button.on_click(_refresh_input_box)
91
94
 
@@ -97,8 +100,7 @@ class ParameterBox(widgets.VBox):
97
100
  return {
98
101
  node: widget.value
99
102
  for node, widget_box in self._input_widgets.items()
100
- if (not isinstance((widget := widget_box.children[0]), SwitchWidget))
101
- or widget.enabled
103
+ if (not isinstance((widget := widget_box), SwitchWidget)) or widget.enabled
102
104
  }
103
105
 
104
106
 
@@ -130,7 +132,7 @@ class ResultBox(widgets.VBox):
130
132
  result_registry.clear()
131
133
  result_registry.update(compute_result)
132
134
  for i in compute_result.values():
133
- display.display(display.HTML(i._repr_html_()))
135
+ display.display(i)
134
136
 
135
137
  def clear_output(_: widgets.Button) -> None:
136
138
  self.output.clear_output()
@@ -232,3 +234,82 @@ def workflow_widget(result_registry: dict | None = None) -> widgets.Widget:
232
234
  workflow_selection_box = widgets.HBox([workflow_select], layout=default_layout)
233
235
  workflow_box = widgets.Box(layout=default_layout)
234
236
  return widgets.VBox([workflow_selection_box, workflow_box])
237
+
238
+
239
+ def _get_parameter_box(widget: WorkflowWidget | ParameterBox) -> ParameterBox:
240
+ if isinstance(widget, WorkflowWidget):
241
+ return widget.parameter_box
242
+ elif isinstance(widget, ParameterBox):
243
+ return widget
244
+ else:
245
+ raise TypeError(
246
+ f"Expected target_widget to be a WorkflowWidget or ParameterBox, "
247
+ f"got {type(widget)}."
248
+ )
249
+
250
+
251
+ def set_parameter_widget_values(
252
+ widget: WorkflowWidget | ParameterBox, new_parameter_values: dict[type, Any]
253
+ ) -> None:
254
+ """Set the values of the input widgets in the target widget.
255
+
256
+ Nodes that don't exist in the input widgets will be ignored.
257
+
258
+ Example
259
+ -------
260
+ .. code-block::
261
+
262
+ set_parameter_widget_values(widget, {
263
+ 'WavelengthBins': {'start': 1.0, 'stop': 14.0, 'nbins': 500}
264
+ })
265
+
266
+ Parameters
267
+ ----------
268
+ widget:
269
+ The widget containing the input widgets.
270
+ new_parameter_values:
271
+ A dictionary of values/state to set each fields/state or value of input widgets.
272
+
273
+ Raises
274
+ ------
275
+ TypeError:
276
+ If the widget is not a WorkflowWidget or a ParameterBox.
277
+
278
+ """
279
+ parameter_box = _get_parameter_box(widget)
280
+ # Walk through the existing input widgets and set the values
281
+ # ``node`s that don't exist in the input widgets will be ignored.
282
+ for node, widget in parameter_box._input_widgets.items():
283
+ if node in new_parameter_values:
284
+ # We shouldn't use `get` here because ``None`` is a valid value.
285
+ set_fields(widget, new_parameter_values[node])
286
+
287
+
288
+ def get_parameter_widget_values(
289
+ widget: WorkflowWidget | ParameterBox,
290
+ ) -> dict[type, Any]:
291
+ """Return the current values of the input widgets in the target widget.
292
+
293
+ The result of this function can be used to set the values of the input widgets
294
+ using the :py:func:`~set_parameter_widget_values` function.
295
+
296
+ Parameters
297
+ ----------
298
+ widget:
299
+ The widget containing the input widgets.
300
+
301
+ Returns
302
+ -------
303
+ :
304
+ A dictionary of the current values/state of each input widget.
305
+
306
+ Raises
307
+ ------
308
+ TypeError:
309
+ If the widget is not a WorkflowWidget or a ParameterBox.
310
+
311
+ """
312
+ return {
313
+ node: get_fields(widget)
314
+ for node, widget in _get_parameter_box(widget)._input_widgets.items()
315
+ }
ess/reduce/uncertainty.py CHANGED
@@ -13,7 +13,7 @@ The recommended use of this module is via the :py:func:`broadcast_uncertainties`
13
13
  helper function.
14
14
  """
15
15
 
16
- from enum import Enum
16
+ from enum import Enum, auto
17
17
  from typing import TypeVar, overload
18
18
 
19
19
  import numpy as np
@@ -23,18 +23,18 @@ from scipp.core.concepts import irreducible_mask
23
23
  T = TypeVar("T", bound=sc.Variable | sc.DataArray)
24
24
 
25
25
 
26
- UncertaintyBroadcastMode = Enum(
27
- 'UncertaintyBroadcastMode', ['drop', 'upper_bound', 'fail']
28
- )
29
- """
30
- Mode for broadcasting uncertainties.
26
+ class UncertaintyBroadcastMode(Enum):
27
+ """Mode for broadcasting uncertainties.
31
28
 
32
- - `drop`: Drop variances if the data is broadcasted.
33
- - `upper_bound`: Compute an upper bound for the variances.
34
- - `fail`: Do not broadcast, simply return the input data.
29
+ See https://doi.org/10.3233/JNR-220049 for context.
30
+ """
35
31
 
36
- See https://doi.org/10.3233/JNR-220049 for context.
37
- """
32
+ drop = auto()
33
+ """Drop variances if the data is broadcast."""
34
+ upper_bound = auto()
35
+ """Compute an upper bound for the variances."""
36
+ fail = auto()
37
+ """Do not broadcast, simply return the input data."""
38
38
 
39
39
 
40
40
  @overload
@@ -10,6 +10,7 @@ from ..parameter import (
10
10
  BooleanParameter,
11
11
  FilenameParameter,
12
12
  MultiFilenameParameter,
13
+ MultiStringParameter,
13
14
  ParamWithOptions,
14
15
  StringParameter,
15
16
  Parameter,
@@ -21,9 +22,11 @@ from ..parameter import (
21
22
  from ._config import default_layout, default_style
22
23
 
23
24
  from ._binedges_widget import BinEdgesWidget
25
+ from ._filename_widget import FilenameWidget, MultiFilenameWidget
24
26
  from ._linspace_widget import LinspaceWidget
25
27
  from ._vector_widget import VectorWidget
26
28
  from ._bounds_widget import BoundsWidget
29
+ from ._string_widget import MultiStringWidget, StringWidget
27
30
  from ._switchable_widget import SwitchWidget
28
31
  from ._optional_widget import OptionalWidget
29
32
 
@@ -101,42 +104,41 @@ def boolean_parameter_widget(param: BooleanParameter):
101
104
 
102
105
  @create_parameter_widget.register(StringParameter)
103
106
  def string_parameter_widget(param: StringParameter):
104
- name = param.name
105
- description = param.description
106
- if param.switchable:
107
- return widgets.Text(
108
- description=name,
109
- tooltip=description,
110
- layout=default_layout,
111
- style=default_style,
112
- )
113
- else:
114
- return widgets.Text(
115
- value=param.default,
116
- description=name,
117
- tooltip=description,
118
- layout=default_layout,
119
- style=default_style,
120
- )
107
+ return StringWidget(
108
+ description=param.name,
109
+ value=param.default,
110
+ layout=default_layout,
111
+ style=default_style,
112
+ )
113
+
114
+
115
+ @create_parameter_widget.register(MultiStringParameter)
116
+ def multi_string_parameter_widget(param: MultiStringParameter):
117
+ return MultiStringWidget(
118
+ description=param.name,
119
+ value=param.default,
120
+ layout=default_layout,
121
+ style=default_style,
122
+ )
121
123
 
122
124
 
123
125
  @create_parameter_widget.register(FilenameParameter)
124
126
  def filename_parameter_widget(param: FilenameParameter):
125
- return widgets.Text(
127
+ return FilenameWidget(
126
128
  description=param.name,
129
+ value=param.default,
127
130
  layout=default_layout,
128
131
  style=default_style,
129
- value=param.default,
130
132
  )
131
133
 
132
134
 
133
135
  @create_parameter_widget.register(MultiFilenameParameter)
134
136
  def multi_filename_parameter_widget(param: MultiFilenameParameter):
135
- return widgets.Text(
137
+ return MultiFilenameWidget(
136
138
  description=param.name,
139
+ value=param.default,
137
140
  layout=default_layout,
138
141
  style=default_style,
139
- value=param.default,
140
142
  )
141
143
 
142
144
 
@@ -182,7 +184,9 @@ __all__ = [
182
184
  'BinEdgesWidget',
183
185
  'BoundsWidget',
184
186
  'EssWidget',
187
+ 'FilenameWidget',
185
188
  'LinspaceWidget',
189
+ 'MultiFilenameWidget',
186
190
  'OptionalWidget',
187
191
  'SwitchWidget',
188
192
  'VectorWidget',
@@ -0,0 +1,63 @@
1
+ # SPDX-License-Identifier: BSD-3-Clause
2
+ # Copyright (c) 2024 Scipp contributors (https://github.com/scipp)
3
+ import warnings
4
+ from typing import Any, Protocol, runtime_checkable
5
+
6
+ from ipywidgets import Widget
7
+
8
+
9
+ @runtime_checkable
10
+ class WidgetWithFieldsProtocol(Protocol):
11
+ def set_fields(self, new_values: dict[str, Any]) -> None: ...
12
+
13
+ def get_fields(self) -> dict[str, Any]: ...
14
+
15
+
16
+ class WidgetWithFieldsMixin:
17
+ def set_fields(self, new_values: dict[str, Any]) -> None:
18
+ # Extract valid fields
19
+ new_field_names = set(new_values.keys())
20
+ valid_field_names = new_field_names & set(self.fields.keys())
21
+ # Warn for invalid fields
22
+ invalid_field_names = new_field_names - valid_field_names
23
+ for field_name in invalid_field_names:
24
+ warning_msg = f"Cannot set field '{field_name}'."
25
+ " The field does not exist in the widget."
26
+ "The field value will be ignored."
27
+ warnings.warn(warning_msg, UserWarning, stacklevel=1)
28
+ # Set valid fields
29
+ for field_name in valid_field_names:
30
+ self.fields[field_name].value = new_values[field_name]
31
+
32
+ def get_fields(self) -> dict[str, Any]:
33
+ return {
34
+ field_name: field_sub_widget.value
35
+ for field_name, field_sub_widget in self.fields.items()
36
+ }
37
+
38
+
39
+ def _has_widget_value_setter(widget: Widget) -> bool:
40
+ widget_type = type(widget)
41
+ return (
42
+ widget_property := getattr(widget_type, 'value', None)
43
+ ) is not None and getattr(widget_property, 'fset', None) is not None
44
+
45
+
46
+ def set_fields(widget: Widget, new_values: Any) -> None:
47
+ if isinstance(widget, WidgetWithFieldsProtocol) and isinstance(new_values, dict):
48
+ widget.set_fields(new_values)
49
+ elif _has_widget_value_setter(widget):
50
+ widget.value = new_values
51
+ else:
52
+ warnings.warn(
53
+ f"Cannot set value or fields for widget of type {type(widget)}."
54
+ " The new_value(s) will be ignored.",
55
+ UserWarning,
56
+ stacklevel=1,
57
+ )
58
+
59
+
60
+ def get_fields(widget: Widget) -> Any:
61
+ if isinstance(widget, WidgetWithFieldsProtocol):
62
+ return widget.get_fields()
63
+ return widget.value
@@ -3,6 +3,8 @@
3
3
  import ipywidgets as ipw
4
4
  import scipp as sc
5
5
 
6
+ from ._base import WidgetWithFieldsMixin
7
+
6
8
  UNITS_LIBRARY = {
7
9
  "wavelength": {"options": ("angstrom", "nm")},
8
10
  "Q": {"options": ("1/angstrom", "1/nm")},
@@ -19,7 +21,7 @@ UNITS_LIBRARY = {
19
21
  }
20
22
 
21
23
 
22
- class BinEdgesWidget(ipw.HBox, ipw.ValueWidget):
24
+ class BinEdgesWidget(ipw.HBox, ipw.ValueWidget, WidgetWithFieldsMixin):
23
25
  def __init__(
24
26
  self,
25
27
  name: str,
@@ -4,9 +4,10 @@ import scipp as sc
4
4
  from ipywidgets import FloatText, GridBox, Label, Text, ValueWidget
5
5
 
6
6
  from ..parameter import ParamWithBounds
7
+ from ._base import WidgetWithFieldsMixin
7
8
 
8
9
 
9
- class BoundsWidget(GridBox, ValueWidget):
10
+ class BoundsWidget(GridBox, ValueWidget, WidgetWithFieldsMixin):
10
11
  def __init__(self):
11
12
  super().__init__()
12
13
 
@@ -0,0 +1,10 @@
1
+ # SPDX-License-Identifier: BSD-3-Clause
2
+ # Copyright (c) 2024 Scipp contributors (https://github.com/scipp)
3
+
4
+ from ._string_widget import MultiStringWidget, StringWidget
5
+
6
+
7
+ class FilenameWidget(StringWidget): ...
8
+
9
+
10
+ class MultiFilenameWidget(MultiStringWidget): ...
@@ -3,8 +3,10 @@
3
3
  import scipp as sc
4
4
  from ipywidgets import FloatText, GridBox, IntText, Label, ValueWidget
5
5
 
6
+ from ._base import WidgetWithFieldsMixin
6
7
 
7
- class LinspaceWidget(GridBox, ValueWidget):
8
+
9
+ class LinspaceWidget(GridBox, ValueWidget, WidgetWithFieldsMixin):
8
10
  def __init__(self, dim: str, unit: str):
9
11
  super().__init__()
10
12
 
@@ -0,0 +1,48 @@
1
+ # SPDX-License-Identifier: BSD-3-Clause
2
+ # Copyright (c) 2024 Scipp contributors (https://github.com/scipp)
3
+ from ipywidgets import HBox, Text, ValueWidget
4
+
5
+ from ._config import default_layout
6
+
7
+
8
+ class StringWidget(HBox, ValueWidget):
9
+ def __init__(self, description: str, value: str | None = None, **kwargs):
10
+ super().__init__(layout=default_layout)
11
+ self.text_widget = Text(description=description, value=value, **kwargs)
12
+ self.children = [self.text_widget]
13
+
14
+ @property
15
+ def value(self) -> str | None:
16
+ v = self.text_widget.value.strip()
17
+ if not v:
18
+ return None
19
+ return v
20
+
21
+ @value.setter
22
+ def value(self, value: str | None):
23
+ if value is None:
24
+ self.text_widget.value = ''
25
+ else:
26
+ self.text_widget.value = value
27
+
28
+
29
+ class MultiStringWidget(StringWidget):
30
+ def __init__(
31
+ self, description: str, value: tuple[str, ...] | None = None, **kwargs
32
+ ):
33
+ # Special case handling to allow initialising with a single string
34
+ if not isinstance(value, str) and value is not None:
35
+ value = ', '.join(value)
36
+
37
+ super().__init__(description, value, **kwargs)
38
+
39
+ @property
40
+ def value(self) -> tuple[str, ...]:
41
+ v = super().value
42
+ if v is None:
43
+ return ()
44
+ return tuple(s.strip() for s in v.split(','))
45
+
46
+ @value.setter
47
+ def value(self, value: tuple[str, ...]):
48
+ self.text_widget.value = ', '.join(value)
@@ -4,8 +4,10 @@
4
4
  import scipp as sc
5
5
  from ipywidgets import FloatText, HBox, Label, Text, ValueWidget
6
6
 
7
+ from ._base import WidgetWithFieldsMixin
7
8
 
8
- class VectorWidget(HBox, ValueWidget):
9
+
10
+ class VectorWidget(HBox, ValueWidget, WidgetWithFieldsMixin):
9
11
  def __init__(self, name: str, variable: sc.Variable, components: str):
10
12
  super().__init__()
11
13
 
ess/reduce/workflow.py CHANGED
@@ -86,10 +86,7 @@ def assign_parameter_values(pipeline: Pipeline, values: dict[Key, Any]) -> Pipel
86
86
  """Set a value for a parameter in the pipeline."""
87
87
  pipeline = pipeline.copy()
88
88
  for key, value in values.items():
89
- if (
90
- isinstance(value, tuple)
91
- and (mapper := parameter_mappers.get(key)) is not None
92
- ):
89
+ if (mapper := parameter_mappers.get(key)) is not None:
93
90
  pipeline = mapper(pipeline, value)
94
91
  else:
95
92
  pipeline[key] = value
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: essreduce
3
- Version: 24.11.2
3
+ Version: 24.12.0
4
4
  Summary: Common data reduction tools for the ESS facility
5
5
  Author: Scipp contributors
6
6
  License: BSD 3-Clause License
@@ -0,0 +1,36 @@
1
+ ess/reduce/__init__.py,sha256=-8a2I4mbJdBgNqKxZJe0lAEQJb-lRjX1xmZvGUWCmmU,382
2
+ ess/reduce/data.py,sha256=vaoeAJ6EpK1YghOiAALLdWiW17TgUnnnt0H-RGiGzXk,3756
3
+ ess/reduce/logging.py,sha256=6n8Czq4LZ3OK9ENlKsWSI1M3KvKv6_HSoUiV4__IUlU,357
4
+ ess/reduce/parameter.py,sha256=4sCfoKOI2HuO_Q7JLH_jAXnEOFANSn5P3NdaOBzhJxc,4635
5
+ ess/reduce/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ ess/reduce/streaming.py,sha256=nEO1Xg6LbQ36F44UaVmSNe2MSWmpwz97y9tnlc-z0zA,7493
7
+ ess/reduce/ui.py,sha256=LHgRhcU4ghhbNLk3b64VoA-0oM6yBf4_5a1Smhenmcs,10793
8
+ ess/reduce/uncertainty.py,sha256=LR4O6ApB6Z-W9gC_XW0ajupl8yFG-du0eee1AX_R-gk,6990
9
+ ess/reduce/workflow.py,sha256=sL34T_2Cjl_8iFlegujxI9VyOUwo6erVC8pOXnfWgYw,3060
10
+ ess/reduce/live/__init__.py,sha256=jPQVhihRVNtEDrE20PoKkclKV2aBF1lS7cCHootgFgI,204
11
+ ess/reduce/live/raw.py,sha256=GQmzgh-ByqGYaiB9srRtOxmg6jQqOGNdXxL5gngGy5U,18927
12
+ ess/reduce/live/workflow.py,sha256=bsbwvTqPhRO6mC__3b7MgU7DWwAnOvGvG-t2n22EKq8,4285
13
+ ess/reduce/nexus/__init__.py,sha256=PxJkhlGcFRzVU4SICBhymK5_5FjM5oXPZ8YUpd0v1pE,967
14
+ ess/reduce/nexus/_nexus_loader.py,sha256=-chpzKcZGr2cXmvSYlGiKdKV591mMG-0BsBusGWTer4,16119
15
+ ess/reduce/nexus/json_generator.py,sha256=ME2Xn8L7Oi3uHJk9ZZdCRQTRX-OV_wh9-DJn07Alplk,2529
16
+ ess/reduce/nexus/json_nexus.py,sha256=QrVc0p424nZ5dHX9gebAJppTw6lGZq9404P_OFl1giA,10282
17
+ ess/reduce/nexus/types.py,sha256=aQMPVURUF0JaMl-82LovPxtAuqtvD150AVg17VHPyhM,8982
18
+ ess/reduce/nexus/workflow.py,sha256=jzdh0ubp9Mmb98a04KIeM8Xo9bpAqpnsfwFWz2VllnQ,23676
19
+ ess/reduce/scripts/grow_nexus.py,sha256=hET3h06M0xlJd62E3palNLFvJMyNax2kK4XyJcOhl-I,3387
20
+ ess/reduce/widgets/__init__.py,sha256=cjRJp4qhzCQgXZFtiZgHNmHFJVLo2Z2MozVG_LZYKlI,5281
21
+ ess/reduce/widgets/_base.py,sha256=0OTQsyoHXRdBs14cWtmJVqQqJcM6EIRA8dct_NQWUic,2200
22
+ ess/reduce/widgets/_binedges_widget.py,sha256=ZCQsGjYHnJr9GFUn7NjoZc1CdsnAzm_fMzyF-fTKKVY,2785
23
+ ess/reduce/widgets/_bounds_widget.py,sha256=CAyswvMA49mGMl2413TKBMbuG0ULCQPEaSODq8ghiDo,1084
24
+ ess/reduce/widgets/_config.py,sha256=LywjxYau1hsBZ-c2W1_DQ4CRMaaai3anjA8Q8Hn0Y6Y,222
25
+ ess/reduce/widgets/_filename_widget.py,sha256=jlDZOvECJBBjsmfBHjhn8-ig_DQxp3C-Uy07DITs_nI,262
26
+ ess/reduce/widgets/_linspace_widget.py,sha256=5CWIxd-yZST3OILTHeA0Q6AMYt6FdTSs8UYvKKdmtHg,1240
27
+ ess/reduce/widgets/_optional_widget.py,sha256=A7vwcVwykj7CIyHSPVh6XL5o-8Z1uJTuzha8TR09Zlc,2216
28
+ ess/reduce/widgets/_string_widget.py,sha256=iPAdfANyXHf-nkfhgkyH6gQDklia0LebLTmwi3m-iYQ,1482
29
+ ess/reduce/widgets/_switchable_widget.py,sha256=SZi65C1iShTi5Xhh7W0A7uwA-12AVaH5xmiIWOkc27o,1726
30
+ ess/reduce/widgets/_vector_widget.py,sha256=aTaBqCFHZQhrIoX6-sSqFWCPePEW8HQt5kUio8jP1t8,1203
31
+ essreduce-24.12.0.dist-info/LICENSE,sha256=nVEiume4Qj6jMYfSRjHTM2jtJ4FGu0g-5Sdh7osfEYw,1553
32
+ essreduce-24.12.0.dist-info/METADATA,sha256=nmgXZ0Dnz80YbVTjNuBKomMX4k7n2hyq63TGeva2B7c,3619
33
+ essreduce-24.12.0.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
34
+ essreduce-24.12.0.dist-info/entry_points.txt,sha256=PMZOIYzCifHMTe4pK3HbhxUwxjFaZizYlLD0td4Isb0,66
35
+ essreduce-24.12.0.dist-info/top_level.txt,sha256=0JxTCgMKPLKtp14wb1-RKisQPQWX7i96innZNvHBr-s,4
36
+ essreduce-24.12.0.dist-info/RECORD,,
@@ -1,33 +0,0 @@
1
- ess/reduce/__init__.py,sha256=-8a2I4mbJdBgNqKxZJe0lAEQJb-lRjX1xmZvGUWCmmU,382
2
- ess/reduce/data.py,sha256=vaoeAJ6EpK1YghOiAALLdWiW17TgUnnnt0H-RGiGzXk,3756
3
- ess/reduce/logging.py,sha256=6n8Czq4LZ3OK9ENlKsWSI1M3KvKv6_HSoUiV4__IUlU,357
4
- ess/reduce/parameter.py,sha256=9glQk8UgsPDIdxq007bRi3xTKNe24nuAcNDYzYyTydk,4443
5
- ess/reduce/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
- ess/reduce/streaming.py,sha256=nEO1Xg6LbQ36F44UaVmSNe2MSWmpwz97y9tnlc-z0zA,7493
7
- ess/reduce/ui.py,sha256=EjzyMUODHn-5-aHRXE3DDWUsaPbsQRiUP2F4o1Hq260,8427
8
- ess/reduce/uncertainty.py,sha256=UrS7wB2aK8QoLD3qG2Ud-XmjDl7Y9dANY31k83-cZKs,6982
9
- ess/reduce/workflow.py,sha256=v0_QlTEub9FLeZAbbZMV_NrLJUT61jTRCFa9Wjli7sE,3125
10
- ess/reduce/live/__init__.py,sha256=jPQVhihRVNtEDrE20PoKkclKV2aBF1lS7cCHootgFgI,204
11
- ess/reduce/live/raw.py,sha256=w-h-G-rXIFxwJ9rxIlCLQepGxLmUjtF2rbXtAUxDgZs,18849
12
- ess/reduce/live/workflow.py,sha256=bsbwvTqPhRO6mC__3b7MgU7DWwAnOvGvG-t2n22EKq8,4285
13
- ess/reduce/nexus/__init__.py,sha256=PxJkhlGcFRzVU4SICBhymK5_5FjM5oXPZ8YUpd0v1pE,967
14
- ess/reduce/nexus/_nexus_loader.py,sha256=-chpzKcZGr2cXmvSYlGiKdKV591mMG-0BsBusGWTer4,16119
15
- ess/reduce/nexus/json_generator.py,sha256=ME2Xn8L7Oi3uHJk9ZZdCRQTRX-OV_wh9-DJn07Alplk,2529
16
- ess/reduce/nexus/json_nexus.py,sha256=QrVc0p424nZ5dHX9gebAJppTw6lGZq9404P_OFl1giA,10282
17
- ess/reduce/nexus/types.py,sha256=Bthjg4T2qXYF1rjU9i0DRAEqkCuYXbV3qF2zWPl_s-Q,8571
18
- ess/reduce/nexus/workflow.py,sha256=xcOjfJRECQ_CAxPbc0nr-BbGXe3rUISEDcu3g7D87Nk,22868
19
- ess/reduce/scripts/grow_nexus.py,sha256=hET3h06M0xlJd62E3palNLFvJMyNax2kK4XyJcOhl-I,3387
20
- ess/reduce/widgets/__init__.py,sha256=f5-wlyiUQtUFe2bi_DTTpTSx8jGF50snLdhoawZfLgE,5106
21
- ess/reduce/widgets/_binedges_widget.py,sha256=09VXYAWJammkzyRYvfFpjzn0E2-BSaDco3osnTUuv8c,2720
22
- ess/reduce/widgets/_bounds_widget.py,sha256=p5hADiSGfHKMfvEILQqpcqggu_G3bLf5vNrW5rYTA0s,1020
23
- ess/reduce/widgets/_config.py,sha256=LywjxYau1hsBZ-c2W1_DQ4CRMaaai3anjA8Q8Hn0Y6Y,222
24
- ess/reduce/widgets/_linspace_widget.py,sha256=73nPfNSinxfuHN5-kkBoo2oTuGygbLVXPvxaw35xsaU,1175
25
- ess/reduce/widgets/_optional_widget.py,sha256=A7vwcVwykj7CIyHSPVh6XL5o-8Z1uJTuzha8TR09Zlc,2216
26
- ess/reduce/widgets/_switchable_widget.py,sha256=SZi65C1iShTi5Xhh7W0A7uwA-12AVaH5xmiIWOkc27o,1726
27
- ess/reduce/widgets/_vector_widget.py,sha256=BvuTtc-NGFTkMKIiZS0Elnl34Hi_3A4rthdNLoDI5fM,1138
28
- essreduce-24.11.2.dist-info/LICENSE,sha256=nVEiume4Qj6jMYfSRjHTM2jtJ4FGu0g-5Sdh7osfEYw,1553
29
- essreduce-24.11.2.dist-info/METADATA,sha256=Lv30Wx2xHZw6RqkkkRTcPX3FyLlk1BYtpDVddaKTNmQ,3619
30
- essreduce-24.11.2.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
31
- essreduce-24.11.2.dist-info/entry_points.txt,sha256=PMZOIYzCifHMTe4pK3HbhxUwxjFaZizYlLD0td4Isb0,66
32
- essreduce-24.11.2.dist-info/top_level.txt,sha256=0JxTCgMKPLKtp14wb1-RKisQPQWX7i96innZNvHBr-s,4
33
- essreduce-24.11.2.dist-info/RECORD,,