essreduce 24.11.3__tar.gz → 24.12.0__tar.gz

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.
Files changed (120) hide show
  1. {essreduce-24.11.3/src/essreduce.egg-info → essreduce-24.12.0}/PKG-INFO +1 -1
  2. {essreduce-24.11.3 → essreduce-24.12.0}/docs/api-reference/index.md +1 -0
  3. {essreduce-24.11.3 → essreduce-24.12.0}/pyproject.toml +1 -0
  4. {essreduce-24.11.3 → essreduce-24.12.0}/requirements/base.txt +1 -1
  5. {essreduce-24.11.3 → essreduce-24.12.0}/src/ess/reduce/live/raw.py +3 -1
  6. {essreduce-24.11.3 → essreduce-24.12.0}/src/ess/reduce/nexus/types.py +25 -2
  7. {essreduce-24.11.3 → essreduce-24.12.0}/src/ess/reduce/nexus/workflow.py +49 -22
  8. {essreduce-24.11.3 → essreduce-24.12.0}/src/ess/reduce/parameter.py +6 -1
  9. {essreduce-24.11.3 → essreduce-24.12.0}/src/ess/reduce/ui.py +87 -6
  10. {essreduce-24.11.3 → essreduce-24.12.0}/src/ess/reduce/uncertainty.py +11 -11
  11. essreduce-24.12.0/src/ess/reduce/widgets/_base.py +63 -0
  12. {essreduce-24.11.3 → essreduce-24.12.0}/src/ess/reduce/widgets/_binedges_widget.py +3 -1
  13. {essreduce-24.11.3 → essreduce-24.12.0}/src/ess/reduce/widgets/_bounds_widget.py +2 -1
  14. {essreduce-24.11.3 → essreduce-24.12.0}/src/ess/reduce/widgets/_linspace_widget.py +3 -1
  15. {essreduce-24.11.3 → essreduce-24.12.0}/src/ess/reduce/widgets/_string_widget.py +9 -0
  16. {essreduce-24.11.3 → essreduce-24.12.0}/src/ess/reduce/widgets/_vector_widget.py +3 -1
  17. {essreduce-24.11.3 → essreduce-24.12.0/src/essreduce.egg-info}/PKG-INFO +1 -1
  18. {essreduce-24.11.3 → essreduce-24.12.0}/src/essreduce.egg-info/SOURCES.txt +1 -0
  19. {essreduce-24.11.3 → essreduce-24.12.0}/tests/nexus/workflow_test.py +74 -8
  20. {essreduce-24.11.3 → essreduce-24.12.0}/tests/widget_test.py +78 -3
  21. {essreduce-24.11.3 → essreduce-24.12.0}/.copier-answers.ess.yml +0 -0
  22. {essreduce-24.11.3 → essreduce-24.12.0}/.copier-answers.yml +0 -0
  23. {essreduce-24.11.3 → essreduce-24.12.0}/.github/ISSUE_TEMPLATE/blank.md +0 -0
  24. {essreduce-24.11.3 → essreduce-24.12.0}/.github/ISSUE_TEMPLATE/high-level-requirement.yml +0 -0
  25. {essreduce-24.11.3 → essreduce-24.12.0}/.github/dependabot.yml +0 -0
  26. {essreduce-24.11.3 → essreduce-24.12.0}/.github/workflows/ci.yml +0 -0
  27. {essreduce-24.11.3 → essreduce-24.12.0}/.github/workflows/docs.yml +0 -0
  28. {essreduce-24.11.3 → essreduce-24.12.0}/.github/workflows/nightly_at_main.yml +0 -0
  29. {essreduce-24.11.3 → essreduce-24.12.0}/.github/workflows/nightly_at_release.yml +0 -0
  30. {essreduce-24.11.3 → essreduce-24.12.0}/.github/workflows/python-version-ci +0 -0
  31. {essreduce-24.11.3 → essreduce-24.12.0}/.github/workflows/release.yml +0 -0
  32. {essreduce-24.11.3 → essreduce-24.12.0}/.github/workflows/test.yml +0 -0
  33. {essreduce-24.11.3 → essreduce-24.12.0}/.github/workflows/unpinned.yml +0 -0
  34. {essreduce-24.11.3 → essreduce-24.12.0}/.gitignore +0 -0
  35. {essreduce-24.11.3 → essreduce-24.12.0}/.pre-commit-config.yaml +0 -0
  36. {essreduce-24.11.3 → essreduce-24.12.0}/.python-version +0 -0
  37. {essreduce-24.11.3 → essreduce-24.12.0}/CODE_OF_CONDUCT.md +0 -0
  38. {essreduce-24.11.3 → essreduce-24.12.0}/CONTRIBUTING.md +0 -0
  39. {essreduce-24.11.3 → essreduce-24.12.0}/LICENSE +0 -0
  40. {essreduce-24.11.3 → essreduce-24.12.0}/MANIFEST.in +0 -0
  41. {essreduce-24.11.3 → essreduce-24.12.0}/README.md +0 -0
  42. {essreduce-24.11.3 → essreduce-24.12.0}/conda/meta.yaml +0 -0
  43. {essreduce-24.11.3 → essreduce-24.12.0}/docs/_static/anaconda-icon.js +0 -0
  44. {essreduce-24.11.3 → essreduce-24.12.0}/docs/_static/favicon.svg +0 -0
  45. {essreduce-24.11.3 → essreduce-24.12.0}/docs/_static/logo-dark.svg +0 -0
  46. {essreduce-24.11.3 → essreduce-24.12.0}/docs/_static/logo.svg +0 -0
  47. {essreduce-24.11.3 → essreduce-24.12.0}/docs/_templates/class-template.rst +0 -0
  48. {essreduce-24.11.3 → essreduce-24.12.0}/docs/_templates/doc_version.html +0 -0
  49. {essreduce-24.11.3 → essreduce-24.12.0}/docs/_templates/module-template.rst +0 -0
  50. {essreduce-24.11.3 → essreduce-24.12.0}/docs/about/index.md +0 -0
  51. {essreduce-24.11.3 → essreduce-24.12.0}/docs/conf.py +0 -0
  52. {essreduce-24.11.3 → essreduce-24.12.0}/docs/developer/coding-conventions.md +0 -0
  53. {essreduce-24.11.3 → essreduce-24.12.0}/docs/developer/dependency-management.md +0 -0
  54. {essreduce-24.11.3 → essreduce-24.12.0}/docs/developer/getting-started.md +0 -0
  55. {essreduce-24.11.3 → essreduce-24.12.0}/docs/developer/gui.ipynb +0 -0
  56. {essreduce-24.11.3 → essreduce-24.12.0}/docs/developer/index.md +0 -0
  57. {essreduce-24.11.3 → essreduce-24.12.0}/docs/index.md +0 -0
  58. {essreduce-24.11.3 → essreduce-24.12.0}/docs/user-guide/index.md +0 -0
  59. {essreduce-24.11.3 → essreduce-24.12.0}/docs/user-guide/reduction-workflow-guidelines.md +0 -0
  60. {essreduce-24.11.3 → essreduce-24.12.0}/docs/user-guide/widget.md +0 -0
  61. {essreduce-24.11.3 → essreduce-24.12.0}/requirements/base.in +0 -0
  62. {essreduce-24.11.3 → essreduce-24.12.0}/requirements/basetest.in +0 -0
  63. {essreduce-24.11.3 → essreduce-24.12.0}/requirements/basetest.txt +0 -0
  64. {essreduce-24.11.3 → essreduce-24.12.0}/requirements/ci.in +0 -0
  65. {essreduce-24.11.3 → essreduce-24.12.0}/requirements/ci.txt +0 -0
  66. {essreduce-24.11.3 → essreduce-24.12.0}/requirements/dev.in +0 -0
  67. {essreduce-24.11.3 → essreduce-24.12.0}/requirements/dev.txt +0 -0
  68. {essreduce-24.11.3 → essreduce-24.12.0}/requirements/docs.in +0 -0
  69. {essreduce-24.11.3 → essreduce-24.12.0}/requirements/docs.txt +0 -0
  70. {essreduce-24.11.3 → essreduce-24.12.0}/requirements/make_base.py +0 -0
  71. {essreduce-24.11.3 → essreduce-24.12.0}/requirements/mypy.in +0 -0
  72. {essreduce-24.11.3 → essreduce-24.12.0}/requirements/mypy.txt +0 -0
  73. {essreduce-24.11.3 → essreduce-24.12.0}/requirements/nightly.in +0 -0
  74. {essreduce-24.11.3 → essreduce-24.12.0}/requirements/nightly.txt +0 -0
  75. {essreduce-24.11.3 → essreduce-24.12.0}/requirements/static.in +0 -0
  76. {essreduce-24.11.3 → essreduce-24.12.0}/requirements/static.txt +0 -0
  77. {essreduce-24.11.3 → essreduce-24.12.0}/requirements/test.in +0 -0
  78. {essreduce-24.11.3 → essreduce-24.12.0}/requirements/test.txt +0 -0
  79. {essreduce-24.11.3 → essreduce-24.12.0}/requirements/wheels.in +0 -0
  80. {essreduce-24.11.3 → essreduce-24.12.0}/requirements/wheels.txt +0 -0
  81. {essreduce-24.11.3 → essreduce-24.12.0}/resources/logo.svg +0 -0
  82. {essreduce-24.11.3 → essreduce-24.12.0}/setup.cfg +0 -0
  83. {essreduce-24.11.3 → essreduce-24.12.0}/src/ess/reduce/__init__.py +0 -0
  84. {essreduce-24.11.3 → essreduce-24.12.0}/src/ess/reduce/data.py +0 -0
  85. {essreduce-24.11.3 → essreduce-24.12.0}/src/ess/reduce/live/__init__.py +0 -0
  86. {essreduce-24.11.3 → essreduce-24.12.0}/src/ess/reduce/live/workflow.py +0 -0
  87. {essreduce-24.11.3 → essreduce-24.12.0}/src/ess/reduce/logging.py +0 -0
  88. {essreduce-24.11.3 → essreduce-24.12.0}/src/ess/reduce/nexus/__init__.py +0 -0
  89. {essreduce-24.11.3 → essreduce-24.12.0}/src/ess/reduce/nexus/_nexus_loader.py +0 -0
  90. {essreduce-24.11.3 → essreduce-24.12.0}/src/ess/reduce/nexus/json_generator.py +0 -0
  91. {essreduce-24.11.3 → essreduce-24.12.0}/src/ess/reduce/nexus/json_nexus.py +0 -0
  92. {essreduce-24.11.3 → essreduce-24.12.0}/src/ess/reduce/py.typed +0 -0
  93. {essreduce-24.11.3 → essreduce-24.12.0}/src/ess/reduce/scripts/grow_nexus.py +0 -0
  94. {essreduce-24.11.3 → essreduce-24.12.0}/src/ess/reduce/streaming.py +0 -0
  95. {essreduce-24.11.3 → essreduce-24.12.0}/src/ess/reduce/widgets/__init__.py +0 -0
  96. {essreduce-24.11.3 → essreduce-24.12.0}/src/ess/reduce/widgets/_config.py +0 -0
  97. {essreduce-24.11.3 → essreduce-24.12.0}/src/ess/reduce/widgets/_filename_widget.py +0 -0
  98. {essreduce-24.11.3 → essreduce-24.12.0}/src/ess/reduce/widgets/_optional_widget.py +0 -0
  99. {essreduce-24.11.3 → essreduce-24.12.0}/src/ess/reduce/widgets/_switchable_widget.py +0 -0
  100. {essreduce-24.11.3 → essreduce-24.12.0}/src/ess/reduce/workflow.py +0 -0
  101. {essreduce-24.11.3 → essreduce-24.12.0}/src/essreduce.egg-info/dependency_links.txt +0 -0
  102. {essreduce-24.11.3 → essreduce-24.12.0}/src/essreduce.egg-info/entry_points.txt +0 -0
  103. {essreduce-24.11.3 → essreduce-24.12.0}/src/essreduce.egg-info/requires.txt +0 -0
  104. {essreduce-24.11.3 → essreduce-24.12.0}/src/essreduce.egg-info/top_level.txt +0 -0
  105. {essreduce-24.11.3 → essreduce-24.12.0}/tests/live/raw_test.py +0 -0
  106. {essreduce-24.11.3 → essreduce-24.12.0}/tests/nexus/json_generator_test.py +0 -0
  107. {essreduce-24.11.3 → essreduce-24.12.0}/tests/nexus/json_nexus_examples/array_dataset.json +0 -0
  108. {essreduce-24.11.3 → essreduce-24.12.0}/tests/nexus/json_nexus_examples/dataset.json +0 -0
  109. {essreduce-24.11.3 → essreduce-24.12.0}/tests/nexus/json_nexus_examples/detector.json +0 -0
  110. {essreduce-24.11.3 → essreduce-24.12.0}/tests/nexus/json_nexus_examples/entry.json +0 -0
  111. {essreduce-24.11.3 → essreduce-24.12.0}/tests/nexus/json_nexus_examples/event_data.json +0 -0
  112. {essreduce-24.11.3 → essreduce-24.12.0}/tests/nexus/json_nexus_examples/instrument.json +0 -0
  113. {essreduce-24.11.3 → essreduce-24.12.0}/tests/nexus/json_nexus_examples/log.json +0 -0
  114. {essreduce-24.11.3 → essreduce-24.12.0}/tests/nexus/json_nexus_test.py +0 -0
  115. {essreduce-24.11.3 → essreduce-24.12.0}/tests/nexus/nexus_loader_test.py +0 -0
  116. {essreduce-24.11.3 → essreduce-24.12.0}/tests/package_test.py +0 -0
  117. {essreduce-24.11.3 → essreduce-24.12.0}/tests/scripts/test_grow_nexus.py +0 -0
  118. {essreduce-24.11.3 → essreduce-24.12.0}/tests/streaming_test.py +0 -0
  119. {essreduce-24.11.3 → essreduce-24.12.0}/tests/uncertainty_test.py +0 -0
  120. {essreduce-24.11.3 → essreduce-24.12.0}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: essreduce
3
- Version: 24.11.3
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
@@ -30,6 +30,7 @@
30
30
  logging
31
31
  nexus
32
32
  streaming
33
+ ui
33
34
  uncertainty
34
35
  widgets
35
36
  ```
@@ -68,6 +68,7 @@ addopts = """
68
68
  testpaths = "tests"
69
69
  filterwarnings = [
70
70
  "error",
71
+ 'ignore:\n Sentinel is not a public part of the traitlets API:DeprecationWarning',
71
72
  ]
72
73
 
73
74
  [tool.ruff]
@@ -50,7 +50,7 @@ python-dateutil==2.9.0.post0
50
50
  # scippnexus
51
51
  sciline==24.10.0
52
52
  # via -r base.in
53
- scipp==24.11.1
53
+ scipp==24.11.2
54
54
  # via
55
55
  # -r base.in
56
56
  # scippneutron
@@ -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)
@@ -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)
@@ -108,7 +118,20 @@ MonitorType = TypeVar(
108
118
  IncidentMonitor,
109
119
  TransmissionMonitor,
110
120
  )
111
- """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
+
112
135
 
113
136
  Component = TypeVar(
114
137
  'Component',
@@ -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)
@@ -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
@@ -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
+ }
@@ -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
@@ -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
 
@@ -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
 
@@ -27,6 +27,15 @@ class StringWidget(HBox, ValueWidget):
27
27
 
28
28
 
29
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
+
30
39
  @property
31
40
  def value(self) -> tuple[str, ...]:
32
41
  v = super().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
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: essreduce
3
- Version: 24.11.3
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
@@ -83,6 +83,7 @@ src/ess/reduce/nexus/types.py
83
83
  src/ess/reduce/nexus/workflow.py
84
84
  src/ess/reduce/scripts/grow_nexus.py
85
85
  src/ess/reduce/widgets/__init__.py
86
+ src/ess/reduce/widgets/_base.py
86
87
  src/ess/reduce/widgets/_binedges_widget.py
87
88
  src/ess/reduce/widgets/_bounds_widget.py
88
89
  src/ess/reduce/widgets/_config.py
@@ -12,16 +12,20 @@ from ess.reduce.nexus.types import (
12
12
  BackgroundRun,
13
13
  Choppers,
14
14
  DetectorData,
15
+ EmptyBeamRun,
15
16
  Filename,
16
17
  Monitor1,
17
18
  Monitor2,
18
19
  Monitor3,
19
20
  MonitorData,
21
+ MonitorType,
20
22
  NeXusComponentLocationSpec,
21
23
  NeXusName,
22
24
  NeXusTransformation,
25
+ RunType,
23
26
  SampleRun,
24
27
  TimeInterval,
28
+ TransmissionMonitor,
25
29
  )
26
30
  from ess.reduce.nexus.workflow import (
27
31
  GenericNeXusWorkflow,
@@ -574,16 +578,11 @@ def test_generic_nexus_workflow_load_analyzers() -> None:
574
578
  assert analyzer['usage'] == 'Bragg'
575
579
 
576
580
 
577
- def test_generic_nexus_workflow_raises_if_monitor_types_but_not_run_types_given() -> (
578
- None
579
- ):
580
- with pytest.raises(ValueError, match='run_types'):
581
- GenericNeXusWorkflow(monitor_types=[Monitor1])
582
-
583
-
584
581
  def test_generic_nexus_workflow_includes_only_given_run_and_monitor_types() -> None:
585
582
  wf = GenericNeXusWorkflow(run_types=[SampleRun], monitor_types=[Monitor1, Monitor3])
586
583
  graph = wf.underlying_graph
584
+
585
+ # Check some examples to avoid relying entirely on complicated loops below.
587
586
  assert DetectorData[SampleRun] in graph
588
587
  assert DetectorData[BackgroundRun] not in graph
589
588
  assert MonitorData[SampleRun, Monitor1] in graph
@@ -592,7 +591,11 @@ def test_generic_nexus_workflow_includes_only_given_run_and_monitor_types() -> N
592
591
  assert MonitorData[BackgroundRun, Monitor1] not in graph
593
592
  assert MonitorData[BackgroundRun, Monitor2] not in graph
594
593
  assert MonitorData[BackgroundRun, Monitor3] not in graph
595
- # Many other keys are also removed, this is just an example
594
+ assert Choppers[SampleRun] in graph
595
+ assert Choppers[BackgroundRun] not in graph
596
+ assert Analyzers[SampleRun] in graph
597
+ assert Analyzers[BackgroundRun] not in graph
598
+
596
599
  assert NeXusComponentLocationSpec[Monitor1, SampleRun] in graph
597
600
  assert NeXusComponentLocationSpec[Monitor2, SampleRun] not in graph
598
601
  assert NeXusComponentLocationSpec[Monitor3, SampleRun] in graph
@@ -605,3 +608,66 @@ def test_generic_nexus_workflow_includes_only_given_run_and_monitor_types() -> N
605
608
  assert NeXusComponentLocationSpec[snx.NXdetector, BackgroundRun] not in graph
606
609
  assert NeXusComponentLocationSpec[snx.NXsample, BackgroundRun] not in graph
607
610
  assert NeXusComponentLocationSpec[snx.NXsource, BackgroundRun] not in graph
611
+
612
+ excluded_run_types = set(RunType.__constraints__) - {SampleRun}
613
+ excluded_monitor_types = set(MonitorType.__constraints__) - {Monitor1, Monitor3}
614
+ for node in graph:
615
+ assert_not_contains_type_arg(node, excluded_run_types)
616
+ assert_not_contains_type_arg(node, excluded_monitor_types)
617
+
618
+
619
+ def test_generic_nexus_workflow_includes_only_given_run_types() -> None:
620
+ wf = GenericNeXusWorkflow(run_types=[EmptyBeamRun])
621
+ graph = wf.underlying_graph
622
+
623
+ # Check some examples to avoid relying entirely on complicated loops below.
624
+ assert DetectorData[EmptyBeamRun] in graph
625
+ assert DetectorData[SampleRun] not in graph
626
+ assert MonitorData[EmptyBeamRun, Monitor1] in graph
627
+ assert MonitorData[EmptyBeamRun, Monitor2] in graph
628
+ assert MonitorData[EmptyBeamRun, Monitor3] in graph
629
+ assert MonitorData[SampleRun, Monitor1] not in graph
630
+ assert MonitorData[SampleRun, Monitor2] not in graph
631
+ assert MonitorData[SampleRun, Monitor3] not in graph
632
+ assert Choppers[EmptyBeamRun] in graph
633
+ assert Choppers[SampleRun] not in graph
634
+ assert Analyzers[EmptyBeamRun] in graph
635
+ assert Analyzers[SampleRun] not in graph
636
+
637
+ excluded_run_types = set(RunType.__constraints__) - {EmptyBeamRun}
638
+ for node in graph:
639
+ assert_not_contains_type_arg(node, excluded_run_types)
640
+
641
+
642
+ def test_generic_nexus_workflow_includes_only_given_monitor_types() -> None:
643
+ wf = GenericNeXusWorkflow(monitor_types=[TransmissionMonitor, Monitor1])
644
+ graph = wf.underlying_graph
645
+
646
+ # Check some examples to avoid relying entirely on complicated loops below.
647
+ assert DetectorData[SampleRun] in graph
648
+ assert DetectorData[BackgroundRun] in graph
649
+ assert MonitorData[SampleRun, TransmissionMonitor] in graph
650
+ assert MonitorData[SampleRun, Monitor1] in graph
651
+ assert MonitorData[SampleRun, Monitor2] not in graph
652
+ assert MonitorData[SampleRun, Monitor3] not in graph
653
+ assert MonitorData[BackgroundRun, TransmissionMonitor] in graph
654
+ assert MonitorData[BackgroundRun, Monitor1] in graph
655
+ assert MonitorData[BackgroundRun, Monitor2] not in graph
656
+ assert MonitorData[BackgroundRun, Monitor3] not in graph
657
+ assert Choppers[SampleRun] in graph
658
+ assert Choppers[BackgroundRun] in graph
659
+ assert Analyzers[SampleRun] in graph
660
+ assert Analyzers[BackgroundRun] in graph
661
+
662
+ excluded_monitor_types = set(MonitorType.__constraints__) - {
663
+ Monitor1,
664
+ TransmissionMonitor,
665
+ }
666
+ for node in graph:
667
+ assert_not_contains_type_arg(node, excluded_monitor_types)
668
+
669
+
670
+ def assert_not_contains_type_arg(node: object, excluded: set[type]) -> None:
671
+ assert not any(
672
+ arg in excluded for arg in getattr(node, "__args__", ())
673
+ ), f"Node {node} contains one of {excluded!r}"
@@ -4,12 +4,19 @@ from collections.abc import Callable, Generator
4
4
  from contextlib import contextmanager
5
5
  from typing import Any, NewType
6
6
 
7
+ import pytest
7
8
  import sciline as sl
8
9
  import scipp as sc
9
10
  from ipywidgets import FloatText, IntText
10
11
 
11
- from ess.reduce.parameter import BinEdgesParameter, Parameter, parameter_registry
12
- from ess.reduce.ui import WorkflowWidget, workflow_widget
12
+ from ess.reduce.parameter import (
13
+ BinEdgesParameter,
14
+ FilenameParameter,
15
+ MultiFilenameParameter,
16
+ Parameter,
17
+ parameter_registry,
18
+ )
19
+ from ess.reduce.ui import ResultBox, WorkflowWidget, workflow_widget
13
20
  from ess.reduce.widgets import OptionalWidget, SwitchWidget, create_parameter_widget
14
21
  from ess.reduce.workflow import register_workflow, workflow_registry
15
22
 
@@ -79,7 +86,7 @@ def provider_with_optional(a: OptionalInt, b: OptionalFloat) -> str:
79
86
 
80
87
 
81
88
  def _get_param_widget(widget: WorkflowWidget, param_type: type) -> Any:
82
- return widget.parameter_box._input_widgets[param_type].children[0]
89
+ return widget.parameter_box._input_widgets[param_type]
83
90
 
84
91
 
85
92
  def test_parameter_default_value_test() -> None:
@@ -402,3 +409,71 @@ def test_bin_edges_widget_with_default_values() -> None:
402
409
  assert param_widget.fields['stop'].value == 0.6
403
410
  assert param_widget.fields['nbins'].value == 150
404
411
  assert param_widget.fields['spacing'].value == 'linear'
412
+
413
+
414
+ @pytest.mark.parametrize(
415
+ 'output',
416
+ [
417
+ (sc.scalar(1), sc.scalar(2)),
418
+ 'Test with a string',
419
+ sc.data.binned_xy(100, 10, 10),
420
+ ],
421
+ )
422
+ def test_result_box_can_handle_different_outputs(output):
423
+ was_called = False
424
+
425
+ def run_workflow():
426
+ nonlocal was_called
427
+ was_called = True
428
+ return dict(enumerate(output))
429
+
430
+ ResultBox(run_workflow).run_button.click()
431
+ assert was_called
432
+
433
+
434
+ FilenameSampleRun = NewType('FilenameSampleRun', str)
435
+ parameter_registry[FilenameSampleRun] = FilenameParameter.from_type(
436
+ FilenameSampleRun, default="SampleRun.hdf"
437
+ )
438
+ MultiFilenameSampleRun = NewType('MultiFilenameSampleRun', list[str])
439
+ parameter_registry[MultiFilenameSampleRun] = MultiFilenameParameter.from_type(
440
+ MultiFilenameSampleRun, default=["file1.hdf", "file2.hdf"]
441
+ )
442
+ MultiFilenameBackgroundRun = NewType('MultiFilenameBackgroundRun', list[str])
443
+ parameter_registry[MultiFilenameBackgroundRun] = MultiFilenameParameter.from_type(
444
+ MultiFilenameBackgroundRun, default="background.hdf"
445
+ )
446
+
447
+
448
+ def filename_print_provider(fname: FilenameSampleRun) -> str:
449
+ return str(fname)
450
+
451
+
452
+ def multi_filename_print_provider(fnames: MultiFilenameSampleRun) -> list[str]:
453
+ return [str(fname) for fname in fnames]
454
+
455
+
456
+ def multi_filename_print_provider_bis(fnames: MultiFilenameBackgroundRun) -> list[str]:
457
+ return [str(fname) for fname in fnames]
458
+
459
+
460
+ def test_filename_widget_single() -> None:
461
+ widget = _ready_widget(providers=[filename_print_provider], output_selections=[str])
462
+ param_widget = _get_param_widget(widget, FilenameSampleRun)
463
+ assert param_widget.value == "SampleRun.hdf"
464
+
465
+
466
+ def test_filename_widget_multiple() -> None:
467
+ widget = _ready_widget(
468
+ providers=[multi_filename_print_provider], output_selections=[list[str]]
469
+ )
470
+ param_widget = _get_param_widget(widget, MultiFilenameSampleRun)
471
+ assert param_widget.value == ("file1.hdf", "file2.hdf")
472
+
473
+
474
+ def test_filename_widget_multiple_initialized_with_single_string() -> None:
475
+ widget = _ready_widget(
476
+ providers=[multi_filename_print_provider_bis], output_selections=[list[str]]
477
+ )
478
+ param_widget = _get_param_widget(widget, MultiFilenameBackgroundRun)
479
+ assert param_widget.value == ("background.hdf",)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes