oscura 0.8.0__py3-none-any.whl → 0.11.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 (161) hide show
  1. oscura/__init__.py +19 -19
  2. oscura/__main__.py +4 -0
  3. oscura/analyzers/__init__.py +2 -0
  4. oscura/analyzers/digital/extraction.py +2 -3
  5. oscura/analyzers/digital/quality.py +1 -1
  6. oscura/analyzers/digital/timing.py +1 -1
  7. oscura/analyzers/ml/signal_classifier.py +6 -0
  8. oscura/analyzers/patterns/__init__.py +66 -0
  9. oscura/analyzers/power/basic.py +3 -3
  10. oscura/analyzers/power/soa.py +1 -1
  11. oscura/analyzers/power/switching.py +3 -3
  12. oscura/analyzers/signal_classification.py +529 -0
  13. oscura/analyzers/signal_integrity/sparams.py +3 -3
  14. oscura/analyzers/statistics/basic.py +10 -7
  15. oscura/analyzers/validation.py +1 -1
  16. oscura/analyzers/waveform/measurements.py +200 -156
  17. oscura/analyzers/waveform/measurements_with_uncertainty.py +91 -35
  18. oscura/analyzers/waveform/spectral.py +182 -84
  19. oscura/api/dsl/commands.py +15 -6
  20. oscura/api/server/templates/base.html +137 -146
  21. oscura/api/server/templates/export.html +84 -110
  22. oscura/api/server/templates/home.html +248 -267
  23. oscura/api/server/templates/protocols.html +44 -48
  24. oscura/api/server/templates/reports.html +27 -35
  25. oscura/api/server/templates/session_detail.html +68 -78
  26. oscura/api/server/templates/sessions.html +62 -72
  27. oscura/api/server/templates/waveforms.html +54 -64
  28. oscura/automotive/__init__.py +1 -1
  29. oscura/automotive/can/session.py +1 -1
  30. oscura/automotive/dbc/generator.py +638 -23
  31. oscura/automotive/dtc/data.json +17 -102
  32. oscura/automotive/flexray/fibex.py +9 -1
  33. oscura/automotive/uds/decoder.py +99 -6
  34. oscura/cli/analyze.py +8 -2
  35. oscura/cli/batch.py +36 -5
  36. oscura/cli/characterize.py +18 -4
  37. oscura/cli/export.py +47 -5
  38. oscura/cli/main.py +2 -0
  39. oscura/cli/onboarding/wizard.py +10 -6
  40. oscura/cli/pipeline.py +585 -0
  41. oscura/cli/visualize.py +6 -4
  42. oscura/convenience.py +400 -32
  43. oscura/core/measurement_result.py +286 -0
  44. oscura/core/progress.py +1 -1
  45. oscura/core/schemas/device_mapping.json +2 -8
  46. oscura/core/schemas/packet_format.json +4 -24
  47. oscura/core/schemas/protocol_definition.json +2 -12
  48. oscura/core/types.py +232 -239
  49. oscura/correlation/multi_protocol.py +1 -1
  50. oscura/export/legacy/__init__.py +11 -0
  51. oscura/export/legacy/wav.py +75 -0
  52. oscura/exporters/__init__.py +19 -0
  53. oscura/exporters/wireshark.py +809 -0
  54. oscura/hardware/acquisition/file.py +5 -19
  55. oscura/hardware/acquisition/saleae.py +10 -10
  56. oscura/hardware/acquisition/socketcan.py +4 -6
  57. oscura/hardware/acquisition/synthetic.py +1 -5
  58. oscura/hardware/acquisition/visa.py +6 -6
  59. oscura/hardware/security/side_channel_detector.py +5 -508
  60. oscura/inference/message_format.py +686 -1
  61. oscura/jupyter/display.py +2 -2
  62. oscura/jupyter/magic.py +3 -3
  63. oscura/loaders/__init__.py +17 -12
  64. oscura/loaders/binary.py +1 -1
  65. oscura/loaders/chipwhisperer.py +1 -2
  66. oscura/loaders/configurable.py +1 -1
  67. oscura/loaders/csv_loader.py +2 -2
  68. oscura/loaders/hdf5_loader.py +1 -1
  69. oscura/loaders/lazy.py +6 -1
  70. oscura/loaders/mmap_loader.py +0 -1
  71. oscura/loaders/numpy_loader.py +8 -7
  72. oscura/loaders/preprocessing.py +3 -5
  73. oscura/loaders/rigol.py +21 -7
  74. oscura/loaders/sigrok.py +2 -5
  75. oscura/loaders/tdms.py +3 -2
  76. oscura/loaders/tektronix.py +38 -32
  77. oscura/loaders/tss.py +20 -27
  78. oscura/loaders/validation.py +17 -10
  79. oscura/loaders/vcd.py +13 -8
  80. oscura/loaders/wav.py +1 -6
  81. oscura/pipeline/__init__.py +76 -0
  82. oscura/pipeline/handlers/__init__.py +165 -0
  83. oscura/pipeline/handlers/analyzers.py +1045 -0
  84. oscura/pipeline/handlers/decoders.py +899 -0
  85. oscura/pipeline/handlers/exporters.py +1103 -0
  86. oscura/pipeline/handlers/filters.py +891 -0
  87. oscura/pipeline/handlers/loaders.py +640 -0
  88. oscura/pipeline/handlers/transforms.py +768 -0
  89. oscura/reporting/formatting/measurements.py +55 -14
  90. oscura/reporting/templates/enhanced/protocol_re.html +504 -503
  91. oscura/sessions/legacy.py +49 -1
  92. oscura/side_channel/__init__.py +38 -57
  93. oscura/utils/builders/signal_builder.py +5 -5
  94. oscura/utils/comparison/compare.py +7 -9
  95. oscura/utils/comparison/golden.py +1 -1
  96. oscura/utils/filtering/convenience.py +2 -2
  97. oscura/utils/math/arithmetic.py +38 -62
  98. oscura/utils/math/interpolation.py +20 -20
  99. oscura/utils/pipeline/__init__.py +4 -17
  100. oscura/utils/progressive.py +1 -4
  101. oscura/utils/triggering/edge.py +1 -1
  102. oscura/utils/triggering/pattern.py +2 -2
  103. oscura/utils/triggering/pulse.py +2 -2
  104. oscura/utils/triggering/window.py +3 -3
  105. oscura/validation/hil_testing.py +11 -11
  106. oscura/visualization/__init__.py +46 -284
  107. oscura/visualization/batch.py +72 -433
  108. oscura/visualization/plot.py +542 -53
  109. oscura/visualization/styles.py +184 -318
  110. oscura/workflows/batch/advanced.py +1 -1
  111. oscura/workflows/batch/aggregate.py +12 -9
  112. oscura/workflows/complete_re.py +251 -23
  113. oscura/workflows/digital.py +27 -4
  114. oscura/workflows/multi_trace.py +136 -17
  115. oscura/workflows/waveform.py +11 -6
  116. oscura-0.11.0.dist-info/METADATA +460 -0
  117. {oscura-0.8.0.dist-info → oscura-0.11.0.dist-info}/RECORD +120 -145
  118. oscura/side_channel/dpa.py +0 -1025
  119. oscura/utils/optimization/__init__.py +0 -19
  120. oscura/utils/optimization/parallel.py +0 -443
  121. oscura/utils/optimization/search.py +0 -532
  122. oscura/utils/pipeline/base.py +0 -338
  123. oscura/utils/pipeline/composition.py +0 -248
  124. oscura/utils/pipeline/parallel.py +0 -449
  125. oscura/utils/pipeline/pipeline.py +0 -375
  126. oscura/utils/search/__init__.py +0 -16
  127. oscura/utils/search/anomaly.py +0 -424
  128. oscura/utils/search/context.py +0 -294
  129. oscura/utils/search/pattern.py +0 -288
  130. oscura/utils/storage/__init__.py +0 -61
  131. oscura/utils/storage/database.py +0 -1166
  132. oscura/visualization/accessibility.py +0 -526
  133. oscura/visualization/annotations.py +0 -371
  134. oscura/visualization/axis_scaling.py +0 -305
  135. oscura/visualization/colors.py +0 -451
  136. oscura/visualization/digital.py +0 -436
  137. oscura/visualization/eye.py +0 -571
  138. oscura/visualization/histogram.py +0 -281
  139. oscura/visualization/interactive.py +0 -1035
  140. oscura/visualization/jitter.py +0 -1042
  141. oscura/visualization/keyboard.py +0 -394
  142. oscura/visualization/layout.py +0 -400
  143. oscura/visualization/optimization.py +0 -1079
  144. oscura/visualization/palettes.py +0 -446
  145. oscura/visualization/power.py +0 -508
  146. oscura/visualization/power_extended.py +0 -955
  147. oscura/visualization/presets.py +0 -469
  148. oscura/visualization/protocols.py +0 -1246
  149. oscura/visualization/render.py +0 -223
  150. oscura/visualization/rendering.py +0 -444
  151. oscura/visualization/reverse_engineering.py +0 -838
  152. oscura/visualization/signal_integrity.py +0 -989
  153. oscura/visualization/specialized.py +0 -643
  154. oscura/visualization/spectral.py +0 -1226
  155. oscura/visualization/thumbnails.py +0 -340
  156. oscura/visualization/time_axis.py +0 -351
  157. oscura/visualization/waveform.py +0 -454
  158. oscura-0.8.0.dist-info/METADATA +0 -661
  159. {oscura-0.8.0.dist-info → oscura-0.11.0.dist-info}/WHEEL +0 -0
  160. {oscura-0.8.0.dist-info → oscura-0.11.0.dist-info}/entry_points.txt +0 -0
  161. {oscura-0.8.0.dist-info → oscura-0.11.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,43 +1,35 @@
1
- {% extends "base.html" %}
2
-
3
- {% block title %}Reports - {{ title }}{% endblock %}
4
-
5
- {% block content %}
1
+ {% extends "base.html" %} {% block title %}Reports - {{ title }}{% endblock %} {% block content %}
6
2
  <div class="row">
7
- <div class="col-12">
8
- <div class="card">
9
- <div class="card-body">
10
- <h2 class="card-title mb-4">
11
- <i class="bi bi-file-text"></i> Analysis Reports
12
- </h2>
13
-
14
- {% if report_path %}
15
- <div class="alert alert-success">
16
- <i class="bi bi-check-circle"></i> Report generated successfully
17
- </div>
18
-
19
- <div class="d-grid gap-2">
20
- <a href="/api/download/{{ session_id }}/report" class="btn btn-primary">
21
- <i class="bi bi-download"></i> Download Full Report
22
- </a>
23
- </div>
24
-
25
- <hr>
3
+ <div class="col-12">
4
+ <div class="card">
5
+ <div class="card-body">
6
+ <h2 class="card-title mb-4"><i class="bi bi-file-text"></i> Analysis Reports</h2>
7
+
8
+ {% if report_path %}
9
+ <div class="alert alert-success"><i class="bi bi-check-circle"></i> Report generated successfully</div>
10
+
11
+ <div class="d-grid gap-2">
12
+ <a href="/api/download/{{ session_id }}/report" class="btn btn-primary">
13
+ <i class="bi bi-download"></i> Download Full Report
14
+ </a>
15
+ </div>
26
16
 
27
- <h5>Report Preview</h5>
28
- <p class="text-muted">Full report available for download above.</p>
17
+ <hr />
29
18
 
30
- {% else %}
31
- <div class="alert alert-warning">
32
- <i class="bi bi-exclamation-triangle"></i> No report available yet. Analysis may still be in progress.
33
- </div>
19
+ <h5>Report Preview</h5>
20
+ <p class="text-muted">Full report available for download above.</p>
34
21
 
35
- <a href="/session/{{ session_id }}" class="btn btn-outline-primary">
36
- <i class="bi bi-arrow-left"></i> Back to Session
37
- </a>
38
- {% endif %}
39
- </div>
22
+ {% else %}
23
+ <div class="alert alert-warning">
24
+ <i class="bi bi-exclamation-triangle"></i> No report available yet. Analysis may still be in progress.
40
25
  </div>
26
+
27
+ <a href="/session/{{ session_id }}" class="btn btn-outline-primary">
28
+ <i class="bi bi-arrow-left"></i> Back to Session
29
+ </a>
30
+ {% endif %}
31
+ </div>
41
32
  </div>
33
+ </div>
42
34
  </div>
43
35
  {% endblock %}
@@ -1,89 +1,79 @@
1
- {% extends "base.html" %}
2
-
3
- {% block title %}Session {{ session.id[:8] }} - {{ title }}{% endblock %}
4
-
5
- {% block content %}
1
+ {% extends "base.html" %} {% block title %}Session {{ session.id[:8] }} - {{ title }}{% endblock %} {% block content %}
6
2
  <div class="row">
7
- <div class="col-lg-8">
8
- <div class="card mb-4">
9
- <div class="card-body">
10
- <h2 class="card-title">
11
- <i class="bi bi-file-earmark-code"></i> {{ session.filename }}
12
- </h2>
13
- <p class="text-muted">Session ID: <code>{{ session.id }}</code></p>
3
+ <div class="col-lg-8">
4
+ <div class="card mb-4">
5
+ <div class="card-body">
6
+ <h2 class="card-title"><i class="bi bi-file-earmark-code"></i> {{ session.filename }}</h2>
7
+ <p class="text-muted">Session ID: <code>{{ session.id }}</code></p>
14
8
 
15
- <div class="row mt-3">
16
- <div class="col-md-6">
17
- <p><strong>Status:</strong>
18
- <span class="session-status status-{{ session.status }}">
19
- {{ session.status }}
20
- </span>
21
- </p>
22
- <p><strong>Created:</strong> {{ session.created_at[:19] }}</p>
23
- <p><strong>Updated:</strong> {{ session.updated_at[:19] }}</p>
24
- </div>
25
- <div class="col-md-6">
26
- <p><strong>File Hash:</strong> <code>{{ session.file_hash[:16] }}...</code></p>
27
- </div>
28
- </div>
9
+ <div class="row mt-3">
10
+ <div class="col-md-6">
11
+ <p>
12
+ <strong>Status:</strong>
13
+ <span class="session-status status-{{ session.status }}"> {{ session.status }} </span>
14
+ </p>
15
+ <p><strong>Created:</strong> {{ session.created_at[:19] }}</p>
16
+ <p><strong>Updated:</strong> {{ session.updated_at[:19] }}</p>
17
+ </div>
18
+ <div class="col-md-6">
19
+ <p><strong>File Hash:</strong> <code>{{ session.file_hash[:16] }}...</code></p>
20
+ </div>
21
+ </div>
29
22
 
30
- {% if protocol_spec %}
31
- <hr>
32
- <h5>Protocol Specification</h5>
33
- <p><strong>Protocol:</strong> {{ protocol_spec.protocol_name }}</p>
34
- <p><strong>Messages:</strong> {{ protocol_spec.message_count }}</p>
35
- <p><strong>Fields:</strong> {{ protocol_spec.field_count }}</p>
23
+ {% if protocol_spec %}
24
+ <hr />
25
+ <h5>Protocol Specification</h5>
26
+ <p><strong>Protocol:</strong> {{ protocol_spec.protocol_name }}</p>
27
+ <p><strong>Messages:</strong> {{ protocol_spec.message_count }}</p>
28
+ <p><strong>Fields:</strong> {{ protocol_spec.field_count }}</p>
36
29
 
37
- {% if protocol_spec.fields %}
38
- <div class="table-responsive mt-3">
39
- <table class="table table-sm">
40
- <thead>
41
- <tr>
42
- <th>Field</th>
43
- <th>Offset</th>
44
- <th>Length</th>
45
- <th>Type</th>
46
- <th>Confidence</th>
47
- </tr>
48
- </thead>
49
- <tbody>
50
- {% for field in protocol_spec.fields %}
51
- <tr>
52
- <td>{{ field.name }}</td>
53
- <td>{{ field.offset }}</td>
54
- <td>{{ field.length }}</td>
55
- <td>{{ field.type }}</td>
56
- <td>{{ (field.confidence * 100)|round(1) }}%</td>
57
- </tr>
58
- {% endfor %}
59
- </tbody>
60
- </table>
61
- </div>
62
- {% endif %}
63
- {% endif %}
64
- </div>
30
+ {% if protocol_spec.fields %}
31
+ <div class="table-responsive mt-3">
32
+ <table class="table table-sm">
33
+ <thead>
34
+ <tr>
35
+ <th>Field</th>
36
+ <th>Offset</th>
37
+ <th>Length</th>
38
+ <th>Type</th>
39
+ <th>Confidence</th>
40
+ </tr>
41
+ </thead>
42
+ <tbody>
43
+ {% for field in protocol_spec.fields %}
44
+ <tr>
45
+ <td>{{ field.name }}</td>
46
+ <td>{{ field.offset }}</td>
47
+ <td>{{ field.length }}</td>
48
+ <td>{{ field.type }}</td>
49
+ <td>{{ (field.confidence * 100)|round(1) }}%</td>
50
+ </tr>
51
+ {% endfor %}
52
+ </tbody>
53
+ </table>
65
54
  </div>
55
+ {% endif %} {% endif %}
56
+ </div>
66
57
  </div>
58
+ </div>
67
59
 
68
- <div class="col-lg-4">
69
- <div class="card mb-4">
70
- <div class="card-body">
71
- <h5 class="card-title">
72
- <i class="bi bi-tools"></i> Actions
73
- </h5>
74
- <div class="d-grid gap-2">
75
- <a href="/waveforms/{{ session.id }}" class="btn btn-outline-primary">
76
- <i class="bi bi-graph-up"></i> View Waveforms
77
- </a>
78
- <a href="/reports/{{ session.id }}" class="btn btn-outline-primary">
79
- <i class="bi bi-file-text"></i> View Reports
80
- </a>
81
- <a href="/export/{{ session.id }}" class="btn btn-outline-primary">
82
- <i class="bi bi-download"></i> Export/Download
83
- </a>
84
- </div>
85
- </div>
60
+ <div class="col-lg-4">
61
+ <div class="card mb-4">
62
+ <div class="card-body">
63
+ <h5 class="card-title"><i class="bi bi-tools"></i> Actions</h5>
64
+ <div class="d-grid gap-2">
65
+ <a href="/waveforms/{{ session.id }}" class="btn btn-outline-primary">
66
+ <i class="bi bi-graph-up"></i> View Waveforms
67
+ </a>
68
+ <a href="/reports/{{ session.id }}" class="btn btn-outline-primary">
69
+ <i class="bi bi-file-text"></i> View Reports
70
+ </a>
71
+ <a href="/export/{{ session.id }}" class="btn btn-outline-primary">
72
+ <i class="bi bi-download"></i> Export/Download
73
+ </a>
86
74
  </div>
75
+ </div>
87
76
  </div>
77
+ </div>
88
78
  </div>
89
79
  {% endblock %}
@@ -1,83 +1,73 @@
1
- {% extends "base.html" %}
2
-
3
- {% block title %}Sessions - {{ title }}{% endblock %}
4
-
5
- {% block content %}
1
+ {% extends "base.html" %} {% block title %}Sessions - {{ title }}{% endblock %} {% block content %}
6
2
  <div class="row">
7
- <div class="col-12">
8
- <div class="card">
9
- <div class="card-body">
10
- <h1 class="card-title mb-4">
11
- <i class="bi bi-list-task"></i> Analysis Sessions
12
- </h1>
3
+ <div class="col-12">
4
+ <div class="card">
5
+ <div class="card-body">
6
+ <h1 class="card-title mb-4"><i class="bi bi-list-task"></i> Analysis Sessions</h1>
13
7
 
14
- {% if sessions %}
15
- <div class="table-responsive">
16
- <table class="table table-hover">
17
- <thead>
18
- <tr>
19
- <th>Session ID</th>
20
- <th>Filename</th>
21
- <th>Status</th>
22
- <th>Created</th>
23
- <th>Updated</th>
24
- <th>Actions</th>
25
- </tr>
26
- </thead>
27
- <tbody>
28
- {% for session in sessions %}
29
- <tr>
30
- <td><code>{{ session.session_id[:8] }}...</code></td>
31
- <td>{{ session.filename }}</td>
32
- <td>
33
- <span class="session-status status-{{ session.status }}">
34
- {{ session.status }}
35
- </span>
36
- </td>
37
- <td>{{ session.created_at[:19] }}</td>
38
- <td>{{ session.updated_at[:19] }}</td>
39
- <td>
40
- <a href="/session/{{ session.session_id }}" class="btn btn-sm btn-primary">
41
- <i class="bi bi-eye"></i> View
42
- </a>
43
- <button class="btn btn-sm btn-danger" onclick="deleteSession('{{ session.session_id }}')">
44
- <i class="bi bi-trash"></i>
45
- </button>
46
- </td>
47
- </tr>
48
- {% endfor %}
49
- </tbody>
50
- </table>
51
- </div>
52
- {% else %}
53
- <div class="alert alert-info">
54
- <i class="bi bi-info-circle"></i> No sessions yet. <a href="/">Upload a file</a> to get started.
55
- </div>
56
- {% endif %}
57
- </div>
8
+ {% if sessions %}
9
+ <div class="table-responsive">
10
+ <table class="table table-hover">
11
+ <thead>
12
+ <tr>
13
+ <th>Session ID</th>
14
+ <th>Filename</th>
15
+ <th>Status</th>
16
+ <th>Created</th>
17
+ <th>Updated</th>
18
+ <th>Actions</th>
19
+ </tr>
20
+ </thead>
21
+ <tbody>
22
+ {% for session in sessions %}
23
+ <tr>
24
+ <td><code>{{ session.session_id[:8] }}...</code></td>
25
+ <td>{{ session.filename }}</td>
26
+ <td>
27
+ <span class="session-status status-{{ session.status }}"> {{ session.status }} </span>
28
+ </td>
29
+ <td>{{ session.created_at[:19] }}</td>
30
+ <td>{{ session.updated_at[:19] }}</td>
31
+ <td>
32
+ <a href="/session/{{ session.session_id }}" class="btn btn-sm btn-primary">
33
+ <i class="bi bi-eye"></i> View
34
+ </a>
35
+ <button class="btn btn-sm btn-danger" onclick="deleteSession('{{ session.session_id }}')">
36
+ <i class="bi bi-trash"></i>
37
+ </button>
38
+ </td>
39
+ </tr>
40
+ {% endfor %}
41
+ </tbody>
42
+ </table>
43
+ </div>
44
+ {% else %}
45
+ <div class="alert alert-info">
46
+ <i class="bi bi-info-circle"></i> No sessions yet. <a href="/">Upload a file</a> to get started.
58
47
  </div>
48
+ {% endif %}
49
+ </div>
59
50
  </div>
51
+ </div>
60
52
  </div>
61
- {% endblock %}
62
-
63
- {% block extra_scripts %}
53
+ {% endblock %} {% block extra_scripts %}
64
54
  <script>
65
- async function deleteSession(sessionId) {
66
- if (!confirm('Delete this session?')) return;
55
+ async function deleteSession(sessionId) {
56
+ if (!confirm('Delete this session?')) return;
67
57
 
68
- try {
69
- const response = await fetch(`/api/session/${sessionId}`, {
70
- method: 'DELETE'
71
- });
58
+ try {
59
+ const response = await fetch(`/api/session/${sessionId}`, {
60
+ method: 'DELETE',
61
+ });
72
62
 
73
- if (response.ok) {
74
- location.reload();
75
- } else {
76
- alert('Failed to delete session');
77
- }
78
- } catch (error) {
79
- alert('Error: ' + error.message);
80
- }
63
+ if (response.ok) {
64
+ location.reload();
65
+ } else {
66
+ alert('Failed to delete session');
67
+ }
68
+ } catch (error) {
69
+ alert('Error: ' + error.message);
81
70
  }
71
+ }
82
72
  </script>
83
73
  {% endblock %}
@@ -1,73 +1,63 @@
1
- {% extends "base.html" %}
2
-
3
- {% block title %}Waveforms - {{ title }}{% endblock %}
4
-
5
- {% block content %}
1
+ {% extends "base.html" %} {% block title %}Waveforms - {{ title }}{% endblock %} {% block content %}
6
2
  <div class="row">
7
- <div class="col-12">
8
- <div class="card mb-4">
9
- <div class="card-body">
10
- <h2 class="card-title">
11
- <i class="bi bi-graph-up"></i> Waveform Viewer
12
- </h2>
13
- <p class="text-muted">{{ filename }}</p>
14
- </div>
15
- </div>
3
+ <div class="col-12">
4
+ <div class="card mb-4">
5
+ <div class="card-body">
6
+ <h2 class="card-title"><i class="bi bi-graph-up"></i> Waveform Viewer</h2>
7
+ <p class="text-muted">{{ filename }}</p>
8
+ </div>
9
+ </div>
16
10
 
17
- <div class="card">
18
- <div class="card-body">
19
- <div id="waveformPlot" style="width: 100%; height: 600px;"></div>
20
- </div>
21
- </div>
11
+ <div class="card">
12
+ <div class="card-body">
13
+ <div id="waveformPlot" style="width: 100%; height: 600px"></div>
14
+ </div>
15
+ </div>
22
16
 
23
- <div class="card mt-4">
24
- <div class="card-body">
25
- <h5 class="card-title">Controls</h5>
26
- <div class="btn-group" role="group">
27
- <button class="btn btn-outline-secondary" onclick="resetZoom()">
28
- <i class="bi bi-zoom-out"></i> Reset Zoom
29
- </button>
30
- <button class="btn btn-outline-secondary" onclick="togglePan()">
31
- <i class="bi bi-arrows-move"></i> Pan
32
- </button>
33
- </div>
34
- </div>
17
+ <div class="card mt-4">
18
+ <div class="card-body">
19
+ <h5 class="card-title">Controls</h5>
20
+ <div class="btn-group" role="group">
21
+ <button class="btn btn-outline-secondary" onclick="resetZoom()">
22
+ <i class="bi bi-zoom-out"></i> Reset Zoom
23
+ </button>
24
+ <button class="btn btn-outline-secondary" onclick="togglePan()"><i class="bi bi-arrows-move"></i> Pan</button>
35
25
  </div>
26
+ </div>
36
27
  </div>
28
+ </div>
37
29
  </div>
38
- {% endblock %}
39
-
40
- {% block extra_scripts %}
30
+ {% endblock %} {% block extra_scripts %}
41
31
  <script>
42
- const sessionId = '{{ session_id }}';
43
- const plotlyConfig = {{ plotly_config|safe }};
44
-
45
- async function loadWaveform() {
46
- try {
47
- const response = await fetch(`/api/session/${sessionId}/waveform`);
48
- const data = await response.json();
49
-
50
- Plotly.newPlot('waveformPlot', data.data, data.layout, plotlyConfig);
51
- } catch (error) {
52
- console.error('Failed to load waveform:', error);
53
- document.getElementById('waveformPlot').innerHTML =
54
- '<div class="alert alert-danger">Failed to load waveform data</div>';
55
- }
56
- }
57
-
58
- function resetZoom() {
59
- Plotly.relayout('waveformPlot', {
60
- 'xaxis.autorange': true,
61
- 'yaxis.autorange': true
62
- });
63
- }
64
-
65
- function togglePan() {
66
- const plot = document.getElementById('waveformPlot');
67
- Plotly.relayout(plot, {dragmode: 'pan'});
68
- }
69
-
70
- // Load waveform on page load
71
- loadWaveform();
32
+ const sessionId = '{{ session_id }}';
33
+ const plotlyConfig = {{ plotly_config|safe }};
34
+
35
+ async function loadWaveform() {
36
+ try {
37
+ const response = await fetch(`/api/session/${sessionId}/waveform`);
38
+ const data = await response.json();
39
+
40
+ Plotly.newPlot('waveformPlot', data.data, data.layout, plotlyConfig);
41
+ } catch (error) {
42
+ console.error('Failed to load waveform:', error);
43
+ document.getElementById('waveformPlot').innerHTML =
44
+ '<div class="alert alert-danger">Failed to load waveform data</div>';
45
+ }
46
+ }
47
+
48
+ function resetZoom() {
49
+ Plotly.relayout('waveformPlot', {
50
+ 'xaxis.autorange': true,
51
+ 'yaxis.autorange': true
52
+ });
53
+ }
54
+
55
+ function togglePan() {
56
+ const plot = document.getElementById('waveformPlot');
57
+ Plotly.relayout(plot, {dragmode: 'pan'});
58
+ }
59
+
60
+ // Load waveform on page load
61
+ loadWaveform();
72
62
  </script>
73
63
  {% endblock %}
@@ -49,7 +49,7 @@ try:
49
49
  __version__ = version("oscura")
50
50
  except Exception:
51
51
  # Fallback for development/testing when package not installed
52
- __version__ = "0.8.0"
52
+ __version__ = "0.11.0"
53
53
 
54
54
  __all__ = [
55
55
  "CANMessage",
@@ -15,7 +15,7 @@ try:
15
15
 
16
16
  _HAS_PANDAS = True
17
17
  except ImportError:
18
- pd = None # type: ignore[assignment]
18
+ pd = None
19
19
  _HAS_PANDAS = False
20
20
 
21
21
  from oscura.automotive.can.analysis import MessageAnalyzer