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.
Files changed (59) hide show
  1. oscura/__init__.py +1 -7
  2. oscura/acquisition/__init__.py +147 -0
  3. oscura/acquisition/file.py +255 -0
  4. oscura/acquisition/hardware.py +186 -0
  5. oscura/acquisition/saleae.py +340 -0
  6. oscura/acquisition/socketcan.py +315 -0
  7. oscura/acquisition/streaming.py +38 -0
  8. oscura/acquisition/synthetic.py +229 -0
  9. oscura/acquisition/visa.py +376 -0
  10. oscura/analyzers/__init__.py +3 -0
  11. oscura/analyzers/digital/__init__.py +48 -0
  12. oscura/analyzers/digital/clock.py +9 -1
  13. oscura/analyzers/digital/edges.py +1 -1
  14. oscura/analyzers/digital/extraction.py +195 -0
  15. oscura/analyzers/digital/ic_database.py +498 -0
  16. oscura/analyzers/digital/timing.py +41 -11
  17. oscura/analyzers/digital/timing_paths.py +339 -0
  18. oscura/analyzers/digital/vintage.py +377 -0
  19. oscura/analyzers/digital/vintage_result.py +148 -0
  20. oscura/analyzers/protocols/__init__.py +22 -1
  21. oscura/analyzers/protocols/parallel_bus.py +449 -0
  22. oscura/analyzers/side_channel/__init__.py +52 -0
  23. oscura/analyzers/side_channel/power.py +690 -0
  24. oscura/analyzers/side_channel/timing.py +369 -0
  25. oscura/analyzers/signal_integrity/sparams.py +1 -1
  26. oscura/automotive/__init__.py +4 -2
  27. oscura/automotive/can/patterns.py +3 -1
  28. oscura/automotive/can/session.py +277 -78
  29. oscura/automotive/can/state_machine.py +5 -2
  30. oscura/builders/__init__.py +9 -11
  31. oscura/builders/signal_builder.py +99 -191
  32. oscura/core/exceptions.py +5 -1
  33. oscura/export/__init__.py +12 -0
  34. oscura/export/wavedrom.py +430 -0
  35. oscura/exporters/json_export.py +47 -0
  36. oscura/exporters/vintage_logic_csv.py +247 -0
  37. oscura/loaders/__init__.py +1 -0
  38. oscura/loaders/chipwhisperer.py +393 -0
  39. oscura/loaders/touchstone.py +1 -1
  40. oscura/reporting/__init__.py +7 -0
  41. oscura/reporting/vintage_logic_report.py +523 -0
  42. oscura/session/session.py +54 -46
  43. oscura/sessions/__init__.py +70 -0
  44. oscura/sessions/base.py +323 -0
  45. oscura/sessions/blackbox.py +640 -0
  46. oscura/sessions/generic.py +189 -0
  47. oscura/utils/autodetect.py +5 -1
  48. oscura/visualization/digital_advanced.py +718 -0
  49. oscura/visualization/figure_manager.py +156 -0
  50. {oscura-0.3.0.dist-info → oscura-0.5.0.dist-info}/METADATA +86 -5
  51. {oscura-0.3.0.dist-info → oscura-0.5.0.dist-info}/RECORD +54 -33
  52. oscura/automotive/dtc/data.json +0 -2763
  53. oscura/schemas/bus_configuration.json +0 -322
  54. oscura/schemas/device_mapping.json +0 -182
  55. oscura/schemas/packet_format.json +0 -418
  56. oscura/schemas/protocol_definition.json +0 -363
  57. {oscura-0.3.0.dist-info → oscura-0.5.0.dist-info}/WHEEL +0 -0
  58. {oscura-0.3.0.dist-info → oscura-0.5.0.dist-info}/entry_points.txt +0 -0
  59. {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"]
@@ -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
- vcc_match = 1.0 - min(1.0, abs(v_cc_est - vcc) / vcc)
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