QuLab 2.11.8__py3-none-any.whl → 2.11.10__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.
qulab/monitor/dataset.py CHANGED
@@ -1,77 +1,184 @@
1
+ """
2
+ QuLab Monitor Dataset Module
3
+
4
+ This module provides data management functionality for the QuLab monitor application.
5
+ It handles storage and retrieval of time-series data with support for both point-by-point
6
+ and trace-based data collection.
7
+
8
+ The Dataset class maintains a rolling buffer of data frames, where each frame can
9
+ contain multiple named columns of numerical data.
10
+ """
11
+
1
12
  from collections import defaultdict, deque
13
+ from typing import Union, Sequence
2
14
 
3
- from .config import Nroll
15
+ from .config import ROLL_BUFFER_SIZE
4
16
 
5
17
  PAUSE_TIME = 1
6
18
 
7
- Number = int | float | complex
19
+ Number = Union[int, float, complex]
20
+ DataPoint = Union[Number, Sequence[Number]]
21
+ DataFrame = Union[Sequence[Number], Sequence[Sequence[Number]]]
8
22
 
9
23
 
10
24
  def remove_duplicates(input_list: list[str]) -> list[str]:
11
25
  """
12
- Remove duplicates from a list of strings, keeping the order of the elements.
26
+ Remove duplicates from a list of strings while preserving order.
27
+
28
+ Args:
29
+ input_list: List of strings that may contain duplicates
30
+
31
+ Returns:
32
+ List of unique strings in their original order
13
33
  """
14
34
  return list(dict.fromkeys(input_list))
15
35
 
16
36
 
17
- class Dataset():
37
+ class Dataset:
38
+ """
39
+ A data management class for storing and retrieving time-series data.
18
40
 
19
- def __init__(self):
20
- self.column_names = []
21
- self.box = deque(maxlen=Nroll)
22
- self.dirty = True
41
+ This class maintains a rolling buffer of data frames, where each frame
42
+ contains multiple columns of numerical data. It supports both point-by-point
43
+ data collection and trace-based data collection.
23
44
 
24
- def clear(self):
25
- self.box.clear()
45
+ Attributes:
46
+ column_names: List of column names for the dataset
47
+ box: Rolling buffer containing data frames
48
+ dirty: Flag indicating if data has been modified since last read
49
+ """
26
50
 
27
- def clear_history(self):
28
- o = self.box.popleft()
51
+ def __init__(self):
52
+ """Initialize an empty dataset with a rolling buffer."""
53
+ self.column_names: list[str] = []
54
+ self.box: deque[dict] = deque(maxlen=ROLL_BUFFER_SIZE)
55
+ self.dirty: bool = True
56
+
57
+ def clear(self) -> None:
58
+ """Clear all data from the dataset."""
29
59
  self.box.clear()
30
- self.box.appendleft(o)
31
60
 
32
- def set_column_names(self, column_names: list[str]):
61
+ def clear_history(self) -> None:
62
+ """
63
+ Clear historical data while preserving the most recent frame.
64
+
65
+ This is useful for maintaining the current state while discarding
66
+ historical data that is no longer needed.
67
+ """
68
+ if self.box:
69
+ current_frame = self.box.popleft()
70
+ self.box.clear()
71
+ self.box.appendleft(current_frame)
72
+
73
+ def set_column_names(self, column_names: list[str]) -> None:
74
+ """
75
+ Set or update the column names for the dataset.
76
+
77
+ If the new column names differ from the current ones, all existing
78
+ data will be cleared to maintain consistency.
79
+
80
+ Args:
81
+ column_names: List of unique column names
82
+ """
33
83
  column_names = remove_duplicates(column_names)
34
84
  if column_names != self.column_names:
35
85
  self.clear()
36
86
  self.column_names = column_names
37
87
 
38
- def get_data(self, step: int, xname: str,
39
- yname: str) -> tuple[list[Number], list[Number]]:
88
+ def get_data(self, step: int, x_name: str,
89
+ y_name: str) -> tuple[list[Number], list[Number]]:
90
+ """
91
+ Retrieve X-Y data pairs from a specific step in the dataset.
92
+
93
+ Args:
94
+ step: Index of the data frame to retrieve
95
+ x_name: Name of the column to use for X values
96
+ y_name: Name of the column to use for Y values
97
+
98
+ Returns:
99
+ Tuple containing lists of X and Y values. Returns empty lists if
100
+ the requested step is not available.
101
+ """
40
102
  try:
41
- b = self.box[step]
103
+ frame = self.box[step]
104
+ return frame[x_name], frame[y_name]
42
105
  except IndexError:
43
106
  return [], []
44
- return b[xname], b[yname]
45
107
 
46
- def append(self, dataframe: list[Number] | list[list[Number]]):
108
+ def append(self, dataframe: DataFrame) -> None:
109
+ """
110
+ Append new data to the dataset.
111
+
112
+ This method automatically determines whether the input is point data
113
+ or trace data based on its structure.
114
+
115
+ Args:
116
+ dataframe: Either a list of values for point data or a list of
117
+ lists for trace data. The length must match the number
118
+ of columns.
119
+
120
+ Raises:
121
+ AssertionError: If the input data length doesn't match the number
122
+ of columns.
123
+ TypeError: If the input data format is invalid.
124
+ """
47
125
  if not dataframe:
48
126
  return
49
127
  try:
50
- iter(dataframe[0]) # test if dataframe is a list of list
128
+ # Test if dataframe is a list of lists (trace data)
129
+ iter(dataframe[0])
51
130
  self._append_traces(dataframe)
52
131
  except TypeError:
53
132
  self._append_points(dataframe)
54
133
 
55
- def roll(self):
134
+ def roll(self) -> None:
135
+ """
136
+ Add a new empty frame to the dataset.
137
+
138
+ This creates a new defaultdict that will automatically create empty
139
+ lists for any new column names accessed.
140
+ """
56
141
  self.box.appendleft(defaultdict(list))
57
142
 
58
- def _append_points(self, points: list[Number]):
143
+ def _append_points(self, points: Sequence[Number]) -> None:
144
+ """
145
+ Append point data to the current frame.
146
+
147
+ Args:
148
+ points: List of values, one for each column
149
+
150
+ Raises:
151
+ AssertionError: If the number of points doesn't match the number
152
+ of columns
153
+ """
59
154
  self.dirty = True
60
- assert (len(points) == len(
61
- self.column_names)), (f"-PointDataBox\n"
62
- f"-ap\n"
63
- f"-Length Must be same\n"
64
- f"the column_names : {self.column_names}\n"
65
- f"given data : {points}")
66
- for name, p in zip(self.column_names, points):
67
- self.box[0][name].append(p)
68
-
69
- def _append_traces(self, traces: list[list[Number]]):
155
+ if len(points) != len(self.column_names):
156
+ raise AssertionError(
157
+ "Point data length mismatch\n"
158
+ f"Expected {len(self.column_names)} values for columns: {self.column_names}\n"
159
+ f"Received {len(points)} values: {points}"
160
+ )
161
+
162
+ for name, value in zip(self.column_names, points):
163
+ self.box[0][name].append(value)
164
+
165
+ def _append_traces(self, traces: Sequence[Sequence[Number]]) -> None:
166
+ """
167
+ Append trace data as a new frame.
168
+
169
+ Args:
170
+ traces: List of data sequences, one for each column
171
+
172
+ Raises:
173
+ AssertionError: If the number of traces doesn't match the number
174
+ of columns
175
+ """
70
176
  self.dirty = True
71
- assert (len(traces) == len(
72
- self.column_names)), (f"-TraceDataBox\n"
73
- f"-at\n"
74
- f"-Length Must be same\n"
75
- f"the column_names : {self.column_names}\n"
76
- f"given data : {traces}")
177
+ if len(traces) != len(self.column_names):
178
+ raise AssertionError(
179
+ "Trace data length mismatch\n"
180
+ f"Expected {len(self.column_names)} sequences for columns: {self.column_names}\n"
181
+ f"Received {len(traces)} sequences"
182
+ )
183
+
77
184
  self.box.appendleft(dict(zip(self.column_names, traces)))
@@ -1,54 +1,127 @@
1
+ """
2
+ QuLab Monitor Event Queue Module
3
+
4
+ This module implements an event handling system for the QuLab monitor application.
5
+ It processes commands and data received through a multiprocessing queue to update
6
+ the monitor's display and datasets.
7
+
8
+ The event queue handles various types of commands:
9
+ - PN/TN: Set column names for point/trace data
10
+ - PD/TD: Append point/trace data
11
+ - ROLL: Create new data frame
12
+ - PXY/TXY: Update plot configurations
13
+ """
14
+
1
15
  import warnings
2
16
  from multiprocessing import Queue
17
+ from typing import Any, Literal, Tuple, Union
3
18
 
4
19
  from .dataset import Dataset
5
20
  from .toolbar import ToolBar
6
21
 
22
+ # Type definitions
23
+ CommandType = Literal["PN", "TN", "PD", "TD", "ROLL", "PXY", "TXY"]
24
+ DataType = Union[list[str], list[float], str, None]
25
+ EventType = Tuple[CommandType, DataType]
26
+
27
+
28
+ class EventQueue:
29
+ """
30
+ Event handler for processing monitor commands and data.
7
31
 
8
- class EventQueue():
32
+ This class manages the communication between the data collection process
33
+ and the monitor display. It receives commands and data through a queue
34
+ and updates the appropriate components of the monitor.
35
+
36
+ Attributes:
37
+ toolbar: The monitor's toolbar for UI controls
38
+ queue: Multiprocessing queue for receiving commands and data
39
+ point_dataset: Dataset for point-by-point data collection
40
+ trace_dataset: Dataset for trace-based data collection
41
+ """
9
42
 
10
43
  def __init__(self, queue: Queue, toolbar: ToolBar, point_dataset: Dataset,
11
44
  trace_dataset: Dataset):
45
+ """
46
+ Initialize the event queue.
47
+
48
+ Args:
49
+ queue: Multiprocessing queue for receiving commands
50
+ toolbar: Monitor's toolbar instance
51
+ point_dataset: Dataset for point data
52
+ trace_dataset: Dataset for trace data
53
+ """
12
54
  self.toolbar = toolbar
13
55
  self.queue = queue
14
56
  self.point_dataset = point_dataset
15
57
  self.trace_dataset = trace_dataset
16
58
 
17
- def flush(self):
18
- while (not self.queue.empty()):
19
- words = self.queue.get()
59
+ def flush(self) -> None:
60
+ """
61
+ Process all pending events in the queue.
62
+
63
+ This method retrieves and processes all available events from the queue.
64
+ Each event should be a tuple containing a command string and associated data.
65
+ Invalid events are logged as warnings and skipped.
66
+ """
67
+ while not self.queue.empty():
20
68
  try:
21
- assert (isinstance(
22
- words, tuple)), "QueueHandler - fifo Content must be tuple"
23
- assert (
24
- len(words) == 2
25
- ), "QueueHandler -the tuple must be like (\"command_type\" , \"DATA\")"
26
- cmd, data = words
27
- assert (isinstance(
28
- cmd, str)), "QueueHandler - the command should be a string"
29
- except AssertionError as e:
30
- warnings.warn(e.args[0])
69
+ event = self.queue.get()
70
+ if not isinstance(event, tuple):
71
+ raise ValueError("Queue event must be a tuple")
72
+ if len(event) != 2:
73
+ raise ValueError("Queue event must contain exactly two elements (command, data)")
74
+
75
+ command, data = event
76
+ if not isinstance(command, str):
77
+ raise ValueError("Command must be a string")
78
+
79
+ self.handle(command, data)
80
+
81
+ except (ValueError, AssertionError) as error:
82
+ warnings.warn(f"Invalid event format: {error}")
31
83
  continue
32
84
 
33
- self.handle(cmd, data)
85
+ def handle(self, command: str, data: Any) -> None:
86
+ """
87
+ Process a single event based on its command type.
88
+
89
+ Args:
90
+ command: String identifying the type of event
91
+ data: Data associated with the event
34
92
 
35
- def handle(self, cmd, data):
36
- match cmd:
37
- case "PN":
93
+ The following commands are supported:
94
+ PN: Set point data column names
95
+ TN: Set trace data column names
96
+ PD: Append point data
97
+ TD: Append trace data
98
+ ROLL: Create new data frame
99
+ PXY: Update point plot configuration
100
+ TXY: Update trace plot configuration
101
+ """
102
+ match command:
103
+ case "PN": # Set point data column names
38
104
  self.point_dataset.set_column_names(data)
39
105
  self.toolbar.refresh_comb()
40
- case "TN":
106
+
107
+ case "TN": # Set trace data column names
41
108
  self.trace_dataset.set_column_names(data)
42
109
  self.toolbar.refresh_comb()
43
- case "PD":
110
+
111
+ case "PD": # Append point data
44
112
  self.point_dataset.append(data)
45
- case "TD":
113
+
114
+ case "TD": # Append trace data
46
115
  self.trace_dataset.append(data)
47
- case "ROLL":
116
+
117
+ case "ROLL": # Create new data frame
48
118
  self.point_dataset.roll()
49
- case "PXY":
119
+
120
+ case "PXY": # Update point plot configuration
50
121
  self.toolbar.set_point_text(data)
51
- case "TXY":
122
+
123
+ case "TXY": # Update trace plot configuration
52
124
  self.toolbar.set_trace_text(data)
125
+
53
126
  case _:
54
- warnings.warn(f"QueueHandler - unknown command : {cmd}")
127
+ warnings.warn(f"Unknown command: {command}")