lima2.client 2.1.0__tar.gz → 2.1.1__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 (48) hide show
  1. {lima2_client-2.1.0/src/lima2.client.egg-info → lima2_client-2.1.1}/PKG-INFO +1 -1
  2. {lima2_client-2.1.0 → lima2_client-2.1.1}/src/lima2/client/pipeline.py +36 -17
  3. {lima2_client-2.1.0 → lima2_client-2.1.1}/src/lima2/client/pipelines/legacy.py +9 -7
  4. {lima2_client-2.1.0 → lima2_client-2.1.1}/src/lima2/client/pipelines/smx.py +17 -14
  5. {lima2_client-2.1.0 → lima2_client-2.1.1}/src/lima2/client/pipelines/xpcs.py +13 -11
  6. {lima2_client-2.1.0 → lima2_client-2.1.1}/src/lima2/client/state_machine.py +19 -10
  7. {lima2_client-2.1.0 → lima2_client-2.1.1}/src/lima2/client/topology.py +3 -2
  8. {lima2_client-2.1.0 → lima2_client-2.1.1}/src/lima2/master_file/generate.py +54 -27
  9. {lima2_client-2.1.0 → lima2_client-2.1.1/src/lima2.client.egg-info}/PKG-INFO +1 -1
  10. {lima2_client-2.1.0 → lima2_client-2.1.1}/tests/test_pipeline.py +35 -5
  11. {lima2_client-2.1.0 → lima2_client-2.1.1}/LICENSE +0 -0
  12. {lima2_client-2.1.0 → lima2_client-2.1.1}/README.md +0 -0
  13. {lima2_client-2.1.0 → lima2_client-2.1.1}/pyproject.toml +0 -0
  14. {lima2_client-2.1.0 → lima2_client-2.1.1}/setup.cfg +0 -0
  15. {lima2_client-2.1.0 → lima2_client-2.1.1}/src/lima2/client/__init__.py +0 -0
  16. {lima2_client-2.1.0 → lima2_client-2.1.1}/src/lima2/client/client.py +0 -0
  17. {lima2_client-2.1.0 → lima2_client-2.1.1}/src/lima2/client/convert.py +0 -0
  18. {lima2_client-2.1.0 → lima2_client-2.1.1}/src/lima2/client/detector.py +0 -0
  19. {lima2_client-2.1.0 → lima2_client-2.1.1}/src/lima2/client/devencoded/__init__.py +0 -0
  20. {lima2_client-2.1.0 → lima2_client-2.1.1}/src/lima2/client/devencoded/dense_frame.py +0 -0
  21. {lima2_client-2.1.0 → lima2_client-2.1.1}/src/lima2/client/devencoded/exception.py +0 -0
  22. {lima2_client-2.1.0 → lima2_client-2.1.1}/src/lima2/client/devencoded/sparse_frame.py +0 -0
  23. {lima2_client-2.1.0 → lima2_client-2.1.1}/src/lima2/client/devencoded/structured_array.py +0 -0
  24. {lima2_client-2.1.0 → lima2_client-2.1.1}/src/lima2/client/pipelines/__init__.py +0 -0
  25. {lima2_client-2.1.0 → lima2_client-2.1.1}/src/lima2/client/progress_counter.py +0 -0
  26. {lima2_client-2.1.0 → lima2_client-2.1.1}/src/lima2/client/py.typed +0 -0
  27. {lima2_client-2.1.0 → lima2_client-2.1.1}/src/lima2/client/shell.py +0 -0
  28. {lima2_client-2.1.0 → lima2_client-2.1.1}/src/lima2/client/utils.py +0 -0
  29. {lima2_client-2.1.0 → lima2_client-2.1.1}/src/lima2/server/__init__.py +0 -0
  30. {lima2_client-2.1.0 → lima2_client-2.1.1}/src/lima2/server/control.py +0 -0
  31. {lima2_client-2.1.0 → lima2_client-2.1.1}/src/lima2/server/receiver.py +0 -0
  32. {lima2_client-2.1.0 → lima2_client-2.1.1}/src/lima2/server/timer.py +0 -0
  33. {lima2_client-2.1.0 → lima2_client-2.1.1}/src/lima2.client.egg-info/SOURCES.txt +0 -0
  34. {lima2_client-2.1.0 → lima2_client-2.1.1}/src/lima2.client.egg-info/dependency_links.txt +0 -0
  35. {lima2_client-2.1.0 → lima2_client-2.1.1}/src/lima2.client.egg-info/entry_points.txt +0 -0
  36. {lima2_client-2.1.0 → lima2_client-2.1.1}/src/lima2.client.egg-info/requires.txt +0 -0
  37. {lima2_client-2.1.0 → lima2_client-2.1.1}/src/lima2.client.egg-info/top_level.txt +0 -0
  38. {lima2_client-2.1.0 → lima2_client-2.1.1}/tests/test_client.py +0 -0
  39. {lima2_client-2.1.0 → lima2_client-2.1.1}/tests/test_convert.py +0 -0
  40. {lima2_client-2.1.0 → lima2_client-2.1.1}/tests/test_dense_frame.py +0 -0
  41. {lima2_client-2.1.0 → lima2_client-2.1.1}/tests/test_detector.py +0 -0
  42. {lima2_client-2.1.0 → lima2_client-2.1.1}/tests/test_pipelines.py +0 -0
  43. {lima2_client-2.1.0 → lima2_client-2.1.1}/tests/test_progress_counter.py +0 -0
  44. {lima2_client-2.1.0 → lima2_client-2.1.1}/tests/test_sparse_frame.py +0 -0
  45. {lima2_client-2.1.0 → lima2_client-2.1.1}/tests/test_state_machine.py +0 -0
  46. {lima2_client-2.1.0 → lima2_client-2.1.1}/tests/test_timer.py +0 -0
  47. {lima2_client-2.1.0 → lima2_client-2.1.1}/tests/test_topology.py +0 -0
  48. {lima2_client-2.1.0 → lima2_client-2.1.1}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: lima2.client
3
- Version: 2.1.0
3
+ Version: 2.1.1
4
4
  Summary: Python client library for Lima2 detector
5
5
  Home-page: https://gitlab.esrf.fr/limagroup/lima2-client
6
6
  Author: BCU (ESRF)
@@ -18,15 +18,17 @@ import traceback
18
18
  from collections.abc import Callable
19
19
  from concurrent.futures import ThreadPoolExecutor
20
20
  from dataclasses import dataclass
21
+ from enum import Enum, auto
21
22
  from uuid import UUID
22
23
 
23
24
  import numpy as np
24
25
  import tango
25
26
  from jsonschema_default import create_from
27
+ from typing_extensions import Optional
26
28
 
27
29
  from lima2.client import progress_counter, utils
28
30
  from lima2.client.convert import frame_info_to_shape_dtype
29
- from lima2.client.devencoded import structured_array
31
+ from lima2.client.devencoded import dense_frame, sparse_frame, structured_array
30
32
  from lima2.client.progress_counter import ProgressCounter, SingleCounter
31
33
  from lima2.client.topology import (
32
34
  RoundRobinTopology,
@@ -40,29 +42,44 @@ from lima2.client.utils import classproperty
40
42
  logger = logging.getLogger(__name__)
41
43
 
42
44
 
45
+ class FrameType(Enum):
46
+ DENSE = auto()
47
+ SPARSE = auto()
48
+
49
+
50
+ decoder_by_type: dict[FrameType, Callable] = {
51
+ FrameType.DENSE: dense_frame.decode,
52
+ FrameType.SPARSE: sparse_frame.decode,
53
+ }
54
+ """Mapping from frame type to associated decode function"""
55
+
56
+
43
57
  @dataclass
44
58
  class FrameSource:
45
- """Specifies the name of a getter and a decode function for a given frame source.
59
+ """Specifies the name of a getter and a frame type for a given frame source.
46
60
 
47
61
  From these two attributes, it is possible to fetch a frame from a processing tango
48
62
  device and then decode it.
49
63
  """
50
64
 
51
65
  getter_name: str
52
- decoder: Callable
53
-
66
+ """Name of the getter method to call on the tango device to fetch the data"""
54
67
 
55
- class Pipeline:
56
- """A base class for all processing pipelines."""
68
+ frame_type: FrameType
69
+ """Frame type"""
57
70
 
58
- SAVING_PARAMS_NAMES: list[str]
59
- """Names of members of type "saving_params" in the corresponding lima2 "proc_params" struct.
71
+ saving_channel: Optional[str]
72
+ """Name of the associated saving_params object in the proc_params struct.
60
73
 
61
- Definition in child classes is enforced by __init_subclass__().
74
+ Can be None for sources without persistency.
62
75
  """
63
76
 
77
+
78
+ class Pipeline:
79
+ """A base class for all processing pipelines."""
80
+
64
81
  FRAME_SOURCES: dict[str, FrameSource]
65
- """Map of available frame source names to a corresponding getter name and decode function.
82
+ """Map of available frame source names to a corresponding FrameSource descriptor.
66
83
 
67
84
  Definition in child classes is enforced by __init_subclass__().
68
85
  """
@@ -128,11 +145,6 @@ class Pipeline:
128
145
  @classmethod
129
146
  def __init_subclass__(cls) -> None:
130
147
  """Initialize a pipeline subclass."""
131
- if not hasattr(cls, "SAVING_PARAMS_NAMES"):
132
- raise ValueError(
133
- f"Pipeline subclass {cls} must define a SAVING_PARAMS_NAMES class member"
134
- )
135
-
136
148
  if not hasattr(cls, "FRAME_SOURCES"):
137
149
  raise ValueError(
138
150
  f"Pipeline subclass {cls} must define a FRAME_SOURCES class member"
@@ -225,6 +237,13 @@ class Pipeline:
225
237
  f"Available ones are: {list(self.FRAME_SOURCES.keys())}"
226
238
  )
227
239
 
240
+ try:
241
+ decode = decoder_by_type[frame_source.frame_type]
242
+ except KeyError as e:
243
+ raise NotImplementedError(
244
+ f"Frame type {frame_source.frame_type} decoder missing"
245
+ ) from e
246
+
228
247
  getters = [getattr(dev, frame_source.getter_name) for dev in self._devs]
229
248
 
230
249
  if frame_idx == -1:
@@ -238,7 +257,7 @@ class Pipeline:
238
257
  Returns None if the receiver hasn't yet processed any frames.
239
258
  """
240
259
  try:
241
- return frame_source.decoder(getters[rcv_idx](-1))
260
+ return decode(getters[rcv_idx](-1))
242
261
  except tango.DevFailed:
243
262
  return None
244
263
 
@@ -264,7 +283,7 @@ class Pipeline:
264
283
  # Get a frame by absolute index -> use topology-specific lookup()
265
284
  frame = self.topology.lookup(frame_idx=frame_idx, getters=getters)
266
285
 
267
- return frame_source.decoder(frame)
286
+ return decode(frame)
268
287
 
269
288
  def cache_byproduct(self, key: str, data: np.ndarray) -> None:
270
289
  """Store `data` into `self.byproduct_cache[key]`.
@@ -13,8 +13,7 @@ from uuid import UUID
13
13
  import tango
14
14
  from beartype import beartype
15
15
 
16
- from lima2.client.devencoded import dense_frame
17
- from lima2.client.pipeline import FrameSource, Pipeline
16
+ from lima2.client.pipeline import FrameSource, FrameType, Pipeline
18
17
  from lima2.client.topology import TopologyKind
19
18
 
20
19
  # Create a logger
@@ -25,14 +24,17 @@ _logger = logging.getLogger(__name__)
25
24
  class Legacy(Pipeline):
26
25
  tango_class = "LimaProcessingLegacy"
27
26
 
28
- SAVING_PARAMS_NAMES = ["saving"]
29
- """Members of type "saving_params" in the corresponding lima2 "proc_params" struct."""
30
-
31
27
  FRAME_SOURCES = {
32
28
  "input_frame": FrameSource(
33
- getter_name="getInputFrame", decoder=dense_frame.decode
29
+ getter_name="getInputFrame",
30
+ frame_type=FrameType.DENSE,
31
+ saving_channel=None,
32
+ ),
33
+ "frame": FrameSource(
34
+ getter_name="getFrame",
35
+ frame_type=FrameType.DENSE,
36
+ saving_channel="saving",
34
37
  ),
35
- "frame": FrameSource(getter_name="getFrame", decoder=dense_frame.decode),
36
38
  }
37
39
  """Available frame sources."""
38
40
 
@@ -16,10 +16,9 @@ import tango
16
16
  from lima2.client import utils
17
17
  from lima2.client.devencoded import (
18
18
  dense_frame,
19
- sparse_frame,
20
19
  structured_array,
21
20
  )
22
- from lima2.client.pipeline import FrameSource, Pipeline
21
+ from lima2.client.pipeline import FrameSource, FrameType, Pipeline
23
22
  from lima2.client.topology import TopologyKind
24
23
 
25
24
  # Create a logger
@@ -29,23 +28,27 @@ logger = logging.getLogger(__name__)
29
28
  class Smx(Pipeline):
30
29
  tango_class = "LimaProcessingSmx"
31
30
 
32
- # Members of type "saving_params" in the corresponding lima2 "proc_params" struct.
33
- SAVING_PARAMS_NAMES = [
34
- "saving_dense",
35
- "saving_sparse",
36
- "saving_accumulation_corrected",
37
- "saving_accumulation_peak",
38
- ]
39
-
40
31
  FRAME_SOURCES = {
41
- "frame": FrameSource(getter_name="getFrame", decoder=dense_frame.decode),
32
+ "frame": FrameSource(
33
+ getter_name="getFrame",
34
+ frame_type=FrameType.DENSE,
35
+ saving_channel="saving_dense",
36
+ ),
42
37
  "sparse_frame": FrameSource(
43
- getter_name="getSparseFrame", decoder=sparse_frame.decode
38
+ getter_name="getSparseFrame",
39
+ frame_type=FrameType.SPARSE,
40
+ saving_channel="saving_sparse",
44
41
  ),
45
42
  "acc_corrected": FrameSource(
46
- getter_name="getAccCorrected", decoder=dense_frame.decode
43
+ getter_name="getAccCorrected",
44
+ frame_type=FrameType.DENSE,
45
+ saving_channel="saving_accumulation_corrected",
46
+ ),
47
+ "acc_peaks": FrameSource(
48
+ getter_name="getAccPeaks",
49
+ frame_type=FrameType.DENSE,
50
+ saving_channel="saving_accumulation_peak",
47
51
  ),
48
- "acc_peaks": FrameSource(getter_name="getAccPeaks", decoder=dense_frame.decode),
49
52
  }
50
53
  """Available frame sources."""
51
54
 
@@ -14,8 +14,8 @@ import numpy as np
14
14
  import tango
15
15
 
16
16
  from lima2.client import progress_counter, utils
17
- from lima2.client.devencoded import dense_frame, sparse_frame, structured_array
18
- from lima2.client.pipeline import FrameSource, Pipeline
17
+ from lima2.client.devencoded import structured_array
18
+ from lima2.client.pipeline import FrameSource, FrameType, Pipeline
19
19
  from lima2.client.topology import TopologyKind
20
20
 
21
21
  # Create a logger
@@ -25,19 +25,21 @@ logger = logging.getLogger(__name__)
25
25
  class Xpcs(Pipeline):
26
26
  tango_class = "LimaProcessingXpcs"
27
27
 
28
- # Members of type "saving_params" in the corresponding lima2 "proc_params" struct.
29
- SAVING_PARAMS_NAMES = [
30
- "saving_dense",
31
- "saving_sparse",
32
- ]
33
-
34
28
  FRAME_SOURCES = {
35
29
  "input_frame": FrameSource(
36
- getter_name="getInputFrame", decoder=dense_frame.decode
30
+ getter_name="getInputFrame",
31
+ frame_type=FrameType.DENSE,
32
+ saving_channel=None,
33
+ ),
34
+ "frame": FrameSource(
35
+ getter_name="getFrame",
36
+ frame_type=FrameType.DENSE,
37
+ saving_channel="saving_dense",
37
38
  ),
38
- "frame": FrameSource(getter_name="getFrame", decoder=dense_frame.decode),
39
39
  "sparse_frame": FrameSource(
40
- getter_name="getSparseFrame", decoder=sparse_frame.decode
40
+ getter_name="getSparseFrame",
41
+ frame_type=FrameType.SPARSE,
42
+ saving_channel="saving_sparse",
41
43
  ),
42
44
  }
43
45
  """Available frame sources."""
@@ -115,18 +115,27 @@ class StateMachine(fsm.StateMachine):
115
115
  for device in [self.ctrl] + self.recvs:
116
116
  device.ping()
117
117
  states = [d.acq_state for d in [self.ctrl] + self.recvs]
118
- if not all(s == states[0] for s in states):
118
+ ctl_state = states[0]
119
+
120
+ ServerState = type(ctl_state)
121
+
122
+ if all(s in [ServerState.idle, ServerState.prepared] for s in states):
123
+ # Tolerate a mix of prepared and idle devices. In this case,
124
+ # the global state shall be IDLE (forcing user to prepare again).
125
+ self.state = State.IDLE
126
+ elif not all(s == ctl_state for s in states):
127
+ # Otherwise, if states are divergent, sync cannot be done
119
128
  self._state = State.UNKNOWN
120
129
  raise RuntimeError(f"Cannot sync: inconsistent server states ({states})")
121
- server_state = states[0]
122
- ServerState = type(server_state)
123
- map = {
124
- ServerState.idle: State.IDLE,
125
- ServerState.prepared: State.PREPARED,
126
- ServerState.running: State.RUNNING,
127
- ServerState.stopped: State.IDLE,
128
- }
129
- self.state = map[server_state]
130
+ else:
131
+ map = {
132
+ ServerState.idle: State.IDLE,
133
+ ServerState.prepared: State.PREPARED,
134
+ ServerState.running: State.RUNNING,
135
+ ServerState.stopped: State.IDLE,
136
+ }
137
+ self.state = map[ctl_state]
138
+
130
139
  return self.state
131
140
 
132
141
  @property
@@ -152,8 +152,9 @@ def distribute_acq(
152
152
  for i in range(num_receivers):
153
153
  # Assign unique filename rank per receiver
154
154
  processing_class = pipelines.get_class(proc_params["class_name"])
155
- for name in processing_class.SAVING_PARAMS_NAMES:
156
- proc[i][name]["filename_rank"] = i
155
+ for source in processing_class.FRAME_SOURCES.values():
156
+ if source.saving_channel is not None:
157
+ proc[i][source.saving_channel]["filename_rank"] = i
157
158
 
158
159
  # Idem for detector RAW saving params
159
160
  if "saving" in acq[i]:
@@ -19,7 +19,7 @@ import os
19
19
  import h5py
20
20
  import numpy as np
21
21
 
22
- from lima2.client.pipeline import Pipeline
22
+ from lima2.client.pipeline import FrameType, Pipeline
23
23
 
24
24
  logger = logging.getLogger(__name__)
25
25
 
@@ -42,42 +42,69 @@ def enable_generation(
42
42
  rr_offset (int): expected receiver of frame 0 in the round robin frame dispatching
43
43
  """
44
44
 
45
- if proc_params["class_name"] != "LimaProcessingLegacy":
45
+ if rr_offset >= len(pipeline._devs):
46
46
  raise ValueError(
47
- f"Unsupported master file generation for pipeline {proc_params['class_name']}"
47
+ f"Round robin offset cannot exceed the last receiver index ({len(pipeline._devs) - 1})."
48
48
  )
49
49
 
50
- base_path = proc_params["saving"]["base_path"]
51
- filename_prefix = proc_params["saving"]["filename_prefix"]
52
- file_exists_policy = proc_params["saving"]["file_exists_policy"]
50
+ generate_params: list[dict] = []
51
+ """Params passed to generate_master_file for each enabled saving channel"""
53
52
 
54
- master_file_path = f"{base_path}/{filename_prefix}_master.h5"
55
- if os.path.exists(master_file_path) and file_exists_policy != "overwrite":
56
- raise ValueError(
57
- f"Master file exists at {master_file_path} but {file_exists_policy=}. "
58
- "Aborting master file generation."
59
- )
53
+ for name, source in pipeline.FRAME_SOURCES.items():
54
+ # Skip non-dense frame sources
55
+ if source.frame_type != FrameType.DENSE:
56
+ continue
57
+ # Skip non-persistent sources
58
+ if source.saving_channel is None:
59
+ continue
60
+ # Skip source if channel is disabled
61
+ if not proc_params[source.saving_channel]["enabled"]:
62
+ continue
60
63
 
61
- if rr_offset >= len(pipeline._devs):
62
- raise ValueError(
63
- f"Round robin offset cannot exceed the last receiver index ({len(pipeline._devs) - 1})."
64
- )
64
+ base_path = proc_params[source.saving_channel]["base_path"]
65
+ filename_prefix = proc_params[source.saving_channel]["filename_prefix"]
66
+ file_exists_policy = proc_params[source.saving_channel]["file_exists_policy"]
65
67
 
66
- logger.info(f"Master file will be generated at {master_file_path}...")
68
+ master_file_path = f"{base_path}/{filename_prefix}_master.h5"
69
+ if os.path.exists(master_file_path) and file_exists_policy != "overwrite":
70
+ raise ValueError(
71
+ f"Master file (channel={source.saving_channel}) exists at {master_file_path} "
72
+ f"but {file_exists_policy=}. Cannot enable master file generation."
73
+ )
67
74
 
68
- frame_info = pipeline.channels["frame"]
75
+ logger.info(
76
+ f"Master file (channel={source.saving_channel}) will be generated "
77
+ f"at {master_file_path}..."
78
+ )
69
79
 
70
- def on_finished() -> None:
71
- generate_master_file(
72
- num_frames=num_frames,
73
- frame_shape=frame_info["shape"],
74
- pixel_dtype=frame_info["dtype"],
75
- num_receivers=len(pipeline._devs),
76
- saving_params=proc_params["saving"],
77
- rr_offset=rr_offset,
80
+ frame_info = pipeline.channels[name]
81
+
82
+ # Calling register_on_finished here with a lambda doesn't work as expected
83
+ # because some parameters will be evaluated at the time of calling, and their value
84
+ # will have changed in subsequent loop iterations.
85
+ # One solution is to store the parameters in a dict and call register_on_finished
86
+ # outside of the loop.
87
+ generate_params.append(
88
+ dict(
89
+ num_frames=num_frames,
90
+ frame_shape=frame_info["shape"],
91
+ pixel_dtype=frame_info["dtype"],
92
+ num_receivers=len(pipeline._devs),
93
+ saving_params=proc_params[source.saving_channel],
94
+ rr_offset=rr_offset,
95
+ )
78
96
  )
79
97
 
80
- pipeline.register_on_finished(on_finished=on_finished)
98
+ if len(generate_params) == 0:
99
+ logger.warning(
100
+ "Master file generation not enabled: no dense frames saving channel enabled."
101
+ )
102
+ else:
103
+ pipeline.register_on_finished(
104
+ on_finished=lambda: [
105
+ generate_master_file(**params) for params in generate_params
106
+ ]
107
+ )
81
108
 
82
109
 
83
110
  def generate_master_file(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: lima2.client
3
- Version: 2.1.0
3
+ Version: 2.1.1
4
4
  Summary: Python client library for Lima2 detector
5
5
  Home-page: https://gitlab.esrf.fr/limagroup/lima2-client
6
6
  Author: BCU (ESRF)
@@ -15,8 +15,8 @@ import numpy as np
15
15
  import pytest
16
16
  import tango
17
17
 
18
- from lima2.client import progress_counter
19
- from lima2.client.pipeline import FrameSource, Pipeline
18
+ from lima2.client import pipeline, progress_counter
19
+ from lima2.client.pipeline import FrameSource, FrameType, Pipeline
20
20
  from lima2.client.pipelines.smx import Smx
21
21
  from lima2.client.pipelines.xpcs import Xpcs
22
22
  from lima2.client.topology import TopologyKind
@@ -90,6 +90,20 @@ def test_constructor_uneven(processing_devices):
90
90
  )
91
91
 
92
92
 
93
+ def test_bad_subclass():
94
+ with pytest.raises(ValueError):
95
+
96
+ class NoFrameSourcePipeline(Pipeline):
97
+ tango_class = "badcafe"
98
+ # Missing FRAME_SOURCES
99
+
100
+ with pytest.raises(ValueError):
101
+
102
+ class NoTangoClassPipeline(Pipeline):
103
+ FRAME_SOURCES = {}
104
+ # Missing tango_class
105
+
106
+
93
107
  def test_progress_counters(processing_devices):
94
108
  """Read progress_counters."""
95
109
  processing_devices[0].progress_counters = json.dumps(
@@ -387,7 +401,7 @@ def test_pop_roi_profiles(processing_devices):
387
401
  assert proc.byproduct_cache["roi_profiles"].size == 0 # No cache
388
402
 
389
403
 
390
- def test_get_frame(processing_devices):
404
+ def test_get_frame(processing_devices, monkeypatch):
391
405
  proc = Pipeline(
392
406
  uuid=uuid1(),
393
407
  proc_devs=processing_devices,
@@ -403,10 +417,26 @@ def test_get_frame(processing_devices):
403
417
  proc.FRAME_SOURCES = {
404
418
  "cafedeca": FrameSource(
405
419
  getter_name="getFrame",
406
- decoder=lambda raw: MockFrame(idx=int(raw[0]), data=raw[1:]),
407
- )
420
+ frame_type=FrameType.DENSE,
421
+ saving_channel=None,
422
+ ),
423
+ "sparse": FrameSource(
424
+ getter_name="getSparseFrame",
425
+ frame_type=FrameType.SPARSE,
426
+ saving_channel=None,
427
+ ),
408
428
  }
409
429
 
430
+ def mock_decode(data: bytes):
431
+ return MockFrame(data[0], data[1:])
432
+
433
+ # Modify the decoder_by_type map to plug our mock decode function
434
+ # No decoder for SPARSE frame so we can test the case where the decoder is not implemented
435
+ monkeypatch.setattr(pipeline, "decoder_by_type", {FrameType.DENSE: mock_decode})
436
+
437
+ with pytest.raises(NotImplementedError):
438
+ proc.get_frame(0, source="sparse")
439
+
410
440
  rcv_0_frames = {
411
441
  0: b"\x00milk",
412
442
  -1: b"\x02milk",
File without changes
File without changes
File without changes