oscura 0.3.0__py3-none-any.whl → 0.5.0__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.
- oscura/__init__.py +1 -7
- oscura/acquisition/__init__.py +147 -0
- oscura/acquisition/file.py +255 -0
- oscura/acquisition/hardware.py +186 -0
- oscura/acquisition/saleae.py +340 -0
- oscura/acquisition/socketcan.py +315 -0
- oscura/acquisition/streaming.py +38 -0
- oscura/acquisition/synthetic.py +229 -0
- oscura/acquisition/visa.py +376 -0
- oscura/analyzers/__init__.py +3 -0
- oscura/analyzers/digital/__init__.py +48 -0
- oscura/analyzers/digital/clock.py +9 -1
- oscura/analyzers/digital/edges.py +1 -1
- oscura/analyzers/digital/extraction.py +195 -0
- oscura/analyzers/digital/ic_database.py +498 -0
- oscura/analyzers/digital/timing.py +41 -11
- oscura/analyzers/digital/timing_paths.py +339 -0
- oscura/analyzers/digital/vintage.py +377 -0
- oscura/analyzers/digital/vintage_result.py +148 -0
- oscura/analyzers/protocols/__init__.py +22 -1
- oscura/analyzers/protocols/parallel_bus.py +449 -0
- oscura/analyzers/side_channel/__init__.py +52 -0
- oscura/analyzers/side_channel/power.py +690 -0
- oscura/analyzers/side_channel/timing.py +369 -0
- oscura/analyzers/signal_integrity/sparams.py +1 -1
- oscura/automotive/__init__.py +4 -2
- oscura/automotive/can/patterns.py +3 -1
- oscura/automotive/can/session.py +277 -78
- oscura/automotive/can/state_machine.py +5 -2
- oscura/builders/__init__.py +9 -11
- oscura/builders/signal_builder.py +99 -191
- oscura/core/exceptions.py +5 -1
- oscura/export/__init__.py +12 -0
- oscura/export/wavedrom.py +430 -0
- oscura/exporters/json_export.py +47 -0
- oscura/exporters/vintage_logic_csv.py +247 -0
- oscura/loaders/__init__.py +1 -0
- oscura/loaders/chipwhisperer.py +393 -0
- oscura/loaders/touchstone.py +1 -1
- oscura/reporting/__init__.py +7 -0
- oscura/reporting/vintage_logic_report.py +523 -0
- oscura/session/session.py +54 -46
- oscura/sessions/__init__.py +70 -0
- oscura/sessions/base.py +323 -0
- oscura/sessions/blackbox.py +640 -0
- oscura/sessions/generic.py +189 -0
- oscura/utils/autodetect.py +5 -1
- oscura/visualization/digital_advanced.py +718 -0
- oscura/visualization/figure_manager.py +156 -0
- {oscura-0.3.0.dist-info → oscura-0.5.0.dist-info}/METADATA +86 -5
- {oscura-0.3.0.dist-info → oscura-0.5.0.dist-info}/RECORD +54 -33
- oscura/automotive/dtc/data.json +0 -2763
- oscura/schemas/bus_configuration.json +0 -322
- oscura/schemas/device_mapping.json +0 -182
- oscura/schemas/packet_format.json +0 -418
- oscura/schemas/protocol_definition.json +0 -363
- {oscura-0.3.0.dist-info → oscura-0.5.0.dist-info}/WHEEL +0 -0
- {oscura-0.3.0.dist-info → oscura-0.5.0.dist-info}/entry_points.txt +0 -0
- {oscura-0.3.0.dist-info → oscura-0.5.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"""Generic analysis session implementation.
|
|
2
|
+
|
|
3
|
+
This module provides GenericSession - a concrete implementation of AnalysisSession
|
|
4
|
+
for general-purpose signal analysis. It wraps the existing session.Session class
|
|
5
|
+
to provide the unified AnalysisSession interface while maintaining backward
|
|
6
|
+
compatibility.
|
|
7
|
+
|
|
8
|
+
Example:
|
|
9
|
+
>>> from oscura.sessions import GenericSession
|
|
10
|
+
>>> from oscura.acquisition import FileSource
|
|
11
|
+
>>>
|
|
12
|
+
>>> # Create generic session
|
|
13
|
+
>>> session = GenericSession(name="Debug Session")
|
|
14
|
+
>>> session.add_recording("capture1", FileSource("signal1.wfm"))
|
|
15
|
+
>>> session.add_recording("capture2", FileSource("signal2.wfm"))
|
|
16
|
+
>>>
|
|
17
|
+
>>> # Compare recordings
|
|
18
|
+
>>> diff = session.compare("capture1", "capture2")
|
|
19
|
+
>>> print(f"Similarity: {diff.similarity_score:.2%}")
|
|
20
|
+
>>>
|
|
21
|
+
>>> # Analyze
|
|
22
|
+
>>> results = session.analyze() # Generic waveform analysis
|
|
23
|
+
|
|
24
|
+
Pattern:
|
|
25
|
+
GenericSession is the default session type for non-domain-specific
|
|
26
|
+
analysis. Use domain-specific sessions (CANSession, SerialSession, etc.)
|
|
27
|
+
when working within a specific protocol or analysis domain.
|
|
28
|
+
|
|
29
|
+
Migration:
|
|
30
|
+
The existing oscura.session.Session class continues to work unchanged.
|
|
31
|
+
GenericSession provides the new AnalysisSession interface while
|
|
32
|
+
delegating to the existing Session implementation internally.
|
|
33
|
+
|
|
34
|
+
References:
|
|
35
|
+
Architecture Plan Phase 0.3: AnalysisSession Generic Implementation
|
|
36
|
+
src/oscura/session/session.py: Legacy Session class
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
from __future__ import annotations
|
|
40
|
+
|
|
41
|
+
from pathlib import Path
|
|
42
|
+
from typing import Any
|
|
43
|
+
|
|
44
|
+
from oscura.sessions.base import AnalysisSession
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class GenericSession(AnalysisSession):
|
|
48
|
+
"""Generic analysis session for general-purpose signal analysis.
|
|
49
|
+
|
|
50
|
+
Provides the unified AnalysisSession interface for non-domain-specific
|
|
51
|
+
analysis. This is the default session type when you don't need CAN,
|
|
52
|
+
Serial, or other specialized functionality.
|
|
53
|
+
|
|
54
|
+
Example:
|
|
55
|
+
>>> from oscura.sessions import GenericSession
|
|
56
|
+
>>> from oscura.acquisition import FileSource
|
|
57
|
+
>>>
|
|
58
|
+
>>> session = GenericSession()
|
|
59
|
+
>>> session.add_recording("test1", FileSource("capture1.wfm"))
|
|
60
|
+
>>> session.add_recording("test2", FileSource("capture2.wfm"))
|
|
61
|
+
>>>
|
|
62
|
+
>>> # Compare traces
|
|
63
|
+
>>> diff = session.compare("test1", "test2")
|
|
64
|
+
>>> print(f"Changed: {diff.changed_bytes} samples")
|
|
65
|
+
>>>
|
|
66
|
+
>>> # Generic analysis
|
|
67
|
+
>>> results = session.analyze()
|
|
68
|
+
>>> print(results["num_recordings"])
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
def analyze(self) -> dict[str, Any]:
|
|
72
|
+
"""Perform generic waveform analysis on all recordings.
|
|
73
|
+
|
|
74
|
+
Analyzes all loaded recordings and provides summary statistics,
|
|
75
|
+
basic waveform measurements, and comparison results.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
Dictionary with analysis results:
|
|
79
|
+
- num_recordings: Number of recordings
|
|
80
|
+
- recordings: List of recording names
|
|
81
|
+
- summary: Summary statistics for each recording
|
|
82
|
+
|
|
83
|
+
Example:
|
|
84
|
+
>>> session = GenericSession()
|
|
85
|
+
>>> session.add_recording("sig", FileSource("capture.wfm"))
|
|
86
|
+
>>> results = session.analyze()
|
|
87
|
+
>>> print(results["summary"]["sig"]["mean"])
|
|
88
|
+
"""
|
|
89
|
+
import numpy as np
|
|
90
|
+
|
|
91
|
+
results: dict[str, Any] = {
|
|
92
|
+
"num_recordings": len(self.recordings),
|
|
93
|
+
"recordings": self.list_recordings(),
|
|
94
|
+
"summary": {},
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
# Analyze each recording
|
|
98
|
+
from oscura.core.types import IQTrace
|
|
99
|
+
|
|
100
|
+
for name in self.list_recordings():
|
|
101
|
+
trace = self.get_recording(name)
|
|
102
|
+
|
|
103
|
+
# Handle IQTrace separately
|
|
104
|
+
if isinstance(trace, IQTrace):
|
|
105
|
+
raise TypeError("IQTrace analysis not yet supported in GenericSession")
|
|
106
|
+
|
|
107
|
+
# Basic statistics
|
|
108
|
+
summary = {
|
|
109
|
+
"num_samples": len(trace.data),
|
|
110
|
+
"sample_rate": trace.metadata.sample_rate,
|
|
111
|
+
"duration": len(trace.data) / trace.metadata.sample_rate,
|
|
112
|
+
"mean": float(np.mean(trace.data)),
|
|
113
|
+
"std": float(np.std(trace.data)),
|
|
114
|
+
"min": float(np.min(trace.data)),
|
|
115
|
+
"max": float(np.max(trace.data)),
|
|
116
|
+
"rms": float(np.sqrt(np.mean(trace.data**2))),
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
results["summary"][name] = summary
|
|
120
|
+
|
|
121
|
+
# If multiple recordings, add comparisons
|
|
122
|
+
if len(self.recordings) >= 2:
|
|
123
|
+
results["comparisons"] = {}
|
|
124
|
+
names = self.list_recordings()
|
|
125
|
+
for i in range(len(names)):
|
|
126
|
+
for j in range(i + 1, len(names)):
|
|
127
|
+
comparison_key = f"{names[i]}_vs_{names[j]}"
|
|
128
|
+
comp_result = self.compare(names[i], names[j])
|
|
129
|
+
results["comparisons"][comparison_key] = {
|
|
130
|
+
"similarity": comp_result.similarity_score,
|
|
131
|
+
"changed_samples": comp_result.changed_bytes,
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return results
|
|
135
|
+
|
|
136
|
+
def export_results(self, format: str, path: str | Path) -> None:
|
|
137
|
+
"""Export analysis results to file.
|
|
138
|
+
|
|
139
|
+
Extends base export with additional formats for generic analysis.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
format: Export format ("report", "json", "csv").
|
|
143
|
+
path: Output file path.
|
|
144
|
+
|
|
145
|
+
Raises:
|
|
146
|
+
ValueError: If format not supported.
|
|
147
|
+
|
|
148
|
+
Example:
|
|
149
|
+
>>> session.export_results("report", "analysis.txt")
|
|
150
|
+
>>> session.export_results("json", "results.json")
|
|
151
|
+
"""
|
|
152
|
+
path = Path(path)
|
|
153
|
+
|
|
154
|
+
if format == "json":
|
|
155
|
+
# Export as JSON
|
|
156
|
+
import json
|
|
157
|
+
|
|
158
|
+
results = self.analyze()
|
|
159
|
+
|
|
160
|
+
with open(path, "w") as f:
|
|
161
|
+
json.dump(results, f, indent=2)
|
|
162
|
+
|
|
163
|
+
elif format == "csv":
|
|
164
|
+
# Export summary as CSV
|
|
165
|
+
import csv
|
|
166
|
+
|
|
167
|
+
results = self.analyze()
|
|
168
|
+
|
|
169
|
+
with open(path, "w", newline="") as f:
|
|
170
|
+
if not results["summary"]:
|
|
171
|
+
return # No data to export
|
|
172
|
+
|
|
173
|
+
# Get fieldnames from first recording
|
|
174
|
+
first_rec = next(iter(results["summary"].values()))
|
|
175
|
+
fieldnames = ["recording"] + list(first_rec.keys())
|
|
176
|
+
|
|
177
|
+
writer = csv.DictWriter(f, fieldnames=fieldnames)
|
|
178
|
+
writer.writeheader()
|
|
179
|
+
|
|
180
|
+
for name, summary in results["summary"].items():
|
|
181
|
+
row = {"recording": name, **summary}
|
|
182
|
+
writer.writerow(row)
|
|
183
|
+
|
|
184
|
+
else:
|
|
185
|
+
# Fall back to base implementation (text report)
|
|
186
|
+
super().export_results(format, path)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
__all__ = ["GenericSession"]
|
oscura/utils/autodetect.py
CHANGED
|
@@ -317,7 +317,11 @@ def detect_logic_family(
|
|
|
317
317
|
# Score based on how well levels match
|
|
318
318
|
low_match = 1.0 - min(1.0, abs(v_low - vol) / 0.5)
|
|
319
319
|
high_match = 1.0 - min(1.0, abs(v_high - voh) / 0.5)
|
|
320
|
-
|
|
320
|
+
# Handle VCC=0 for families like ECL
|
|
321
|
+
if vcc != 0:
|
|
322
|
+
vcc_match = 1.0 - min(1.0, abs(v_cc_est - vcc) / abs(vcc))
|
|
323
|
+
else:
|
|
324
|
+
vcc_match = 1.0 if abs(v_cc_est) < 0.5 else 0.0
|
|
321
325
|
|
|
322
326
|
score = (low_match + high_match + vcc_match) / 3
|
|
323
327
|
|