oscura 0.8.0__py3-none-any.whl → 0.10.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 (151) hide show
  1. oscura/__init__.py +19 -19
  2. oscura/analyzers/__init__.py +2 -0
  3. oscura/analyzers/digital/extraction.py +2 -3
  4. oscura/analyzers/digital/quality.py +1 -1
  5. oscura/analyzers/digital/timing.py +1 -1
  6. oscura/analyzers/patterns/__init__.py +66 -0
  7. oscura/analyzers/power/basic.py +3 -3
  8. oscura/analyzers/power/soa.py +1 -1
  9. oscura/analyzers/power/switching.py +3 -3
  10. oscura/analyzers/signal_classification.py +529 -0
  11. oscura/analyzers/signal_integrity/sparams.py +3 -3
  12. oscura/analyzers/statistics/basic.py +10 -7
  13. oscura/analyzers/validation.py +1 -1
  14. oscura/analyzers/waveform/measurements.py +200 -156
  15. oscura/analyzers/waveform/measurements_with_uncertainty.py +91 -35
  16. oscura/analyzers/waveform/spectral.py +164 -73
  17. oscura/api/dsl/commands.py +15 -6
  18. oscura/api/server/templates/base.html +137 -146
  19. oscura/api/server/templates/export.html +84 -110
  20. oscura/api/server/templates/home.html +248 -267
  21. oscura/api/server/templates/protocols.html +44 -48
  22. oscura/api/server/templates/reports.html +27 -35
  23. oscura/api/server/templates/session_detail.html +68 -78
  24. oscura/api/server/templates/sessions.html +62 -72
  25. oscura/api/server/templates/waveforms.html +54 -64
  26. oscura/automotive/__init__.py +1 -1
  27. oscura/automotive/can/session.py +1 -1
  28. oscura/automotive/dbc/generator.py +638 -23
  29. oscura/automotive/uds/decoder.py +99 -6
  30. oscura/cli/analyze.py +8 -2
  31. oscura/cli/batch.py +36 -5
  32. oscura/cli/characterize.py +18 -4
  33. oscura/cli/export.py +47 -5
  34. oscura/cli/main.py +2 -0
  35. oscura/cli/onboarding/wizard.py +10 -6
  36. oscura/cli/pipeline.py +585 -0
  37. oscura/cli/visualize.py +6 -4
  38. oscura/convenience.py +400 -32
  39. oscura/core/measurement_result.py +286 -0
  40. oscura/core/progress.py +1 -1
  41. oscura/core/types.py +232 -239
  42. oscura/correlation/multi_protocol.py +1 -1
  43. oscura/export/legacy/__init__.py +11 -0
  44. oscura/export/legacy/wav.py +75 -0
  45. oscura/exporters/__init__.py +19 -0
  46. oscura/exporters/wireshark.py +809 -0
  47. oscura/hardware/acquisition/file.py +5 -19
  48. oscura/hardware/acquisition/saleae.py +10 -10
  49. oscura/hardware/acquisition/socketcan.py +4 -6
  50. oscura/hardware/acquisition/synthetic.py +1 -5
  51. oscura/hardware/acquisition/visa.py +6 -6
  52. oscura/hardware/security/side_channel_detector.py +5 -508
  53. oscura/inference/message_format.py +686 -1
  54. oscura/jupyter/display.py +2 -2
  55. oscura/jupyter/magic.py +3 -3
  56. oscura/loaders/__init__.py +17 -12
  57. oscura/loaders/binary.py +1 -1
  58. oscura/loaders/chipwhisperer.py +1 -2
  59. oscura/loaders/configurable.py +1 -1
  60. oscura/loaders/csv_loader.py +2 -2
  61. oscura/loaders/hdf5_loader.py +1 -1
  62. oscura/loaders/lazy.py +6 -1
  63. oscura/loaders/mmap_loader.py +0 -1
  64. oscura/loaders/numpy_loader.py +8 -7
  65. oscura/loaders/preprocessing.py +3 -5
  66. oscura/loaders/rigol.py +21 -7
  67. oscura/loaders/sigrok.py +2 -5
  68. oscura/loaders/tdms.py +3 -2
  69. oscura/loaders/tektronix.py +38 -32
  70. oscura/loaders/tss.py +20 -27
  71. oscura/loaders/vcd.py +13 -8
  72. oscura/loaders/wav.py +1 -6
  73. oscura/pipeline/__init__.py +76 -0
  74. oscura/pipeline/handlers/__init__.py +165 -0
  75. oscura/pipeline/handlers/analyzers.py +1045 -0
  76. oscura/pipeline/handlers/decoders.py +899 -0
  77. oscura/pipeline/handlers/exporters.py +1103 -0
  78. oscura/pipeline/handlers/filters.py +891 -0
  79. oscura/pipeline/handlers/loaders.py +640 -0
  80. oscura/pipeline/handlers/transforms.py +768 -0
  81. oscura/reporting/formatting/measurements.py +55 -14
  82. oscura/reporting/templates/enhanced/protocol_re.html +504 -503
  83. oscura/side_channel/__init__.py +38 -57
  84. oscura/utils/builders/signal_builder.py +5 -5
  85. oscura/utils/comparison/compare.py +7 -9
  86. oscura/utils/comparison/golden.py +1 -1
  87. oscura/utils/filtering/convenience.py +2 -2
  88. oscura/utils/math/arithmetic.py +38 -62
  89. oscura/utils/math/interpolation.py +20 -20
  90. oscura/utils/pipeline/__init__.py +4 -17
  91. oscura/utils/progressive.py +1 -4
  92. oscura/utils/triggering/edge.py +1 -1
  93. oscura/utils/triggering/pattern.py +2 -2
  94. oscura/utils/triggering/pulse.py +2 -2
  95. oscura/utils/triggering/window.py +3 -3
  96. oscura/validation/hil_testing.py +11 -11
  97. oscura/visualization/__init__.py +46 -284
  98. oscura/visualization/batch.py +72 -433
  99. oscura/visualization/plot.py +542 -53
  100. oscura/visualization/styles.py +184 -318
  101. oscura/workflows/batch/advanced.py +1 -1
  102. oscura/workflows/batch/aggregate.py +7 -8
  103. oscura/workflows/complete_re.py +251 -23
  104. oscura/workflows/digital.py +27 -4
  105. oscura/workflows/multi_trace.py +136 -17
  106. oscura/workflows/waveform.py +11 -6
  107. {oscura-0.8.0.dist-info → oscura-0.10.0.dist-info}/METADATA +59 -79
  108. {oscura-0.8.0.dist-info → oscura-0.10.0.dist-info}/RECORD +111 -136
  109. oscura/side_channel/dpa.py +0 -1025
  110. oscura/utils/optimization/__init__.py +0 -19
  111. oscura/utils/optimization/parallel.py +0 -443
  112. oscura/utils/optimization/search.py +0 -532
  113. oscura/utils/pipeline/base.py +0 -338
  114. oscura/utils/pipeline/composition.py +0 -248
  115. oscura/utils/pipeline/parallel.py +0 -449
  116. oscura/utils/pipeline/pipeline.py +0 -375
  117. oscura/utils/search/__init__.py +0 -16
  118. oscura/utils/search/anomaly.py +0 -424
  119. oscura/utils/search/context.py +0 -294
  120. oscura/utils/search/pattern.py +0 -288
  121. oscura/utils/storage/__init__.py +0 -61
  122. oscura/utils/storage/database.py +0 -1166
  123. oscura/visualization/accessibility.py +0 -526
  124. oscura/visualization/annotations.py +0 -371
  125. oscura/visualization/axis_scaling.py +0 -305
  126. oscura/visualization/colors.py +0 -451
  127. oscura/visualization/digital.py +0 -436
  128. oscura/visualization/eye.py +0 -571
  129. oscura/visualization/histogram.py +0 -281
  130. oscura/visualization/interactive.py +0 -1035
  131. oscura/visualization/jitter.py +0 -1042
  132. oscura/visualization/keyboard.py +0 -394
  133. oscura/visualization/layout.py +0 -400
  134. oscura/visualization/optimization.py +0 -1079
  135. oscura/visualization/palettes.py +0 -446
  136. oscura/visualization/power.py +0 -508
  137. oscura/visualization/power_extended.py +0 -955
  138. oscura/visualization/presets.py +0 -469
  139. oscura/visualization/protocols.py +0 -1246
  140. oscura/visualization/render.py +0 -223
  141. oscura/visualization/rendering.py +0 -444
  142. oscura/visualization/reverse_engineering.py +0 -838
  143. oscura/visualization/signal_integrity.py +0 -989
  144. oscura/visualization/specialized.py +0 -643
  145. oscura/visualization/spectral.py +0 -1226
  146. oscura/visualization/thumbnails.py +0 -340
  147. oscura/visualization/time_axis.py +0 -351
  148. oscura/visualization/waveform.py +0 -454
  149. {oscura-0.8.0.dist-info → oscura-0.10.0.dist-info}/WHEEL +0 -0
  150. {oscura-0.8.0.dist-info → oscura-0.10.0.dist-info}/entry_points.txt +0 -0
  151. {oscura-0.8.0.dist-info → oscura-0.10.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.10.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