sigvue 2026.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.
- sigvue-2026.1/PKG-INFO +440 -0
- sigvue-2026.1/README.md +426 -0
- sigvue-2026.1/pyproject.toml +29 -0
- sigvue-2026.1/setup.cfg +4 -0
- sigvue-2026.1/src/sigvue/__init__.py +3 -0
- sigvue-2026.1/src/sigvue/_packaging/__init__.py +1 -0
- sigvue-2026.1/src/sigvue/_packaging/build.py +22 -0
- sigvue-2026.1/src/sigvue/_packaging/sigvue.spec +71 -0
- sigvue-2026.1/src/sigvue/catalog/__init__.py +9 -0
- sigvue-2026.1/src/sigvue/catalog/browser.py +71 -0
- sigvue-2026.1/src/sigvue/core/__init__.py +23 -0
- sigvue-2026.1/src/sigvue/core/capabilities.py +186 -0
- sigvue-2026.1/src/sigvue/core/contracts.py +18 -0
- sigvue-2026.1/src/sigvue/core/errors.py +14 -0
- sigvue-2026.1/src/sigvue/core/layout.py +61 -0
- sigvue-2026.1/src/sigvue/core/models.py +62 -0
- sigvue-2026.1/src/sigvue/core/page.py +140 -0
- sigvue-2026.1/src/sigvue/core/plugin.py +1098 -0
- sigvue-2026.1/src/sigvue/layout/__init__.py +28 -0
- sigvue-2026.1/src/sigvue/layout/components.py +43 -0
- sigvue-2026.1/src/sigvue/plugin.py +45 -0
- sigvue-2026.1/src/sigvue/profile.py +138 -0
- sigvue-2026.1/src/sigvue/py.typed +1 -0
- sigvue-2026.1/src/sigvue/refresh/__init__.py +3 -0
- sigvue-2026.1/src/sigvue/refresh/manager.py +63 -0
- sigvue-2026.1/src/sigvue/registry/__init__.py +10 -0
- sigvue-2026.1/src/sigvue/registry/discovery.py +50 -0
- sigvue-2026.1/src/sigvue/registry/registry.py +23 -0
- sigvue-2026.1/src/sigvue/rendering/__init__.py +4 -0
- sigvue-2026.1/src/sigvue/rendering/dispatch.py +44 -0
- sigvue-2026.1/src/sigvue/rendering/matplotlib_renderer.py +17 -0
- sigvue-2026.1/src/sigvue/web/__init__.py +3 -0
- sigvue-2026.1/src/sigvue/web/application.py +941 -0
- sigvue-2026.1/src/sigvue/web/routes.py +13 -0
- sigvue-2026.1/src/sigvue/web/sessions.py +11 -0
- sigvue-2026.1/src/sigvue.egg-info/PKG-INFO +440 -0
- sigvue-2026.1/src/sigvue.egg-info/SOURCES.txt +51 -0
- sigvue-2026.1/src/sigvue.egg-info/dependency_links.txt +1 -0
- sigvue-2026.1/src/sigvue.egg-info/entry_points.txt +3 -0
- sigvue-2026.1/src/sigvue.egg-info/requires.txt +9 -0
- sigvue-2026.1/src/sigvue.egg-info/top_level.txt +1 -0
- sigvue-2026.1/tests/test_capabilities.py +32 -0
- sigvue-2026.1/tests/test_catalog.py +35 -0
- sigvue-2026.1/tests/test_discovery.py +26 -0
- sigvue-2026.1/tests/test_layout.py +28 -0
- sigvue-2026.1/tests/test_models.py +17 -0
- sigvue-2026.1/tests/test_packaging.py +15 -0
- sigvue-2026.1/tests/test_plugin.py +520 -0
- sigvue-2026.1/tests/test_profile.py +134 -0
- sigvue-2026.1/tests/test_refresh.py +26 -0
- sigvue-2026.1/tests/test_registry.py +27 -0
- sigvue-2026.1/tests/test_rendering.py +29 -0
- sigvue-2026.1/tests/test_web_app.py +449 -0
sigvue-2026.1/PKG-INFO
ADDED
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sigvue
|
|
3
|
+
Version: 2026.1
|
|
4
|
+
Summary: Extensible signal viewer
|
|
5
|
+
Author: Sigvue
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: matplotlib>=3.7
|
|
9
|
+
Requires-Dist: plotly>=6.1.1
|
|
10
|
+
Requires-Dist: tomli>=2; python_version < "3.11"
|
|
11
|
+
Provides-Extra: build
|
|
12
|
+
Requires-Dist: certifi>=2024.2.2; extra == "build"
|
|
13
|
+
Requires-Dist: pyinstaller>=6.0; extra == "build"
|
|
14
|
+
|
|
15
|
+
# Sigvue
|
|
16
|
+
|
|
17
|
+
Sigvue turns file-backed analysis scripts into a local browser application. A workspace package decides:
|
|
18
|
+
|
|
19
|
+
1. Which items are available.
|
|
20
|
+
2. How an item is opened.
|
|
21
|
+
3. What data is delivered for one analysis run.
|
|
22
|
+
4. How that data is processed and displayed.
|
|
23
|
+
|
|
24
|
+
The framework supplies the catalog, page layout, parameters, themes, refresh and playback controls, plot updates, background capability execution, and HTTP service.
|
|
25
|
+
|
|
26
|
+
## Install and run
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
python -m pip install sigvue
|
|
30
|
+
sigvue --config browser.toml
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Open `http://127.0.0.1:8000`. The package contains no built-in workspaces; `browser.toml` chooses which independently installed or local workspace packages to load.
|
|
34
|
+
|
|
35
|
+
## The workspace-author contract
|
|
36
|
+
|
|
37
|
+
Most workspace packages implement one factory and two ordinary functions:
|
|
38
|
+
|
|
39
|
+
| Hook | Required | Responsibility |
|
|
40
|
+
| --- | --- | --- |
|
|
41
|
+
| `create_workspace(config)` | Yes | Construct and return an `AnalysisWorkspace`. |
|
|
42
|
+
| Source `discover()` / `open()` | Yes | List items and open the selected item. `DirectorySource` implements this for files. |
|
|
43
|
+
| `analyze(data, ui)` | Yes | Process the delivered data and register plots, tables, text, parameters, and layout. |
|
|
44
|
+
| Delivery `prepare(source_data, ui)` | No | Select or transform data before `analyze`, including buffering, seeking, live reads, or window selection. |
|
|
45
|
+
| `DataAnnotator` | No | Discover and persist domain-native annotations. No contract means no Annotate UI. |
|
|
46
|
+
| `DataExporter` | No | Advertise scope/format choices and serialize domain data. No contract means no Download UI. |
|
|
47
|
+
| Package entry point | Yes for profile loading | Give the factory a stable name for `browser.toml`. |
|
|
48
|
+
|
|
49
|
+
The data passed between these hooks is owned by the workspace package. The framework does not require a particular file format, array shape, reader, or analysis library.
|
|
50
|
+
|
|
51
|
+
### Typed lifecycle contracts
|
|
52
|
+
|
|
53
|
+
`DataSource` and `DataDelivery` are public, generic, runtime-checkable
|
|
54
|
+
interfaces. Their type parameters describe the complete data path:
|
|
55
|
+
|
|
56
|
+
```text
|
|
57
|
+
DataSource[SourceData]
|
|
58
|
+
discover() -> Iterable[DataResource]
|
|
59
|
+
open(resource) -> SourceData
|
|
60
|
+
│
|
|
61
|
+
▼
|
|
62
|
+
DataDelivery[SourceData, DeliveredData] optional
|
|
63
|
+
prepare(source_data, ui) -> DeliveredData
|
|
64
|
+
│
|
|
65
|
+
▼
|
|
66
|
+
analyze(delivered_data: DeliveredData, ui: AnalysisContext) -> None
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`AnalysisWorkspace` has typed constructor overloads connecting these stages. A
|
|
70
|
+
type checker therefore catches a delivery that expects the wrong reader type or
|
|
71
|
+
an analysis function that expects something other than the delivery output.
|
|
72
|
+
The installed package includes a `py.typed` marker, so these checks also work
|
|
73
|
+
when `sigvue` is installed from a wheel.
|
|
74
|
+
|
|
75
|
+
Implementations may explicitly inherit the interfaces, which is recommended
|
|
76
|
+
for readability:
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
from collections.abc import Iterable
|
|
80
|
+
|
|
81
|
+
from sigvue.plugin import AnalysisContext, DataDelivery, DataResource, DataSource
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class MySource(DataSource[Recording]):
|
|
85
|
+
def discover(self) -> Iterable[DataResource]:
|
|
86
|
+
...
|
|
87
|
+
|
|
88
|
+
def open(self, resource: DataResource) -> Recording:
|
|
89
|
+
...
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class WindowDelivery(DataDelivery[Recording, SampleWindow]):
|
|
93
|
+
def prepare(
|
|
94
|
+
self,
|
|
95
|
+
recording: Recording,
|
|
96
|
+
ui: AnalysisContext,
|
|
97
|
+
) -> SampleWindow:
|
|
98
|
+
...
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Explicitly inherited methods are abstract, so an incomplete subclass cannot be
|
|
102
|
+
instantiated. Inheritance is not required: structurally compatible objects also
|
|
103
|
+
satisfy the interfaces. At runtime, `AnalysisWorkspace` validates that sources provide
|
|
104
|
+
`discover()` and `open()`, deliveries provide `prepare()`, analysis is callable,
|
|
105
|
+
discovery returns `DataResource` objects, and resource identifiers are unique.
|
|
106
|
+
Failures identify the missing method or invalid discovery value directly.
|
|
107
|
+
|
|
108
|
+
### Minimal file-backed workspace
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
# src/my_workspace/workspace.py
|
|
112
|
+
import json
|
|
113
|
+
from pathlib import Path
|
|
114
|
+
from typing import TypedDict
|
|
115
|
+
|
|
116
|
+
import plotly.graph_objects as go
|
|
117
|
+
|
|
118
|
+
from sigvue.plugin import AnalysisContext, AnalysisWorkspace, DirectorySource
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class ResultFile(TypedDict):
|
|
122
|
+
values: list[float]
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def load_result(path: Path) -> ResultFile:
|
|
126
|
+
return json.loads(path.read_text())
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def analyze(result: ResultFile, ui: AnalysisContext) -> None:
|
|
130
|
+
scale = ui.number("scale", label="Scale", default=1.0, step=0.1)
|
|
131
|
+
values = [scale * value for value in result["values"]]
|
|
132
|
+
|
|
133
|
+
figure = go.Figure(go.Scatter(y=values, name="Value"))
|
|
134
|
+
with ui.tab("Values"):
|
|
135
|
+
ui.plot(figure, key="values")
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def create_workspace(config):
|
|
139
|
+
return AnalysisWorkspace(
|
|
140
|
+
identifier=config["id"],
|
|
141
|
+
name=config["name"],
|
|
142
|
+
description="Inspect result files.",
|
|
143
|
+
source=DirectorySource(
|
|
144
|
+
config["data_root"],
|
|
145
|
+
pattern="*.result.json",
|
|
146
|
+
loader=load_result,
|
|
147
|
+
),
|
|
148
|
+
analyze=analyze,
|
|
149
|
+
)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Set `recursive=True` on `DirectorySource` to preserve nested directories in the
|
|
153
|
+
browser. The framework derives folder breadcrumbs from each file's path relative
|
|
154
|
+
to the source root; files are not flattened and directories are not presented as
|
|
155
|
+
fake analysis items. A custom source can provide the same behavior by setting
|
|
156
|
+
`DataResource(navigation_path=("campaign", "day-2"), ...)`.
|
|
157
|
+
|
|
158
|
+
### Discovery columns
|
|
159
|
+
|
|
160
|
+
Each workspace can declare the metadata columns shown beside discovered files.
|
|
161
|
+
The workspace supplies raw values in `DataResource.summary`; Sigvue owns table
|
|
162
|
+
rendering, null display, search, and sorting:
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
from sigvue.plugin import AnalysisWorkspace, DataResource, DiscoveryColumn
|
|
166
|
+
|
|
167
|
+
columns = (
|
|
168
|
+
DiscoveryColumn("date", "Date", kind="datetime"),
|
|
169
|
+
DiscoveryColumn("sample_rate", "Sampling rate", kind="si", unit="sample/s"),
|
|
170
|
+
DiscoveryColumn("rf_frequency", "RF frequency", kind="si", unit="Hz"),
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
resource = DataResource(
|
|
174
|
+
identifier="recording-1",
|
|
175
|
+
title="Recording 1",
|
|
176
|
+
location="recording-1.sigmf-meta",
|
|
177
|
+
summary={
|
|
178
|
+
"date": "2026-07-19T12:00:00Z",
|
|
179
|
+
"sample_rate": 10_000_000,
|
|
180
|
+
"rf_frequency": None,
|
|
181
|
+
},
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
workspace = AnalysisWorkspace(
|
|
185
|
+
# ...normal workspace arguments...
|
|
186
|
+
discovery_columns=columns,
|
|
187
|
+
)
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Column kinds are `text`, `number`, `datetime`, and `si`. Missing values remain
|
|
191
|
+
visible as unavailable values and sort after populated values in either sort
|
|
192
|
+
direction. Browser search includes titles, paths, tags, and every declared
|
|
193
|
+
summary value.
|
|
194
|
+
|
|
195
|
+
Advertise the factory in the workspace package:
|
|
196
|
+
|
|
197
|
+
```toml
|
|
198
|
+
# pyproject.toml in the workspace package
|
|
199
|
+
[project.entry-points."sigvue.workspaces"]
|
|
200
|
+
my-analysis = "my_workspace.workspace:create_workspace"
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Select and configure it:
|
|
204
|
+
|
|
205
|
+
```toml
|
|
206
|
+
# browser.toml
|
|
207
|
+
[browser]
|
|
208
|
+
title = "My Analysis Browser"
|
|
209
|
+
subtitle = "Explore scientific and analytical results"
|
|
210
|
+
|
|
211
|
+
[[workspaces]]
|
|
212
|
+
use = "my-analysis"
|
|
213
|
+
id = "results"
|
|
214
|
+
name = "Results"
|
|
215
|
+
|
|
216
|
+
[workspaces.config]
|
|
217
|
+
data_root = "./data"
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
`config` contains the `[workspaces.config]` values plus `id`, `name`, and `profile_dir`. Relative paths resolve from the directory containing `browser.toml`.
|
|
221
|
+
|
|
222
|
+
For an uninstalled workspace under development, add its repository path:
|
|
223
|
+
|
|
224
|
+
```toml
|
|
225
|
+
[[workspaces]]
|
|
226
|
+
use = "my-analysis"
|
|
227
|
+
path = "../my-workspace"
|
|
228
|
+
id = "results"
|
|
229
|
+
name = "Results"
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
The browser adds its `src` directory. Reloading the browser page reparses
|
|
233
|
+
`browser.toml` and applies added, removed, or reconfigured workspace entries
|
|
234
|
+
without restarting the server. Changed workspace modules are reloaded as part
|
|
235
|
+
of the same request; use `--no-reload` to disable subsequent automatic module
|
|
236
|
+
watching. A direct `module:factory` string is also accepted in `use`.
|
|
237
|
+
|
|
238
|
+
## Data delivery
|
|
239
|
+
|
|
240
|
+
Without a delivery object, `analyze` receives exactly what the source opened. A delivery object can prepare a different value while leaving analysis unchanged:
|
|
241
|
+
|
|
242
|
+
```python
|
|
243
|
+
from dataclasses import dataclass
|
|
244
|
+
|
|
245
|
+
from sigvue.plugin import AnalysisContext, DataDelivery
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
@dataclass(frozen=True)
|
|
249
|
+
class SampleWindow:
|
|
250
|
+
start_seconds: float
|
|
251
|
+
samples: list[complex]
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
class FrameDelivery(DataDelivery[Recording, SampleWindow]):
|
|
255
|
+
def prepare(
|
|
256
|
+
self,
|
|
257
|
+
recording: Recording,
|
|
258
|
+
ui: AnalysisContext,
|
|
259
|
+
) -> SampleWindow:
|
|
260
|
+
frame_seconds = ui.number("frame_seconds", default=0.1, minimum=0.001)
|
|
261
|
+
position = ui.playback(
|
|
262
|
+
mode="seek",
|
|
263
|
+
duration=max(0.0, recording.duration - frame_seconds),
|
|
264
|
+
step=0.01,
|
|
265
|
+
)
|
|
266
|
+
return SampleWindow(position, recording.read(position, frame_seconds))
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def analyze(window: SampleWindow, ui: AnalysisContext) -> None:
|
|
270
|
+
...
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Pass it to `AnalysisWorkspace(delivery=FrameDelivery(), ...)`. The framework calls `source.open`, then `delivery.prepare`, then `analyze` for every requested state.
|
|
274
|
+
|
|
275
|
+
Available lifecycle modes are:
|
|
276
|
+
|
|
277
|
+
| Mode | Framework UI | Delivery behavior |
|
|
278
|
+
| --- | --- | --- |
|
|
279
|
+
| `static` | No timeline | Return the complete or fixed input. |
|
|
280
|
+
| `seek` | Play/pause, slider, editable time | Return the buffer at the requested time. |
|
|
281
|
+
| `live` | Seek controls plus **Live** | Return historical buffers or follow a growing source. |
|
|
282
|
+
| `windowed` | Movable and resizable interval, optionally over a full-record overview | Return only the selected interval. |
|
|
283
|
+
| `segmented` | Discrete markers with previous/next navigation | Return the selected regular or irregular segment. |
|
|
284
|
+
|
|
285
|
+
Use `ui.playback(...)` for static, seek, and live policies. In live mode, the delivery should check the currently available duration on each request.
|
|
286
|
+
|
|
287
|
+
Timeline values remain canonical seconds between the browser, delivery, annotations,
|
|
288
|
+
and exports, but a pipeline can choose the unit used by every framework-owned display:
|
|
289
|
+
|
|
290
|
+
```python
|
|
291
|
+
position = ui.playback(
|
|
292
|
+
mode="seek",
|
|
293
|
+
duration=3 * 86_400,
|
|
294
|
+
step=60,
|
|
295
|
+
time_unit="h",
|
|
296
|
+
)
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Pass `time_unit=` to `ui.playback`, `ui.windowed`, or `ui.segmented`. Supported
|
|
300
|
+
values are `"ns"`, `"us"`, `"ms"`, `"s"`, `"min"`, `"h"`, and `"d"`.
|
|
301
|
+
`"auto"` chooses a sensible unit from the full duration. Editable boxes display
|
|
302
|
+
and accept that unit while the delivery continues receiving seconds, so changing
|
|
303
|
+
presentation units cannot change sample addressing or persisted annotation times.
|
|
304
|
+
|
|
305
|
+
For windowed selection, the workspace reads the returned interval and may provide a low-resolution overview statistic:
|
|
306
|
+
|
|
307
|
+
```python
|
|
308
|
+
start, end = ui.windowed(
|
|
309
|
+
duration=recording.duration,
|
|
310
|
+
default_window=0.1,
|
|
311
|
+
minimum_window=0.001,
|
|
312
|
+
step=0.001,
|
|
313
|
+
overview=recording.summary_values(),
|
|
314
|
+
overview_label="Activity",
|
|
315
|
+
time_unit="ms",
|
|
316
|
+
)
|
|
317
|
+
return recording.read(start, end)
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
`overview` is optional. When supplied, it may be any finite 1D summary and does not need one value per sample. The framework distributes its values uniformly over the recording duration, so block statistics, sliding-window results, and decimated summaries all work. The framework draws and operates the range selector; tabs and exports receive only the value returned by the delivery policy.
|
|
321
|
+
|
|
322
|
+
For irregular stored results, provide explicit segment descriptors and use the returned descriptor to load the matching result:
|
|
323
|
+
|
|
324
|
+
```python
|
|
325
|
+
from sigvue.plugin import Segment
|
|
326
|
+
|
|
327
|
+
selected = ui.segmented(
|
|
328
|
+
duration=recording.duration,
|
|
329
|
+
segments=(
|
|
330
|
+
Segment("event-1", 1.25, 0.08, "First event"),
|
|
331
|
+
Segment("event-2", 4.90, 0.12, "Second event"),
|
|
332
|
+
),
|
|
333
|
+
)
|
|
334
|
+
return results_by_id[selected.identifier]
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
Regular segments with gaps or overlaps can instead use `ui.segmented(duration=..., segment_duration=..., stride=...)`. Segmented mode only owns selection and navigation; the delivery policy decides whether selecting a marker reads raw data, computes one interval lazily, or loads an existing post-processing result.
|
|
338
|
+
|
|
339
|
+
For non-playback refresh, call `ui.refresh(every=1.0)`. The framework prevents overlapping refresh requests and updates mounted views.
|
|
340
|
+
|
|
341
|
+
## Analysis UI
|
|
342
|
+
|
|
343
|
+
The commonly used `AnalysisContext` methods are:
|
|
344
|
+
|
|
345
|
+
| Method | Purpose |
|
|
346
|
+
| --- | --- |
|
|
347
|
+
| `ui.tab(label, columns=..., update=...)` | Add a tab and choose its layout and static/dynamic lifecycle. |
|
|
348
|
+
| `ui.plot(figure, key=...)` | Display a native Plotly or Matplotlib figure. |
|
|
349
|
+
| `ui.table(value, key=...)` | Display tabular data. |
|
|
350
|
+
| `ui.text(value, key=...)` | Display text or Markdown diagnostics. |
|
|
351
|
+
| `ui.number(...)`, `ui.select(...)`, `ui.color(...)` | Declare stored user parameters. |
|
|
352
|
+
| `ui.colormap(...)` | Add a compact Plotly colormap picker with low-to-high gradient previews. |
|
|
353
|
+
| `ui.limits(...)` | Add paired numeric boxes with a shared dual-handle limits bar. |
|
|
354
|
+
| `ui.parameter_group(...)` | Place parameters directly inside the current view. |
|
|
355
|
+
| `ui.view_switcher(...)` | Switch local views with buttons or a dropdown without creating another tab. |
|
|
356
|
+
| `ui.trace_style(...)` | Add a compact color, width, opacity, line-style, and marker picker. |
|
|
357
|
+
| `ui.stat(label, value)` | Add workflow-specific runtime or result details. |
|
|
358
|
+
| `ui.once(key, factory, depends_on=...)` | Cache item-level work across dynamic updates. |
|
|
359
|
+
| `ui.segmented(...)` | Select one regular or irregular timeline segment. |
|
|
360
|
+
|
|
361
|
+
Plotly figures remain interactive. Matplotlib figures are rendered as responsive PNG images. Tabs can mix plots, tables, and text.
|
|
362
|
+
|
|
363
|
+
Use `update="static"` for item context that should be computed once and `update="dynamic"` for data that follows delivery. A static plot factory can name parameter dependencies:
|
|
364
|
+
|
|
365
|
+
```python
|
|
366
|
+
with ui.tab("Reference", update="static"):
|
|
367
|
+
ui.plot(
|
|
368
|
+
lambda: make_reference_figure(data, threshold),
|
|
369
|
+
key="reference",
|
|
370
|
+
depends_on=("threshold",),
|
|
371
|
+
)
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
## Optional annotation and export capabilities
|
|
375
|
+
|
|
376
|
+
Annotation and download are plugin-owned capabilities. If a workspace does not pass an
|
|
377
|
+
`annotator=` or `exporter=` to `AnalysisWorkspace`, the corresponding header menu is not
|
|
378
|
+
shown. The framework supplies typed field/choice helpers, renders the controls, and runs
|
|
379
|
+
exports on its background executor; the plugin decides how annotations are persisted and
|
|
380
|
+
how its domain data is serialized.
|
|
381
|
+
|
|
382
|
+
Implement `DataAnnotator` to discover timeline annotations and add one from the current
|
|
383
|
+
delivered value. Implement `DataExporter` to advertise scope and format choices and write
|
|
384
|
+
one result file into the supplied directory. `CapabilityChoice`, `AnnotationField`,
|
|
385
|
+
`AnnotationPlotBinding`, `Annotation`, `AnnotationRequest`, and `ExportRequest` are
|
|
386
|
+
available from `sigvue.plugin`.
|
|
387
|
+
This keeps formats such as SigMF annotations, MAT, JSON, or a domain-specific archive out
|
|
388
|
+
of the framework.
|
|
389
|
+
|
|
390
|
+
Plot-oriented plugins can attach an `AnnotationPlotBinding` to a numeric
|
|
391
|
+
`AnnotationField`. When the annotation menu opens, Sigvue fills that input from the
|
|
392
|
+
currently visible lower or upper edge of the named axis. A pipeline can set
|
|
393
|
+
`selection_policy="box_preferred"` on the binding to prefer the latest compatible Plotly
|
|
394
|
+
box-selection bounds; deselecting or double-clicking clears the captured box. The plugin
|
|
395
|
+
declares the unit transform and may add the current playback position for buffer-relative
|
|
396
|
+
plot axes; the resulting editable value is still persisted entirely by the plugin.
|
|
397
|
+
|
|
398
|
+
## HTTP API
|
|
399
|
+
|
|
400
|
+
The browser UI uses the same local JSON API available to integrations:
|
|
401
|
+
|
|
402
|
+
| Method and path | Result |
|
|
403
|
+
| --- | --- |
|
|
404
|
+
| `GET /health` | Service health. |
|
|
405
|
+
| `GET /workspaces` | Registered workspaces. |
|
|
406
|
+
| `GET /workspaces/{workspace_id}/items` | Discovered items. |
|
|
407
|
+
| `GET /workspaces/{workspace_id}/items/{item_id}` | Page definition and rendered views. Query parameters carry controls and timeline state. |
|
|
408
|
+
| `POST /workspaces/{workspace_id}/items/{item_id}/exports` | Start a plugin-owned background export with `scope`, `format`, and `control_values`. |
|
|
409
|
+
| `GET /exports/{job_id}` | Poll export status. |
|
|
410
|
+
| `GET /exports/{job_id}/{filename}` | Download a completed export. |
|
|
411
|
+
| `POST /workspaces/{workspace_id}/items/{item_id}/annotations` | Add an annotation through the plugin contract. |
|
|
412
|
+
|
|
413
|
+
## PyPI and standalone distribution
|
|
414
|
+
|
|
415
|
+
The PyPI wheel contains:
|
|
416
|
+
|
|
417
|
+
- The browser server and typed plugin contracts.
|
|
418
|
+
- Dependency metadata that installs Plotly and Matplotlib.
|
|
419
|
+
- The PyInstaller spec under `sigvue._packaging`.
|
|
420
|
+
- The `sigvue-build` command.
|
|
421
|
+
|
|
422
|
+
To build a platform-specific, one-file executable:
|
|
423
|
+
|
|
424
|
+
```bash
|
|
425
|
+
python -m pip install "sigvue[build]"
|
|
426
|
+
sigvue-build
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
The result is `dist/sigvue` or `dist/sigvue.exe`. Build separately on Windows, Linux, and macOS.
|
|
430
|
+
|
|
431
|
+
Workspace packages, `browser.toml`, and data remain external to the executable.
|
|
432
|
+
|
|
433
|
+
## Development
|
|
434
|
+
|
|
435
|
+
```bash
|
|
436
|
+
python -m pip install -e ".[build]"
|
|
437
|
+
PYTHONPATH=src python -m unittest discover -s tests -q
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
Neutral, runnable workspace packages are maintained separately so the framework distribution stays format-independent: [Sigvue Examples](https://github.com/briday1/sigvue-examples).
|