essreduce 24.11.1__py3-none-any.whl → 24.11.3__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/__init__.py CHANGED
@@ -7,7 +7,7 @@ import importlib.metadata
7
7
  from . import nexus, uncertainty
8
8
 
9
9
  try:
10
- __version__ = importlib.metadata.version(__package__ or __name__)
10
+ __version__ = importlib.metadata.version("essreduce")
11
11
  except importlib.metadata.PackageNotFoundError:
12
12
  __version__ = "0.0.0"
13
13
 
ess/reduce/data.py CHANGED
@@ -31,7 +31,16 @@ class Registry:
31
31
  return self._registry.fetch(name, processor=pooch.Unzip() if unzip else None)
32
32
 
33
33
 
34
- _registry = Registry(
34
+ _bifrost_registry = Registry(
35
+ instrument='bifrost',
36
+ files={
37
+ "BIFROST_20240914T053723.h5": "md5:0f2fa5c9a851f8e3a4fa61defaa3752e",
38
+ },
39
+ version='1',
40
+ )
41
+
42
+
43
+ _loki_registry = Registry(
35
44
  instrument='loki',
36
45
  files={
37
46
  # Files from LoKI@Larmor detector test experiment
@@ -57,26 +66,31 @@ _registry = Registry(
57
66
  )
58
67
 
59
68
 
69
+ def bifrost_simulated_elastic() -> str:
70
+ """McStas simulation with elastic incoherent scattering + phonon."""
71
+ return _bifrost_registry.get_path('BIFROST_20240914T053723.h5')
72
+
73
+
60
74
  def loki_tutorial_sample_run_60250() -> str:
61
75
  """Sample run with sample and sample holder/can, no transmission monitor in beam."""
62
- return _registry.get_path('60250-2022-02-28_2215.nxs')
76
+ return _loki_registry.get_path('60250-2022-02-28_2215.nxs')
63
77
 
64
78
 
65
79
  def loki_tutorial_sample_run_60339() -> str:
66
80
  """Sample run with sample and sample holder/can, no transmission monitor in beam."""
67
- return _registry.get_path('60339-2022-02-28_2215.nxs')
81
+ return _loki_registry.get_path('60339-2022-02-28_2215.nxs')
68
82
 
69
83
 
70
84
  def loki_tutorial_background_run_60248() -> str:
71
85
  """Background run with sample holder/can only, no transmission monitor."""
72
- return _registry.get_path('60248-2022-02-28_2215.nxs')
86
+ return _loki_registry.get_path('60248-2022-02-28_2215.nxs')
73
87
 
74
88
 
75
89
  def loki_tutorial_background_run_60393() -> str:
76
90
  """Background run with sample holder/can only, no transmission monitor."""
77
- return _registry.get_path('60393-2022-02-28_2215.nxs')
91
+ return _loki_registry.get_path('60393-2022-02-28_2215.nxs')
78
92
 
79
93
 
80
94
  def loki_tutorial_sample_transmission_run() -> str:
81
95
  """Sample transmission run (sample + sample holder/can + transmission monitor)."""
82
- return _registry.get_path('60394-2022-02-28_2215.nxs')
96
+ return _loki_registry.get_path('60394-2022-02-28_2215.nxs')
@@ -18,6 +18,7 @@ from ._nexus_loader import (
18
18
  load_data,
19
19
  group_event_data,
20
20
  load_component,
21
+ load_all_components,
21
22
  compute_component_position,
22
23
  extract_signal_data_array,
23
24
  )
@@ -25,6 +26,7 @@ from ._nexus_loader import (
25
26
  __all__ = [
26
27
  'types',
27
28
  'group_event_data',
29
+ 'load_all_components',
28
30
  'load_data',
29
31
  'load_component',
30
32
  'compute_component_position',
@@ -3,8 +3,8 @@
3
3
 
4
4
  """NeXus loaders."""
5
5
 
6
- from collections.abc import Mapping
7
- from contextlib import AbstractContextManager, nullcontext
6
+ from collections.abc import Generator, Mapping
7
+ from contextlib import AbstractContextManager, contextmanager, nullcontext
8
8
  from dataclasses import dataclass
9
9
  from math import prod
10
10
  from typing import cast
@@ -13,7 +13,14 @@ import scipp as sc
13
13
  import scippnexus as snx
14
14
 
15
15
  from ..logging import get_logger
16
- from .types import FilePath, NeXusEntryName, NeXusFile, NeXusGroup, NeXusLocationSpec
16
+ from .types import (
17
+ FilePath,
18
+ NeXusAllLocationSpec,
19
+ NeXusEntryName,
20
+ NeXusFile,
21
+ NeXusGroup,
22
+ NeXusLocationSpec,
23
+ )
17
24
 
18
25
 
19
26
  class NoNewDefinitionsType: ...
@@ -28,22 +35,36 @@ def load_component(
28
35
  nx_class: type[snx.NXobject],
29
36
  definitions: Mapping | None | NoNewDefinitionsType = NoNewDefinitions,
30
37
  ) -> sc.DataGroup:
31
- file_path = location.filename
38
+ """Load a single component of a given class from NeXus."""
32
39
  selection = location.selection
33
- entry_name = location.entry_name
34
40
  group_name = location.component_name
35
- with _open_nexus_file(file_path, definitions=definitions) as f:
36
- entry = _unique_child_group(f, snx.NXentry, entry_name)
37
- if nx_class is snx.NXsample:
38
- instrument = entry
39
- else:
40
- instrument = _unique_child_group(entry, snx.NXinstrument, None)
41
- component = _unique_child_group(instrument, nx_class, group_name)
41
+ with _open_component_parent(
42
+ location, nx_class=nx_class, definitions=definitions
43
+ ) as parent:
44
+ component = _unique_child_group(parent, nx_class, group_name)
42
45
  loaded = cast(sc.DataGroup, component[selection])
43
- loaded['nexus_component_name'] = component.name.split('/')[-1]
46
+ loaded['nexus_component_name'] = component.name.rsplit('/', 1)[-1]
44
47
  return loaded
45
48
 
46
49
 
50
+ def load_all_components(
51
+ location: NeXusAllLocationSpec,
52
+ *,
53
+ nx_class: type[snx.NXobject],
54
+ definitions: Mapping | None | NoNewDefinitionsType = NoNewDefinitions,
55
+ ) -> sc.DataGroup:
56
+ """Load all components of a given class from NeXus."""
57
+ with _open_component_parent(
58
+ location, nx_class=nx_class, definitions=definitions
59
+ ) as parent:
60
+ components = sc.DataGroup()
61
+ for name, component in parent[nx_class].items():
62
+ loaded = component[location.selection]
63
+ loaded['nexus_component_name'] = name
64
+ components[name] = loaded
65
+ return components
66
+
67
+
47
68
  def compute_component_position(dg: sc.DataGroup) -> sc.DataGroup:
48
69
  # In some downstream packages we use some of the Nexus components which attempt
49
70
  # to compute positions without having actual Nexus data defining depends_on chains.
@@ -70,7 +91,7 @@ def compute_component_position(dg: sc.DataGroup) -> sc.DataGroup:
70
91
  def _open_nexus_file(
71
92
  file_path: FilePath | NeXusFile | NeXusGroup,
72
93
  definitions: Mapping | None | NoNewDefinitionsType = NoNewDefinitions,
73
- ) -> AbstractContextManager:
94
+ ) -> AbstractContextManager[snx.Group]:
74
95
  if isinstance(file_path, getattr(NeXusGroup, '__supertype__', type(None))):
75
96
  if (
76
97
  definitions is not NoNewDefinitions
@@ -85,6 +106,24 @@ def _open_nexus_file(
85
106
  return snx.File(file_path, definitions=definitions)
86
107
 
87
108
 
109
+ @contextmanager
110
+ def _open_component_parent(
111
+ location: NeXusLocationSpec,
112
+ *,
113
+ nx_class: type[snx.NXobject],
114
+ definitions: Mapping | None | NoNewDefinitionsType = NoNewDefinitions,
115
+ ) -> Generator[snx.Group, None, None]:
116
+ """Locate the parent group of a NeXus component."""
117
+ file_path = location.filename
118
+ entry_name = location.entry_name
119
+ with _open_nexus_file(file_path, definitions=definitions) as f:
120
+ entry = _unique_child_group(f, snx.NXentry, entry_name)
121
+ if nx_class is snx.NXsample:
122
+ yield entry
123
+ else:
124
+ yield _unique_child_group(entry, snx.NXinstrument, None)
125
+
126
+
88
127
  def _unique_child_group(
89
128
  group: snx.Group, nx_class: type[snx.NXobject], name: str | None
90
129
  ) -> snx.Group:
ess/reduce/nexus/types.py CHANGED
@@ -91,6 +91,8 @@ Monitor4 = NewType('Monitor4', int)
91
91
  """Identifier for an arbitrary monitor"""
92
92
  Monitor5 = NewType('Monitor5', int)
93
93
  """Identifier for an arbitrary monitor"""
94
+ Monitor6 = NewType('Monitor6', int)
95
+ """Identifier for an arbitrary monitor"""
94
96
  IncidentMonitor = NewType('IncidentMonitor', int)
95
97
  """Incident monitor"""
96
98
  TransmissionMonitor = NewType('TransmissionMonitor', int)
@@ -102,6 +104,7 @@ MonitorType = TypeVar(
102
104
  Monitor3,
103
105
  Monitor4,
104
106
  Monitor5,
107
+ Monitor6,
105
108
  IncidentMonitor,
106
109
  TransmissionMonitor,
107
110
  )
@@ -112,11 +115,14 @@ Component = TypeVar(
112
115
  snx.NXdetector,
113
116
  snx.NXsample,
114
117
  snx.NXsource,
118
+ snx.NXdisk_chopper,
119
+ snx.NXcrystal,
115
120
  Monitor1,
116
121
  Monitor2,
117
122
  Monitor3,
118
123
  Monitor4,
119
124
  Monitor5,
125
+ Monitor6,
120
126
  IncidentMonitor,
121
127
  TransmissionMonitor,
122
128
  )
@@ -128,7 +134,7 @@ class NeXusName(sciline.Scope[Component, str], str):
128
134
  """Name of a component in a NeXus file."""
129
135
 
130
136
 
131
- class NeXusClass(sciline.Scope[Component, str], str):
137
+ class NeXusClass(sciline.Scope[Component, type], type):
132
138
  """NX_class of a component in a NeXus file."""
133
139
 
134
140
 
@@ -142,6 +148,12 @@ class NeXusComponent(
142
148
  """Raw data from a NeXus component."""
143
149
 
144
150
 
151
+ class AllNeXusComponents(
152
+ sciline.ScopeTwoParams[Component, RunType, sc.DataGroup], sc.DataGroup
153
+ ):
154
+ """Raw data from all NeXus components of one class."""
155
+
156
+
145
157
  class NeXusData(sciline.ScopeTwoParams[Component, RunType, sc.DataArray], sc.DataArray):
146
158
  """
147
159
  Data array loaded from an NXevent_data or NXdata group.
@@ -223,11 +235,26 @@ class NeXusComponentLocationSpec(NeXusLocationSpec, Generic[Component, RunType])
223
235
 
224
236
 
225
237
  @dataclass
226
- class NeXusDataLocationSpec(NeXusLocationSpec, Generic[Component, RunType]):
227
- """NeXus filename and parameters to identify (parts of) detector data to load."""
238
+ class NeXusAllLocationSpec:
239
+ """
240
+ NeXus parameters to identify all components of a class to load.
241
+ """
228
242
 
243
+ filename: FilePath | NeXusFile | NeXusGroup
244
+ entry_name: NeXusEntryName | None = None
245
+ selection: snx.typing.ScippIndex | slice = ()
229
246
 
230
- T = TypeVar('T', bound='NeXusTransformationChain')
247
+
248
+ @dataclass
249
+ class NeXusAllComponentLocationSpec(NeXusAllLocationSpec, Generic[Component, RunType]):
250
+ """
251
+ NeXus parameters to identify all components of a class to load.
252
+ """
253
+
254
+
255
+ @dataclass
256
+ class NeXusDataLocationSpec(NeXusLocationSpec, Generic[Component, RunType]):
257
+ """NeXus filename and parameters to identify (parts of) detector data to load."""
231
258
 
232
259
 
233
260
  class NeXusTransformationChain(
@@ -257,3 +284,17 @@ class NeXusTransformation(Generic[Component, RunType]):
257
284
  raise ValueError(f"Expected scalar transformation, got {chain}")
258
285
  transform = chain.compute()
259
286
  return NeXusTransformation(value=transform)
287
+
288
+
289
+ class Choppers(
290
+ sciline.Scope[RunType, sc.DataGroup[sc.DataGroup[Any]]],
291
+ sc.DataGroup[sc.DataGroup[Any]],
292
+ ):
293
+ """All choppers in a NeXus file."""
294
+
295
+
296
+ class Analyzers(
297
+ sciline.Scope[RunType, sc.DataGroup[sc.DataGroup[Any]]],
298
+ sc.DataGroup[sc.DataGroup[Any]],
299
+ ):
300
+ """All analyzers in a NeXus file."""
@@ -13,12 +13,16 @@ import scipp as sc
13
13
  import scippnexus as snx
14
14
  from scipp.constants import g
15
15
  from scipp.core import label_based_index_to_positional_index
16
+ from scippneutron.chopper import extract_chopper_from_nexus
16
17
 
17
18
  from . import _nexus_loader as nexus
18
19
  from .types import (
20
+ AllNeXusComponents,
21
+ Analyzers,
19
22
  CalibratedBeamline,
20
23
  CalibratedDetector,
21
24
  CalibratedMonitor,
25
+ Choppers,
22
26
  Component,
23
27
  DetectorBankSizes,
24
28
  DetectorData,
@@ -28,6 +32,7 @@ from .types import (
28
32
  MonitorData,
29
33
  MonitorPositionOffset,
30
34
  MonitorType,
35
+ NeXusAllComponentLocationSpec,
31
36
  NeXusClass,
32
37
  NeXusComponent,
33
38
  NeXusComponentLocationSpec,
@@ -96,6 +101,13 @@ def component_spec_by_name(
96
101
  )
97
102
 
98
103
 
104
+ def all_component_spec(
105
+ filename: NeXusFileSpec[RunType],
106
+ ) -> NeXusAllComponentLocationSpec[Component, RunType]:
107
+ """Create a location spec for all components of a class in a NeXus file."""
108
+ return NeXusAllComponentLocationSpec[Component, RunType](filename=filename.value)
109
+
110
+
99
111
  def unique_component_spec(
100
112
  filename: NeXusFileSpec[RunType],
101
113
  ) -> NeXusComponentLocationSpec[UniqueComponent, RunType]:
@@ -173,6 +185,14 @@ def nx_class_for_sample() -> NeXusClass[snx.NXsample]:
173
185
  return NeXusClass[snx.NXsample](snx.NXsample)
174
186
 
175
187
 
188
+ def nx_class_for_disk_chopper() -> NeXusClass[snx.NXdisk_chopper]:
189
+ return NeXusClass[snx.NXdisk_chopper](snx.NXdisk_chopper)
190
+
191
+
192
+ def nx_class_for_crystal() -> NeXusClass[snx.NXcrystal]:
193
+ return NeXusClass[snx.NXcrystal](snx.NXcrystal)
194
+
195
+
176
196
  def load_nexus_component(
177
197
  location: NeXusComponentLocationSpec[Component, RunType],
178
198
  nx_class: NeXusClass[Component],
@@ -206,6 +226,27 @@ def load_nexus_component(
206
226
  )
207
227
 
208
228
 
229
+ def load_all_nexus_components(
230
+ location: NeXusAllComponentLocationSpec[Component, RunType],
231
+ nx_class: NeXusClass[Component],
232
+ ) -> AllNeXusComponents[Component, RunType]:
233
+ """
234
+ Load all NeXus components of one class from one entry a file.
235
+
236
+ This is equivalent to calling :func:`load_nexus_component` for every component.
237
+
238
+ Parameters
239
+ ----------
240
+ location:
241
+ Location spec for the source group.
242
+ nx_class:
243
+ NX_class to identify the components.
244
+ """
245
+ return AllNeXusComponents[Component, RunType](
246
+ nexus.load_all_components(location, nx_class=nx_class, definitions=definitions)
247
+ )
248
+
249
+
209
250
  def load_nexus_data(
210
251
  location: NeXusDataLocationSpec[Component, RunType],
211
252
  ) -> NeXusData[Component, RunType]:
@@ -465,6 +506,26 @@ def assemble_monitor_data(
465
506
  return MonitorData[RunType, MonitorType](_add_variances(da))
466
507
 
467
508
 
509
+ def parse_disk_choppers(
510
+ choppers: AllNeXusComponents[snx.NXdisk_chopper, RunType],
511
+ ) -> Choppers[RunType]:
512
+ """Convert the NeXus representation of a chopper to ours."""
513
+ return Choppers[RunType](
514
+ choppers.apply(
515
+ lambda chopper: extract_chopper_from_nexus(
516
+ nexus.compute_component_position(chopper)
517
+ )
518
+ )
519
+ )
520
+
521
+
522
+ def parse_analyzers(
523
+ analyzers: AllNeXusComponents[snx.NXcrystal, RunType],
524
+ ) -> Analyzers[RunType]:
525
+ """Convert the NeXus representation of an analyzer to ours."""
526
+ return Analyzers[RunType](analyzers.apply(nexus.compute_component_position))
527
+
528
+
468
529
  def _drop(
469
530
  children: dict[str, snx.Field | snx.Group], classes: tuple[snx.NXobject, ...]
470
531
  ) -> dict[str, snx.Field | snx.Group]:
@@ -536,16 +597,20 @@ _common_providers = (
536
597
  full_time_interval,
537
598
  component_spec_by_name,
538
599
  unique_component_spec, # after component_spec_by_name, partially overrides
600
+ all_component_spec,
539
601
  get_transformation_chain,
540
602
  to_transformation,
541
603
  compute_position,
542
604
  load_nexus_data,
543
605
  load_nexus_component,
606
+ load_all_nexus_components,
544
607
  data_by_name,
545
608
  nx_class_for_detector,
546
609
  nx_class_for_monitor,
547
610
  nx_class_for_source,
548
611
  nx_class_for_sample,
612
+ nx_class_for_disk_chopper,
613
+ nx_class_for_crystal,
549
614
  )
550
615
 
551
616
  _monitor_providers = (
@@ -562,6 +627,10 @@ _detector_providers = (
562
627
  assemble_detector_data,
563
628
  )
564
629
 
630
+ _chopper_providers = (parse_disk_choppers,)
631
+
632
+ _analyzer_providers = (parse_analyzers,)
633
+
565
634
 
566
635
  def LoadMonitorWorkflow() -> sciline.Pipeline:
567
636
  """Generic workflow for loading monitor data from a NeXus file."""
@@ -605,7 +674,13 @@ def GenericNeXusWorkflow(
605
674
  if monitor_types is not None and run_types is None:
606
675
  raise ValueError("run_types must be specified if monitor_types is specified")
607
676
  wf = sciline.Pipeline(
608
- (*_common_providers, *_monitor_providers, *_detector_providers)
677
+ (
678
+ *_common_providers,
679
+ *_monitor_providers,
680
+ *_detector_providers,
681
+ *_chopper_providers,
682
+ *_analyzer_providers,
683
+ )
609
684
  )
610
685
  wf[DetectorBankSizes] = DetectorBankSizes({})
611
686
  wf[PreopenNeXusFile] = PreopenNeXusFile(False)
ess/reduce/parameter.py CHANGED
@@ -126,6 +126,11 @@ class StringParameter(Parameter[str]):
126
126
  pass
127
127
 
128
128
 
129
+ @dataclass
130
+ class MultiStringParameter(Parameter[tuple[str, ...]]):
131
+ """Widget for entering multiple strings."""
132
+
133
+
129
134
  @dataclass(kw_only=True)
130
135
  class ParamWithBounds(Parameter[T]):
131
136
  bounds: tuple[T, T]
@@ -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
 
@@ -33,7 +36,6 @@ class EssWidget(Protocol):
33
36
 
34
37
  All widgets should have a `value` property that returns the value of the widget.
35
38
  It can be composed from multiple widgets.
36
- ```
37
39
  """
38
40
 
39
41
  @property
@@ -102,42 +104,41 @@ def boolean_parameter_widget(param: BooleanParameter):
102
104
 
103
105
  @create_parameter_widget.register(StringParameter)
104
106
  def string_parameter_widget(param: StringParameter):
105
- name = param.name
106
- description = param.description
107
- if param.switchable:
108
- return widgets.Text(
109
- description=name,
110
- tooltip=description,
111
- layout=default_layout,
112
- style=default_style,
113
- )
114
- else:
115
- return widgets.Text(
116
- value=param.default,
117
- description=name,
118
- tooltip=description,
119
- layout=default_layout,
120
- style=default_style,
121
- )
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
+ )
122
123
 
123
124
 
124
125
  @create_parameter_widget.register(FilenameParameter)
125
126
  def filename_parameter_widget(param: FilenameParameter):
126
- return widgets.Text(
127
+ return FilenameWidget(
127
128
  description=param.name,
129
+ value=param.default,
128
130
  layout=default_layout,
129
131
  style=default_style,
130
- value=param.default,
131
132
  )
132
133
 
133
134
 
134
135
  @create_parameter_widget.register(MultiFilenameParameter)
135
136
  def multi_filename_parameter_widget(param: MultiFilenameParameter):
136
- return widgets.Text(
137
+ return MultiFilenameWidget(
137
138
  description=param.name,
139
+ value=param.default,
138
140
  layout=default_layout,
139
141
  style=default_style,
140
- value=param.default,
141
142
  )
142
143
 
143
144
 
@@ -183,7 +184,9 @@ __all__ = [
183
184
  'BinEdgesWidget',
184
185
  'BoundsWidget',
185
186
  'EssWidget',
187
+ 'FilenameWidget',
186
188
  'LinspaceWidget',
189
+ 'MultiFilenameWidget',
187
190
  'OptionalWidget',
188
191
  'SwitchWidget',
189
192
  'VectorWidget',
@@ -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): ...
@@ -0,0 +1,39 @@
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
+ @property
31
+ def value(self) -> tuple[str, ...]:
32
+ v = super().value
33
+ if v is None:
34
+ return ()
35
+ return tuple(s.strip() for s in v.split(','))
36
+
37
+ @value.setter
38
+ def value(self, value: tuple[str, ...]):
39
+ self.text_widget.value = ', '.join(value)
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.1
3
+ Version: 24.11.3
4
4
  Summary: Common data reduction tools for the ESS facility
5
5
  Author: Scipp contributors
6
6
  License: BSD 3-Clause License
@@ -45,18 +45,20 @@ Classifier: Programming Language :: Python :: 3 :: Only
45
45
  Classifier: Programming Language :: Python :: 3.10
46
46
  Classifier: Programming Language :: Python :: 3.11
47
47
  Classifier: Programming Language :: Python :: 3.12
48
+ Classifier: Programming Language :: Python :: 3.13
48
49
  Classifier: Topic :: Scientific/Engineering
49
50
  Classifier: Typing :: Typed
50
51
  Requires-Python: >=3.10
51
52
  Description-Content-Type: text/markdown
52
53
  License-File: LICENSE
53
- Requires-Dist: sciline >=24.06.2
54
- Requires-Dist: scipp >=24.02.0
55
- Requires-Dist: scippnexus >=24.11.0
54
+ Requires-Dist: sciline>=24.06.2
55
+ Requires-Dist: scipp>=24.02.0
56
+ Requires-Dist: scippneutron>=24.11.0
57
+ Requires-Dist: scippnexus>=24.11.0
56
58
  Provides-Extra: test
57
- Requires-Dist: ipywidgets ; extra == 'test'
58
- Requires-Dist: pooch ; extra == 'test'
59
- Requires-Dist: pytest ; extra == 'test'
59
+ Requires-Dist: ipywidgets; extra == "test"
60
+ Requires-Dist: pooch; extra == "test"
61
+ Requires-Dist: pytest; extra == "test"
60
62
 
61
63
  [![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](CODE_OF_CONDUCT.md)
62
64
  [![PyPI badge](http://img.shields.io/pypi/v/essreduce.svg)](https://pypi.python.org/pypi/essreduce)
@@ -1,33 +1,35 @@
1
- ess/reduce/__init__.py,sha256=2zbox2LZD92bfion5_v0syHvtoiT8uneMVIskf1x5TU,394
2
- ess/reduce/data.py,sha256=IUkhDFyJagsW779D52qUURzh7RoJUVKBC3vqXScSm2Q,3369
1
+ ess/reduce/__init__.py,sha256=-8a2I4mbJdBgNqKxZJe0lAEQJb-lRjX1xmZvGUWCmmU,382
2
+ ess/reduce/data.py,sha256=vaoeAJ6EpK1YghOiAALLdWiW17TgUnnnt0H-RGiGzXk,3756
3
3
  ess/reduce/logging.py,sha256=6n8Czq4LZ3OK9ENlKsWSI1M3KvKv6_HSoUiV4__IUlU,357
4
- ess/reduce/parameter.py,sha256=9glQk8UgsPDIdxq007bRi3xTKNe24nuAcNDYzYyTydk,4443
4
+ ess/reduce/parameter.py,sha256=R3y2SMk9EIHWfT7_89Sx6Km2mtni5qCWLVGRi8hVO7A,4560
5
5
  ess/reduce/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  ess/reduce/streaming.py,sha256=nEO1Xg6LbQ36F44UaVmSNe2MSWmpwz97y9tnlc-z0zA,7493
7
7
  ess/reduce/ui.py,sha256=EjzyMUODHn-5-aHRXE3DDWUsaPbsQRiUP2F4o1Hq260,8427
8
8
  ess/reduce/uncertainty.py,sha256=UrS7wB2aK8QoLD3qG2Ud-XmjDl7Y9dANY31k83-cZKs,6982
9
- ess/reduce/workflow.py,sha256=v0_QlTEub9FLeZAbbZMV_NrLJUT61jTRCFa9Wjli7sE,3125
9
+ ess/reduce/workflow.py,sha256=sL34T_2Cjl_8iFlegujxI9VyOUwo6erVC8pOXnfWgYw,3060
10
10
  ess/reduce/live/__init__.py,sha256=jPQVhihRVNtEDrE20PoKkclKV2aBF1lS7cCHootgFgI,204
11
11
  ess/reduce/live/raw.py,sha256=w-h-G-rXIFxwJ9rxIlCLQepGxLmUjtF2rbXtAUxDgZs,18849
12
12
  ess/reduce/live/workflow.py,sha256=bsbwvTqPhRO6mC__3b7MgU7DWwAnOvGvG-t2n22EKq8,4285
13
- ess/reduce/nexus/__init__.py,sha256=gqCNkTh-JjjCYomSu9Sa345cE3DJOiSQKBlVIf3-h-0,915
14
- ess/reduce/nexus/_nexus_loader.py,sha256=wwvW9tKUOGyMNe0QRIjycBPbkSIjz4szTLCgL6T4S80,14958
13
+ ess/reduce/nexus/__init__.py,sha256=PxJkhlGcFRzVU4SICBhymK5_5FjM5oXPZ8YUpd0v1pE,967
14
+ ess/reduce/nexus/_nexus_loader.py,sha256=-chpzKcZGr2cXmvSYlGiKdKV591mMG-0BsBusGWTer4,16119
15
15
  ess/reduce/nexus/json_generator.py,sha256=ME2Xn8L7Oi3uHJk9ZZdCRQTRX-OV_wh9-DJn07Alplk,2529
16
16
  ess/reduce/nexus/json_nexus.py,sha256=QrVc0p424nZ5dHX9gebAJppTw6lGZq9404P_OFl1giA,10282
17
- ess/reduce/nexus/types.py,sha256=AWbMxvq_A6sZZQsS4y3M5yCljqocNab7a3TujERlBYw,7640
18
- ess/reduce/nexus/workflow.py,sha256=BnENo_OX3IutBHFoEzKG40d8qourPnqF-cgaFa2nZsU,20607
17
+ ess/reduce/nexus/types.py,sha256=YIXECk-bctwexCvimLrlv97GMLj1UnGHLV3C95IcBOw,8677
18
+ ess/reduce/nexus/workflow.py,sha256=xcOjfJRECQ_CAxPbc0nr-BbGXe3rUISEDcu3g7D87Nk,22868
19
19
  ess/reduce/scripts/grow_nexus.py,sha256=hET3h06M0xlJd62E3palNLFvJMyNax2kK4XyJcOhl-I,3387
20
- ess/reduce/widgets/__init__.py,sha256=tWX73mP11zdAmF9WgbQdm6nDZ-xB80qiKWtKEktKgWM,5114
20
+ ess/reduce/widgets/__init__.py,sha256=cjRJp4qhzCQgXZFtiZgHNmHFJVLo2Z2MozVG_LZYKlI,5281
21
21
  ess/reduce/widgets/_binedges_widget.py,sha256=09VXYAWJammkzyRYvfFpjzn0E2-BSaDco3osnTUuv8c,2720
22
22
  ess/reduce/widgets/_bounds_widget.py,sha256=p5hADiSGfHKMfvEILQqpcqggu_G3bLf5vNrW5rYTA0s,1020
23
23
  ess/reduce/widgets/_config.py,sha256=LywjxYau1hsBZ-c2W1_DQ4CRMaaai3anjA8Q8Hn0Y6Y,222
24
+ ess/reduce/widgets/_filename_widget.py,sha256=jlDZOvECJBBjsmfBHjhn8-ig_DQxp3C-Uy07DITs_nI,262
24
25
  ess/reduce/widgets/_linspace_widget.py,sha256=73nPfNSinxfuHN5-kkBoo2oTuGygbLVXPvxaw35xsaU,1175
25
26
  ess/reduce/widgets/_optional_widget.py,sha256=A7vwcVwykj7CIyHSPVh6XL5o-8Z1uJTuzha8TR09Zlc,2216
27
+ ess/reduce/widgets/_string_widget.py,sha256=r0PrFP4OOU_UTmwlHYDWcaFMlnYS_NUp7iCic9ehBAU,1148
26
28
  ess/reduce/widgets/_switchable_widget.py,sha256=SZi65C1iShTi5Xhh7W0A7uwA-12AVaH5xmiIWOkc27o,1726
27
29
  ess/reduce/widgets/_vector_widget.py,sha256=BvuTtc-NGFTkMKIiZS0Elnl34Hi_3A4rthdNLoDI5fM,1138
28
- essreduce-24.11.1.dist-info/LICENSE,sha256=nVEiume4Qj6jMYfSRjHTM2jtJ4FGu0g-5Sdh7osfEYw,1553
29
- essreduce-24.11.1.dist-info/METADATA,sha256=4oWdudXqzZZ20a_ZPeruGzHb2hGanK4t9vYtXANrcCE,3537
30
- essreduce-24.11.1.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
31
- essreduce-24.11.1.dist-info/entry_points.txt,sha256=PMZOIYzCifHMTe4pK3HbhxUwxjFaZizYlLD0td4Isb0,66
32
- essreduce-24.11.1.dist-info/top_level.txt,sha256=0JxTCgMKPLKtp14wb1-RKisQPQWX7i96innZNvHBr-s,4
33
- essreduce-24.11.1.dist-info/RECORD,,
30
+ essreduce-24.11.3.dist-info/LICENSE,sha256=nVEiume4Qj6jMYfSRjHTM2jtJ4FGu0g-5Sdh7osfEYw,1553
31
+ essreduce-24.11.3.dist-info/METADATA,sha256=wpqZdOA7tOrAlGWLiDPzN3E9Nh8-6B6Pk3CFOSSdr-Y,3619
32
+ essreduce-24.11.3.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
33
+ essreduce-24.11.3.dist-info/entry_points.txt,sha256=PMZOIYzCifHMTe4pK3HbhxUwxjFaZizYlLD0td4Isb0,66
34
+ essreduce-24.11.3.dist-info/top_level.txt,sha256=0JxTCgMKPLKtp14wb1-RKisQPQWX7i96innZNvHBr-s,4
35
+ essreduce-24.11.3.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.3.0)
2
+ Generator: setuptools (75.6.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5