oscura 0.5.0__py3-none-any.whl → 0.6.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 (513) hide show
  1. oscura/__init__.py +169 -167
  2. oscura/analyzers/__init__.py +3 -0
  3. oscura/analyzers/classification.py +659 -0
  4. oscura/analyzers/digital/__init__.py +0 -48
  5. oscura/analyzers/digital/edges.py +325 -65
  6. oscura/analyzers/digital/extraction.py +0 -195
  7. oscura/analyzers/digital/quality.py +293 -166
  8. oscura/analyzers/digital/timing.py +260 -115
  9. oscura/analyzers/digital/timing_numba.py +334 -0
  10. oscura/analyzers/entropy.py +605 -0
  11. oscura/analyzers/eye/diagram.py +176 -109
  12. oscura/analyzers/eye/metrics.py +5 -5
  13. oscura/analyzers/jitter/__init__.py +6 -4
  14. oscura/analyzers/jitter/ber.py +52 -52
  15. oscura/analyzers/jitter/classification.py +156 -0
  16. oscura/analyzers/jitter/decomposition.py +163 -113
  17. oscura/analyzers/jitter/spectrum.py +80 -64
  18. oscura/analyzers/ml/__init__.py +39 -0
  19. oscura/analyzers/ml/features.py +600 -0
  20. oscura/analyzers/ml/signal_classifier.py +604 -0
  21. oscura/analyzers/packet/daq.py +246 -158
  22. oscura/analyzers/packet/parser.py +12 -1
  23. oscura/analyzers/packet/payload.py +50 -2110
  24. oscura/analyzers/packet/payload_analysis.py +361 -181
  25. oscura/analyzers/packet/payload_patterns.py +133 -70
  26. oscura/analyzers/packet/stream.py +84 -23
  27. oscura/analyzers/patterns/__init__.py +26 -5
  28. oscura/analyzers/patterns/anomaly_detection.py +908 -0
  29. oscura/analyzers/patterns/clustering.py +169 -108
  30. oscura/analyzers/patterns/clustering_optimized.py +227 -0
  31. oscura/analyzers/patterns/discovery.py +1 -1
  32. oscura/analyzers/patterns/matching.py +581 -197
  33. oscura/analyzers/patterns/pattern_mining.py +778 -0
  34. oscura/analyzers/patterns/periodic.py +121 -38
  35. oscura/analyzers/patterns/sequences.py +175 -78
  36. oscura/analyzers/power/conduction.py +1 -1
  37. oscura/analyzers/power/soa.py +6 -6
  38. oscura/analyzers/power/switching.py +250 -110
  39. oscura/analyzers/protocol/__init__.py +17 -1
  40. oscura/analyzers/protocols/__init__.py +1 -22
  41. oscura/analyzers/protocols/base.py +6 -6
  42. oscura/analyzers/protocols/ble/__init__.py +38 -0
  43. oscura/analyzers/protocols/ble/analyzer.py +809 -0
  44. oscura/analyzers/protocols/ble/uuids.py +288 -0
  45. oscura/analyzers/protocols/can.py +257 -127
  46. oscura/analyzers/protocols/can_fd.py +107 -80
  47. oscura/analyzers/protocols/flexray.py +139 -80
  48. oscura/analyzers/protocols/hdlc.py +93 -58
  49. oscura/analyzers/protocols/i2c.py +247 -106
  50. oscura/analyzers/protocols/i2s.py +138 -86
  51. oscura/analyzers/protocols/industrial/__init__.py +40 -0
  52. oscura/analyzers/protocols/industrial/bacnet/__init__.py +33 -0
  53. oscura/analyzers/protocols/industrial/bacnet/analyzer.py +708 -0
  54. oscura/analyzers/protocols/industrial/bacnet/encoding.py +412 -0
  55. oscura/analyzers/protocols/industrial/bacnet/services.py +622 -0
  56. oscura/analyzers/protocols/industrial/ethercat/__init__.py +30 -0
  57. oscura/analyzers/protocols/industrial/ethercat/analyzer.py +474 -0
  58. oscura/analyzers/protocols/industrial/ethercat/mailbox.py +339 -0
  59. oscura/analyzers/protocols/industrial/ethercat/topology.py +166 -0
  60. oscura/analyzers/protocols/industrial/modbus/__init__.py +31 -0
  61. oscura/analyzers/protocols/industrial/modbus/analyzer.py +525 -0
  62. oscura/analyzers/protocols/industrial/modbus/crc.py +79 -0
  63. oscura/analyzers/protocols/industrial/modbus/functions.py +436 -0
  64. oscura/analyzers/protocols/industrial/opcua/__init__.py +21 -0
  65. oscura/analyzers/protocols/industrial/opcua/analyzer.py +552 -0
  66. oscura/analyzers/protocols/industrial/opcua/datatypes.py +446 -0
  67. oscura/analyzers/protocols/industrial/opcua/services.py +264 -0
  68. oscura/analyzers/protocols/industrial/profinet/__init__.py +23 -0
  69. oscura/analyzers/protocols/industrial/profinet/analyzer.py +441 -0
  70. oscura/analyzers/protocols/industrial/profinet/dcp.py +263 -0
  71. oscura/analyzers/protocols/industrial/profinet/ptcp.py +200 -0
  72. oscura/analyzers/protocols/jtag.py +180 -98
  73. oscura/analyzers/protocols/lin.py +219 -114
  74. oscura/analyzers/protocols/manchester.py +4 -4
  75. oscura/analyzers/protocols/onewire.py +253 -149
  76. oscura/analyzers/protocols/parallel_bus/__init__.py +20 -0
  77. oscura/analyzers/protocols/parallel_bus/centronics.py +92 -0
  78. oscura/analyzers/protocols/parallel_bus/gpib.py +137 -0
  79. oscura/analyzers/protocols/spi.py +192 -95
  80. oscura/analyzers/protocols/swd.py +321 -167
  81. oscura/analyzers/protocols/uart.py +267 -125
  82. oscura/analyzers/protocols/usb.py +235 -131
  83. oscura/analyzers/side_channel/power.py +17 -12
  84. oscura/analyzers/signal/__init__.py +15 -0
  85. oscura/analyzers/signal/timing_analysis.py +1086 -0
  86. oscura/analyzers/signal_integrity/__init__.py +4 -1
  87. oscura/analyzers/signal_integrity/sparams.py +2 -19
  88. oscura/analyzers/spectral/chunked.py +129 -60
  89. oscura/analyzers/spectral/chunked_fft.py +300 -94
  90. oscura/analyzers/spectral/chunked_wavelet.py +100 -80
  91. oscura/analyzers/statistical/checksum.py +376 -217
  92. oscura/analyzers/statistical/classification.py +229 -107
  93. oscura/analyzers/statistical/entropy.py +78 -53
  94. oscura/analyzers/statistics/correlation.py +407 -211
  95. oscura/analyzers/statistics/outliers.py +2 -2
  96. oscura/analyzers/statistics/streaming.py +30 -5
  97. oscura/analyzers/validation.py +216 -101
  98. oscura/analyzers/waveform/measurements.py +9 -0
  99. oscura/analyzers/waveform/measurements_with_uncertainty.py +31 -15
  100. oscura/analyzers/waveform/spectral.py +500 -228
  101. oscura/api/__init__.py +31 -5
  102. oscura/api/dsl/__init__.py +582 -0
  103. oscura/{dsl → api/dsl}/commands.py +43 -76
  104. oscura/{dsl → api/dsl}/interpreter.py +26 -51
  105. oscura/{dsl → api/dsl}/parser.py +107 -77
  106. oscura/{dsl → api/dsl}/repl.py +2 -2
  107. oscura/api/dsl.py +1 -1
  108. oscura/{integrations → api/integrations}/__init__.py +1 -1
  109. oscura/{integrations → api/integrations}/llm.py +201 -102
  110. oscura/api/operators.py +3 -3
  111. oscura/api/optimization.py +144 -30
  112. oscura/api/rest_server.py +921 -0
  113. oscura/api/server/__init__.py +17 -0
  114. oscura/api/server/dashboard.py +850 -0
  115. oscura/api/server/static/README.md +34 -0
  116. oscura/api/server/templates/base.html +181 -0
  117. oscura/api/server/templates/export.html +120 -0
  118. oscura/api/server/templates/home.html +284 -0
  119. oscura/api/server/templates/protocols.html +58 -0
  120. oscura/api/server/templates/reports.html +43 -0
  121. oscura/api/server/templates/session_detail.html +89 -0
  122. oscura/api/server/templates/sessions.html +83 -0
  123. oscura/api/server/templates/waveforms.html +73 -0
  124. oscura/automotive/__init__.py +8 -1
  125. oscura/automotive/can/__init__.py +10 -0
  126. oscura/automotive/can/checksum.py +3 -1
  127. oscura/automotive/can/dbc_generator.py +590 -0
  128. oscura/automotive/can/message_wrapper.py +121 -74
  129. oscura/automotive/can/patterns.py +98 -21
  130. oscura/automotive/can/session.py +292 -56
  131. oscura/automotive/can/state_machine.py +6 -3
  132. oscura/automotive/can/stimulus_response.py +97 -75
  133. oscura/automotive/dbc/__init__.py +10 -2
  134. oscura/automotive/dbc/generator.py +84 -56
  135. oscura/automotive/dbc/parser.py +6 -6
  136. oscura/automotive/dtc/data.json +2763 -0
  137. oscura/automotive/dtc/database.py +2 -2
  138. oscura/automotive/flexray/__init__.py +31 -0
  139. oscura/automotive/flexray/analyzer.py +504 -0
  140. oscura/automotive/flexray/crc.py +185 -0
  141. oscura/automotive/flexray/fibex.py +449 -0
  142. oscura/automotive/j1939/__init__.py +45 -8
  143. oscura/automotive/j1939/analyzer.py +605 -0
  144. oscura/automotive/j1939/spns.py +326 -0
  145. oscura/automotive/j1939/transport.py +306 -0
  146. oscura/automotive/lin/__init__.py +47 -0
  147. oscura/automotive/lin/analyzer.py +612 -0
  148. oscura/automotive/loaders/blf.py +13 -2
  149. oscura/automotive/loaders/csv_can.py +143 -72
  150. oscura/automotive/loaders/dispatcher.py +50 -2
  151. oscura/automotive/loaders/mdf.py +86 -45
  152. oscura/automotive/loaders/pcap.py +111 -61
  153. oscura/automotive/uds/__init__.py +4 -0
  154. oscura/automotive/uds/analyzer.py +725 -0
  155. oscura/automotive/uds/decoder.py +140 -58
  156. oscura/automotive/uds/models.py +7 -1
  157. oscura/automotive/visualization.py +1 -1
  158. oscura/cli/analyze.py +348 -0
  159. oscura/cli/batch.py +142 -122
  160. oscura/cli/benchmark.py +275 -0
  161. oscura/cli/characterize.py +137 -82
  162. oscura/cli/compare.py +224 -131
  163. oscura/cli/completion.py +250 -0
  164. oscura/cli/config_cmd.py +361 -0
  165. oscura/cli/decode.py +164 -87
  166. oscura/cli/export.py +286 -0
  167. oscura/cli/main.py +115 -31
  168. oscura/{onboarding → cli/onboarding}/__init__.py +3 -3
  169. oscura/{onboarding → cli/onboarding}/help.py +80 -58
  170. oscura/{onboarding → cli/onboarding}/tutorials.py +97 -72
  171. oscura/{onboarding → cli/onboarding}/wizard.py +55 -36
  172. oscura/cli/progress.py +147 -0
  173. oscura/cli/shell.py +157 -135
  174. oscura/cli/validate_cmd.py +204 -0
  175. oscura/cli/visualize.py +158 -0
  176. oscura/convenience.py +125 -79
  177. oscura/core/__init__.py +4 -2
  178. oscura/core/backend_selector.py +3 -3
  179. oscura/core/cache.py +126 -15
  180. oscura/core/cancellation.py +1 -1
  181. oscura/{config → core/config}/__init__.py +20 -11
  182. oscura/{config → core/config}/defaults.py +1 -1
  183. oscura/{config → core/config}/loader.py +7 -5
  184. oscura/{config → core/config}/memory.py +5 -5
  185. oscura/{config → core/config}/migration.py +1 -1
  186. oscura/{config → core/config}/pipeline.py +99 -23
  187. oscura/{config → core/config}/preferences.py +1 -1
  188. oscura/{config → core/config}/protocol.py +3 -3
  189. oscura/{config → core/config}/schema.py +426 -272
  190. oscura/{config → core/config}/settings.py +1 -1
  191. oscura/{config → core/config}/thresholds.py +195 -153
  192. oscura/core/correlation.py +5 -6
  193. oscura/core/cross_domain.py +0 -2
  194. oscura/core/debug.py +9 -5
  195. oscura/{extensibility → core/extensibility}/docs.py +158 -70
  196. oscura/{extensibility → core/extensibility}/extensions.py +160 -76
  197. oscura/{extensibility → core/extensibility}/logging.py +1 -1
  198. oscura/{extensibility → core/extensibility}/measurements.py +1 -1
  199. oscura/{extensibility → core/extensibility}/plugins.py +1 -1
  200. oscura/{extensibility → core/extensibility}/templates.py +73 -3
  201. oscura/{extensibility → core/extensibility}/validation.py +1 -1
  202. oscura/core/gpu_backend.py +11 -7
  203. oscura/core/log_query.py +101 -11
  204. oscura/core/logging.py +126 -54
  205. oscura/core/logging_advanced.py +5 -5
  206. oscura/core/memory_limits.py +108 -70
  207. oscura/core/memory_monitor.py +2 -2
  208. oscura/core/memory_progress.py +7 -7
  209. oscura/core/memory_warnings.py +1 -1
  210. oscura/core/numba_backend.py +13 -13
  211. oscura/{plugins → core/plugins}/__init__.py +9 -9
  212. oscura/{plugins → core/plugins}/base.py +7 -7
  213. oscura/{plugins → core/plugins}/cli.py +3 -3
  214. oscura/{plugins → core/plugins}/discovery.py +186 -106
  215. oscura/{plugins → core/plugins}/lifecycle.py +1 -1
  216. oscura/{plugins → core/plugins}/manager.py +7 -7
  217. oscura/{plugins → core/plugins}/registry.py +3 -3
  218. oscura/{plugins → core/plugins}/versioning.py +1 -1
  219. oscura/core/progress.py +16 -1
  220. oscura/core/provenance.py +8 -2
  221. oscura/{schemas → core/schemas}/__init__.py +2 -2
  222. oscura/core/schemas/bus_configuration.json +322 -0
  223. oscura/core/schemas/device_mapping.json +182 -0
  224. oscura/core/schemas/packet_format.json +418 -0
  225. oscura/core/schemas/protocol_definition.json +363 -0
  226. oscura/core/types.py +4 -0
  227. oscura/core/uncertainty.py +3 -3
  228. oscura/correlation/__init__.py +52 -0
  229. oscura/correlation/multi_protocol.py +811 -0
  230. oscura/discovery/auto_decoder.py +117 -35
  231. oscura/discovery/comparison.py +191 -86
  232. oscura/discovery/quality_validator.py +155 -68
  233. oscura/discovery/signal_detector.py +196 -79
  234. oscura/export/__init__.py +18 -20
  235. oscura/export/kaitai_struct.py +513 -0
  236. oscura/export/scapy_layer.py +801 -0
  237. oscura/export/wireshark/README.md +15 -15
  238. oscura/export/wireshark/generator.py +1 -1
  239. oscura/export/wireshark/templates/dissector.lua.j2 +2 -2
  240. oscura/export/wireshark_dissector.py +746 -0
  241. oscura/guidance/wizard.py +207 -111
  242. oscura/hardware/__init__.py +19 -0
  243. oscura/{acquisition → hardware/acquisition}/__init__.py +4 -4
  244. oscura/{acquisition → hardware/acquisition}/file.py +2 -2
  245. oscura/{acquisition → hardware/acquisition}/hardware.py +7 -7
  246. oscura/{acquisition → hardware/acquisition}/saleae.py +15 -12
  247. oscura/{acquisition → hardware/acquisition}/socketcan.py +1 -1
  248. oscura/{acquisition → hardware/acquisition}/streaming.py +2 -2
  249. oscura/{acquisition → hardware/acquisition}/synthetic.py +3 -3
  250. oscura/{acquisition → hardware/acquisition}/visa.py +33 -11
  251. oscura/hardware/firmware/__init__.py +29 -0
  252. oscura/hardware/firmware/pattern_recognition.py +874 -0
  253. oscura/hardware/hal_detector.py +736 -0
  254. oscura/hardware/security/__init__.py +37 -0
  255. oscura/hardware/security/side_channel_detector.py +1126 -0
  256. oscura/inference/__init__.py +4 -0
  257. oscura/inference/active_learning/README.md +7 -7
  258. oscura/inference/active_learning/observation_table.py +4 -1
  259. oscura/inference/alignment.py +216 -123
  260. oscura/inference/bayesian.py +113 -33
  261. oscura/inference/crc_reverse.py +101 -55
  262. oscura/inference/logic.py +6 -2
  263. oscura/inference/message_format.py +342 -183
  264. oscura/inference/protocol.py +95 -44
  265. oscura/inference/protocol_dsl.py +180 -82
  266. oscura/inference/signal_intelligence.py +1439 -706
  267. oscura/inference/spectral.py +99 -57
  268. oscura/inference/state_machine.py +810 -158
  269. oscura/inference/stream.py +270 -110
  270. oscura/iot/__init__.py +34 -0
  271. oscura/iot/coap/__init__.py +32 -0
  272. oscura/iot/coap/analyzer.py +668 -0
  273. oscura/iot/coap/options.py +212 -0
  274. oscura/iot/lorawan/__init__.py +21 -0
  275. oscura/iot/lorawan/crypto.py +206 -0
  276. oscura/iot/lorawan/decoder.py +801 -0
  277. oscura/iot/lorawan/mac_commands.py +341 -0
  278. oscura/iot/mqtt/__init__.py +27 -0
  279. oscura/iot/mqtt/analyzer.py +999 -0
  280. oscura/iot/mqtt/properties.py +315 -0
  281. oscura/iot/zigbee/__init__.py +31 -0
  282. oscura/iot/zigbee/analyzer.py +615 -0
  283. oscura/iot/zigbee/security.py +153 -0
  284. oscura/iot/zigbee/zcl.py +349 -0
  285. oscura/jupyter/display.py +125 -45
  286. oscura/{exploratory → jupyter/exploratory}/__init__.py +8 -8
  287. oscura/{exploratory → jupyter/exploratory}/error_recovery.py +298 -141
  288. oscura/jupyter/exploratory/fuzzy.py +746 -0
  289. oscura/{exploratory → jupyter/exploratory}/fuzzy_advanced.py +258 -100
  290. oscura/{exploratory → jupyter/exploratory}/legacy.py +464 -242
  291. oscura/{exploratory → jupyter/exploratory}/parse.py +167 -145
  292. oscura/{exploratory → jupyter/exploratory}/recovery.py +119 -87
  293. oscura/jupyter/exploratory/sync.py +612 -0
  294. oscura/{exploratory → jupyter/exploratory}/unknown.py +299 -176
  295. oscura/jupyter/magic.py +4 -4
  296. oscura/{ui → jupyter/ui}/__init__.py +2 -2
  297. oscura/{ui → jupyter/ui}/formatters.py +3 -3
  298. oscura/{ui → jupyter/ui}/progressive_display.py +153 -82
  299. oscura/loaders/__init__.py +171 -63
  300. oscura/loaders/binary.py +88 -1
  301. oscura/loaders/chipwhisperer.py +153 -137
  302. oscura/loaders/configurable.py +208 -86
  303. oscura/loaders/csv_loader.py +458 -215
  304. oscura/loaders/hdf5_loader.py +278 -119
  305. oscura/loaders/lazy.py +87 -54
  306. oscura/loaders/mmap_loader.py +1 -1
  307. oscura/loaders/numpy_loader.py +253 -116
  308. oscura/loaders/pcap.py +226 -151
  309. oscura/loaders/rigol.py +110 -49
  310. oscura/loaders/sigrok.py +201 -78
  311. oscura/loaders/tdms.py +81 -58
  312. oscura/loaders/tektronix.py +291 -174
  313. oscura/loaders/touchstone.py +182 -87
  314. oscura/loaders/vcd.py +215 -117
  315. oscura/loaders/wav.py +155 -68
  316. oscura/reporting/__init__.py +9 -7
  317. oscura/reporting/analyze.py +352 -146
  318. oscura/reporting/argument_preparer.py +69 -14
  319. oscura/reporting/auto_report.py +97 -61
  320. oscura/reporting/batch.py +131 -58
  321. oscura/reporting/chart_selection.py +57 -45
  322. oscura/reporting/comparison.py +63 -17
  323. oscura/reporting/content/executive.py +76 -24
  324. oscura/reporting/core_formats/multi_format.py +11 -8
  325. oscura/reporting/engine.py +312 -158
  326. oscura/reporting/enhanced_reports.py +949 -0
  327. oscura/reporting/export.py +86 -43
  328. oscura/reporting/formatting/numbers.py +69 -42
  329. oscura/reporting/html.py +139 -58
  330. oscura/reporting/index.py +137 -65
  331. oscura/reporting/output.py +158 -67
  332. oscura/reporting/pdf.py +67 -102
  333. oscura/reporting/plots.py +191 -112
  334. oscura/reporting/sections.py +88 -47
  335. oscura/reporting/standards.py +104 -61
  336. oscura/reporting/summary_generator.py +75 -55
  337. oscura/reporting/tables.py +138 -54
  338. oscura/reporting/templates/enhanced/protocol_re.html +525 -0
  339. oscura/reporting/templates/index.md +13 -13
  340. oscura/sessions/__init__.py +14 -23
  341. oscura/sessions/base.py +3 -3
  342. oscura/sessions/blackbox.py +106 -10
  343. oscura/sessions/generic.py +2 -2
  344. oscura/sessions/legacy.py +783 -0
  345. oscura/side_channel/__init__.py +63 -0
  346. oscura/side_channel/dpa.py +1025 -0
  347. oscura/utils/__init__.py +15 -1
  348. oscura/utils/autodetect.py +1 -5
  349. oscura/utils/bitwise.py +118 -0
  350. oscura/{builders → utils/builders}/__init__.py +1 -1
  351. oscura/{comparison → utils/comparison}/__init__.py +6 -6
  352. oscura/{comparison → utils/comparison}/compare.py +202 -101
  353. oscura/{comparison → utils/comparison}/golden.py +83 -63
  354. oscura/{comparison → utils/comparison}/limits.py +313 -89
  355. oscura/{comparison → utils/comparison}/mask.py +151 -45
  356. oscura/{comparison → utils/comparison}/trace_diff.py +1 -1
  357. oscura/{comparison → utils/comparison}/visualization.py +147 -89
  358. oscura/{component → utils/component}/__init__.py +3 -3
  359. oscura/{component → utils/component}/impedance.py +122 -58
  360. oscura/{component → utils/component}/reactive.py +165 -168
  361. oscura/{component → utils/component}/transmission_line.py +3 -3
  362. oscura/{filtering → utils/filtering}/__init__.py +6 -6
  363. oscura/{filtering → utils/filtering}/base.py +1 -1
  364. oscura/{filtering → utils/filtering}/convenience.py +2 -2
  365. oscura/{filtering → utils/filtering}/design.py +169 -93
  366. oscura/{filtering → utils/filtering}/filters.py +2 -2
  367. oscura/{filtering → utils/filtering}/introspection.py +2 -2
  368. oscura/utils/geometry.py +31 -0
  369. oscura/utils/imports.py +184 -0
  370. oscura/utils/lazy.py +1 -1
  371. oscura/{math → utils/math}/__init__.py +2 -2
  372. oscura/{math → utils/math}/arithmetic.py +114 -48
  373. oscura/{math → utils/math}/interpolation.py +139 -106
  374. oscura/utils/memory.py +129 -66
  375. oscura/utils/memory_advanced.py +92 -9
  376. oscura/utils/memory_extensions.py +10 -8
  377. oscura/{optimization → utils/optimization}/__init__.py +1 -1
  378. oscura/{optimization → utils/optimization}/search.py +2 -2
  379. oscura/utils/performance/__init__.py +58 -0
  380. oscura/utils/performance/caching.py +889 -0
  381. oscura/utils/performance/lsh_clustering.py +333 -0
  382. oscura/utils/performance/memory_optimizer.py +699 -0
  383. oscura/utils/performance/optimizations.py +675 -0
  384. oscura/utils/performance/parallel.py +654 -0
  385. oscura/utils/performance/profiling.py +661 -0
  386. oscura/{pipeline → utils/pipeline}/base.py +1 -1
  387. oscura/{pipeline → utils/pipeline}/composition.py +11 -3
  388. oscura/{pipeline → utils/pipeline}/parallel.py +3 -2
  389. oscura/{pipeline → utils/pipeline}/pipeline.py +1 -1
  390. oscura/{pipeline → utils/pipeline}/reverse_engineering.py +412 -221
  391. oscura/{search → utils/search}/__init__.py +3 -3
  392. oscura/{search → utils/search}/anomaly.py +188 -58
  393. oscura/utils/search/context.py +294 -0
  394. oscura/{search → utils/search}/pattern.py +138 -10
  395. oscura/utils/serial.py +51 -0
  396. oscura/utils/storage/__init__.py +61 -0
  397. oscura/utils/storage/database.py +1166 -0
  398. oscura/{streaming → utils/streaming}/chunked.py +302 -143
  399. oscura/{streaming → utils/streaming}/progressive.py +1 -1
  400. oscura/{streaming → utils/streaming}/realtime.py +3 -2
  401. oscura/{triggering → utils/triggering}/__init__.py +6 -6
  402. oscura/{triggering → utils/triggering}/base.py +6 -6
  403. oscura/{triggering → utils/triggering}/edge.py +2 -2
  404. oscura/{triggering → utils/triggering}/pattern.py +2 -2
  405. oscura/{triggering → utils/triggering}/pulse.py +115 -74
  406. oscura/{triggering → utils/triggering}/window.py +2 -2
  407. oscura/utils/validation.py +32 -0
  408. oscura/validation/__init__.py +121 -0
  409. oscura/{compliance → validation/compliance}/__init__.py +5 -5
  410. oscura/{compliance → validation/compliance}/advanced.py +5 -5
  411. oscura/{compliance → validation/compliance}/masks.py +1 -1
  412. oscura/{compliance → validation/compliance}/reporting.py +127 -53
  413. oscura/{compliance → validation/compliance}/testing.py +114 -52
  414. oscura/validation/compliance_tests.py +915 -0
  415. oscura/validation/fuzzer.py +990 -0
  416. oscura/validation/grammar_tests.py +596 -0
  417. oscura/validation/grammar_validator.py +904 -0
  418. oscura/validation/hil_testing.py +977 -0
  419. oscura/{quality → validation/quality}/__init__.py +4 -4
  420. oscura/{quality → validation/quality}/ensemble.py +251 -171
  421. oscura/{quality → validation/quality}/explainer.py +3 -3
  422. oscura/{quality → validation/quality}/scoring.py +1 -1
  423. oscura/{quality → validation/quality}/warnings.py +4 -4
  424. oscura/validation/regression_suite.py +808 -0
  425. oscura/validation/replay.py +788 -0
  426. oscura/{testing → validation/testing}/__init__.py +2 -2
  427. oscura/{testing → validation/testing}/synthetic.py +5 -5
  428. oscura/visualization/__init__.py +9 -0
  429. oscura/visualization/accessibility.py +1 -1
  430. oscura/visualization/annotations.py +64 -67
  431. oscura/visualization/colors.py +7 -7
  432. oscura/visualization/digital.py +180 -81
  433. oscura/visualization/eye.py +236 -85
  434. oscura/visualization/interactive.py +320 -143
  435. oscura/visualization/jitter.py +587 -247
  436. oscura/visualization/layout.py +169 -134
  437. oscura/visualization/optimization.py +103 -52
  438. oscura/visualization/palettes.py +1 -1
  439. oscura/visualization/power.py +427 -211
  440. oscura/visualization/power_extended.py +626 -297
  441. oscura/visualization/presets.py +2 -0
  442. oscura/visualization/protocols.py +495 -181
  443. oscura/visualization/render.py +79 -63
  444. oscura/visualization/reverse_engineering.py +171 -124
  445. oscura/visualization/signal_integrity.py +460 -279
  446. oscura/visualization/specialized.py +190 -100
  447. oscura/visualization/spectral.py +670 -255
  448. oscura/visualization/thumbnails.py +166 -137
  449. oscura/visualization/waveform.py +150 -63
  450. oscura/workflows/__init__.py +3 -0
  451. oscura/{batch → workflows/batch}/__init__.py +5 -5
  452. oscura/{batch → workflows/batch}/advanced.py +150 -75
  453. oscura/workflows/batch/aggregate.py +531 -0
  454. oscura/workflows/batch/analyze.py +236 -0
  455. oscura/{batch → workflows/batch}/logging.py +2 -2
  456. oscura/{batch → workflows/batch}/metrics.py +1 -1
  457. oscura/workflows/complete_re.py +1144 -0
  458. oscura/workflows/compliance.py +44 -54
  459. oscura/workflows/digital.py +197 -51
  460. oscura/workflows/legacy/__init__.py +12 -0
  461. oscura/{workflow → workflows/legacy}/dag.py +4 -1
  462. oscura/workflows/multi_trace.py +9 -9
  463. oscura/workflows/power.py +42 -62
  464. oscura/workflows/protocol.py +82 -49
  465. oscura/workflows/reverse_engineering.py +351 -150
  466. oscura/workflows/signal_integrity.py +157 -82
  467. oscura-0.6.0.dist-info/METADATA +643 -0
  468. oscura-0.6.0.dist-info/RECORD +590 -0
  469. oscura/analyzers/digital/ic_database.py +0 -498
  470. oscura/analyzers/digital/timing_paths.py +0 -339
  471. oscura/analyzers/digital/vintage.py +0 -377
  472. oscura/analyzers/digital/vintage_result.py +0 -148
  473. oscura/analyzers/protocols/parallel_bus.py +0 -449
  474. oscura/batch/aggregate.py +0 -300
  475. oscura/batch/analyze.py +0 -139
  476. oscura/dsl/__init__.py +0 -73
  477. oscura/exceptions.py +0 -59
  478. oscura/exploratory/fuzzy.py +0 -513
  479. oscura/exploratory/sync.py +0 -384
  480. oscura/export/wavedrom.py +0 -430
  481. oscura/exporters/__init__.py +0 -94
  482. oscura/exporters/csv.py +0 -303
  483. oscura/exporters/exporters.py +0 -44
  484. oscura/exporters/hdf5.py +0 -217
  485. oscura/exporters/html_export.py +0 -701
  486. oscura/exporters/json_export.py +0 -338
  487. oscura/exporters/markdown_export.py +0 -367
  488. oscura/exporters/matlab_export.py +0 -354
  489. oscura/exporters/npz_export.py +0 -219
  490. oscura/exporters/spice_export.py +0 -210
  491. oscura/exporters/vintage_logic_csv.py +0 -247
  492. oscura/reporting/vintage_logic_report.py +0 -523
  493. oscura/search/context.py +0 -149
  494. oscura/session/__init__.py +0 -34
  495. oscura/session/annotations.py +0 -289
  496. oscura/session/history.py +0 -313
  497. oscura/session/session.py +0 -520
  498. oscura/visualization/digital_advanced.py +0 -718
  499. oscura/visualization/figure_manager.py +0 -156
  500. oscura/workflow/__init__.py +0 -13
  501. oscura-0.5.0.dist-info/METADATA +0 -407
  502. oscura-0.5.0.dist-info/RECORD +0 -486
  503. /oscura/core/{config.py → config/legacy.py} +0 -0
  504. /oscura/{extensibility → core/extensibility}/__init__.py +0 -0
  505. /oscura/{extensibility → core/extensibility}/registry.py +0 -0
  506. /oscura/{plugins → core/plugins}/isolation.py +0 -0
  507. /oscura/{builders → utils/builders}/signal_builder.py +0 -0
  508. /oscura/{optimization → utils/optimization}/parallel.py +0 -0
  509. /oscura/{pipeline → utils/pipeline}/__init__.py +0 -0
  510. /oscura/{streaming → utils/streaming}/__init__.py +0 -0
  511. {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/WHEEL +0 -0
  512. {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/entry_points.txt +0 -0
  513. {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/licenses/LICENSE +0 -0
@@ -4,7 +4,7 @@ This module provides compliance report generation in multiple formats.
4
4
 
5
5
 
6
6
  Example:
7
- >>> from oscura.compliance import test_compliance, generate_compliance_report
7
+ >>> from oscura.validation.compliance import test_compliance, generate_compliance_report
8
8
  >>> result = test_compliance(trace, mask)
9
9
  >>> generate_compliance_report(result, 'report.html')
10
10
 
@@ -20,7 +20,7 @@ from pathlib import Path
20
20
  from typing import TYPE_CHECKING
21
21
 
22
22
  if TYPE_CHECKING:
23
- from oscura.compliance.testing import ComplianceResult
23
+ from oscura.validation.compliance.testing import ComplianceResult
24
24
 
25
25
 
26
26
  class ComplianceReportFormat(Enum):
@@ -110,37 +110,43 @@ def generate_compliance_report(
110
110
  return output_path
111
111
 
112
112
 
113
- def _generate_html_report(
114
- result: ComplianceResult,
115
- output_path: Path,
116
- *,
117
- include_plot: bool = True,
118
- title: str | None = None,
119
- company_name: str | None = None,
120
- dut_info: dict[str, str] | None = None,
121
- ) -> None:
122
- """Generate HTML compliance report."""
123
- title = title or "EMC Compliance Report"
124
- status_color = "#28a745" if result.passed else "#dc3545"
125
- status_text = "PASS" if result.passed else "FAIL"
113
+ def _generate_dut_section_html(dut_info: dict[str, str] | None) -> str:
114
+ """Generate DUT information section HTML.
126
115
 
127
- # Build DUT info section
128
- dut_section = ""
129
- if dut_info:
130
- dut_rows = "".join(f"<tr><td>{k}</td><td>{v}</td></tr>" for k, v in dut_info.items())
131
- dut_section = f"""
116
+ Args:
117
+ dut_info: Device Under Test information dict.
118
+
119
+ Returns:
120
+ HTML string for DUT section.
121
+ """
122
+ if not dut_info:
123
+ return ""
124
+
125
+ dut_rows = "".join(f"<tr><td>{k}</td><td>{v}</td></tr>" for k, v in dut_info.items())
126
+ return f"""
132
127
  <h3>Device Under Test</h3>
133
128
  <table class="info-table">
134
129
  {dut_rows}
135
130
  </table>
136
131
  """
137
- # Build violations table
138
- violations_section = ""
139
- if result.violations:
140
- violation_rows = ""
141
- for v in result.violations:
142
- freq_mhz = v.frequency / 1e6
143
- violation_rows += f"""
132
+
133
+
134
+ def _generate_violations_section_html(result: ComplianceResult) -> str:
135
+ """Generate violations table section HTML.
136
+
137
+ Args:
138
+ result: ComplianceResult with violations list.
139
+
140
+ Returns:
141
+ HTML string for violations section.
142
+ """
143
+ if not result.violations:
144
+ return ""
145
+
146
+ violation_rows = ""
147
+ for v in result.violations:
148
+ freq_mhz = v.frequency / 1e6
149
+ violation_rows += f"""
144
150
  <tr>
145
151
  <td>{freq_mhz:.3f}</td>
146
152
  <td>{v.measured_level:.1f}</td>
@@ -148,7 +154,8 @@ def _generate_html_report(
148
154
  <td style="color: red;">{v.excess_db:.1f}</td>
149
155
  </tr>
150
156
  """
151
- violations_section = f"""
157
+
158
+ return f"""
152
159
  <h3>Violations ({len(result.violations)})</h3>
153
160
  <table class="data-table">
154
161
  <thead>
@@ -164,22 +171,18 @@ def _generate_html_report(
164
171
  </tbody>
165
172
  </table>
166
173
  """
167
- # Build plot section
168
- plot_section = ""
169
- if include_plot and len(result.spectrum_freq) > 0:
170
- plot_section = _generate_plot_html(result)
171
174
 
172
- # Company header
173
- company_header = ""
174
- if company_name:
175
- company_header = f"<div class='company-name'>{company_name}</div>"
176
175
 
177
- html = f"""<!DOCTYPE html>
178
- <html>
179
- <head>
180
- <meta charset="UTF-8">
181
- <title>{title}</title>
182
- <style>
176
+ def _generate_report_css(status_color: str) -> str:
177
+ """Generate CSS stylesheet for compliance report.
178
+
179
+ Args:
180
+ status_color: Badge color for pass/fail status.
181
+
182
+ Returns:
183
+ CSS stylesheet string.
184
+ """
185
+ return f"""
183
186
  body {{
184
187
  font-family: 'Segoe UI', Arial, sans-serif;
185
188
  max-width: 1200px;
@@ -258,14 +261,19 @@ def _generate_html_report(
258
261
  color: #666;
259
262
  font-size: 12px;
260
263
  }}
261
- </style>
262
- </head>
263
- <body>
264
- {company_header}
265
- <h1>{title}</h1>
264
+ """
266
265
 
267
- <div class="status-badge">{status_text}</div>
268
266
 
267
+ def _generate_summary_grid_html(result: ComplianceResult) -> str:
268
+ """Generate summary grid HTML.
269
+
270
+ Args:
271
+ result: ComplianceResult with summary metrics.
272
+
273
+ Returns:
274
+ HTML string for summary grid.
275
+ """
276
+ return f"""
269
277
  <h2>Test Summary</h2>
270
278
  <div class="summary-grid">
271
279
  <div class="summary-card">
@@ -285,18 +293,84 @@ def _generate_html_report(
285
293
  <div class="value">{len(result.violations)}</div>
286
294
  </div>
287
295
  </div>
296
+ """
288
297
 
289
- {dut_section}
290
298
 
291
- {violations_section}
299
+ def _generate_footer_html(result: ComplianceResult) -> str:
300
+ """Generate footer HTML.
292
301
 
293
- {plot_section}
302
+ Args:
303
+ result: ComplianceResult with metadata.
294
304
 
305
+ Returns:
306
+ HTML string for footer.
307
+ """
308
+ return f"""
295
309
  <div class="footer">
296
310
  <p>Report generated: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}</p>
297
311
  <p>Detector: {result.detector} | Distance: {result.metadata.get("distance", "N/A")}m</p>
298
312
  <p>Generated by Oscura EMC Compliance Module</p>
299
313
  </div>
314
+ """
315
+
316
+
317
+ def _generate_html_report(
318
+ result: ComplianceResult,
319
+ output_path: Path,
320
+ *,
321
+ include_plot: bool = True,
322
+ title: str | None = None,
323
+ company_name: str | None = None,
324
+ dut_info: dict[str, str] | None = None,
325
+ ) -> None:
326
+ """Generate HTML compliance report.
327
+
328
+ Args:
329
+ result: ComplianceResult from test_compliance().
330
+ output_path: Output file path.
331
+ include_plot: Include spectrum/limit plot in report.
332
+ title: Report title (default: "EMC Compliance Report").
333
+ company_name: Company name for header.
334
+ dut_info: Device Under Test information dict.
335
+ """
336
+ title = title or "EMC Compliance Report"
337
+ status_color = "#28a745" if result.passed else "#dc3545"
338
+ status_text = "PASS" if result.passed else "FAIL"
339
+
340
+ # Build sections
341
+ dut_section = _generate_dut_section_html(dut_info)
342
+ violations_section = _generate_violations_section_html(result)
343
+
344
+ plot_section = ""
345
+ if include_plot and len(result.spectrum_freq) > 0:
346
+ plot_section = _generate_plot_html(result)
347
+
348
+ company_header = f"<div class='company-name'>{company_name}</div>" if company_name else ""
349
+
350
+ html = f"""<!DOCTYPE html>
351
+ <html>
352
+ <head>
353
+ <meta charset="UTF-8">
354
+ <title>{title}</title>
355
+ <style>
356
+ {_generate_report_css(status_color)}
357
+ </style>
358
+ </head>
359
+ <body>
360
+ {company_header}
361
+ <h1>{title}</h1>
362
+
363
+ <div class="status-badge">{status_text}</div>
364
+
365
+ {_generate_summary_grid_html(result)}
366
+
367
+ {dut_section}
368
+
369
+ {violations_section}
370
+
371
+ {plot_section}
372
+
373
+ {_generate_footer_html(result)}
300
374
  </body>
301
375
  </html>
302
376
  """
@@ -326,7 +400,7 @@ def _generate_plot_html(result: ComplianceResult) -> str:
326
400
  width - 2 * padding
327
401
  )
328
402
 
329
- def y_scale(l: float) -> float: # noqa: E741
403
+ def y_scale(l: float) -> float:
330
404
  return height - padding - (l - level_min) / (level_max - level_min) * (height - 2 * padding) # type: ignore[no-any-return]
331
405
 
332
406
  # Build spectrum path (downsample for SVG)
@@ -466,7 +540,7 @@ def _convert_html_to_pdf(html_path: Path, pdf_path: Path) -> None:
466
540
  """Convert HTML to PDF using available tools."""
467
541
  try:
468
542
  # Try weasyprint first
469
- from weasyprint import HTML # type: ignore[import-not-found]
543
+ from weasyprint import HTML
470
544
 
471
545
  HTML(str(html_path)).write_pdf(str(pdf_path))
472
546
  except ImportError:
@@ -4,7 +4,7 @@ This module provides compliance testing against regulatory limit masks.
4
4
 
5
5
 
6
6
  Example:
7
- >>> from oscura.compliance import load_limit_mask, test_compliance
7
+ >>> from oscura.validation.compliance import load_limit_mask, test_compliance
8
8
  >>> mask = load_limit_mask('FCC_Part15_ClassB')
9
9
  >>> result = test_compliance(trace, mask)
10
10
  >>> print(f"Status: {result.status}")
@@ -25,8 +25,8 @@ import numpy as np
25
25
  if TYPE_CHECKING:
26
26
  from numpy.typing import NDArray
27
27
 
28
- from oscura.compliance.masks import LimitMask
29
28
  from oscura.core.types import WaveformTrace
29
+ from oscura.validation.compliance.masks import LimitMask
30
30
 
31
31
 
32
32
  class DetectorType(Enum):
@@ -127,39 +127,23 @@ class ComplianceResult:
127
127
  return "\n".join(lines)
128
128
 
129
129
 
130
- def check_compliance(
130
+ def _prepare_spectrum(
131
131
  trace_or_spectrum: WaveformTrace | tuple[NDArray[np.float64], NDArray[np.float64]],
132
- mask: LimitMask,
133
- *,
134
- detector: DetectorType | str = DetectorType.PEAK,
135
- frequency_range: tuple[float, float] | None = None,
136
- unit_conversion: str | None = None,
137
- ) -> ComplianceResult:
138
- """Check signal against EMC limit mask.
132
+ detector: DetectorType,
133
+ unit_conversion: str | None,
134
+ ) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
135
+ """Prepare spectrum data from trace or pre-computed spectrum.
139
136
 
140
137
  Args:
141
- trace_or_spectrum: Either a WaveformTrace to analyze, or a tuple of
142
- (frequency_array, magnitude_array) if spectrum already computed.
143
- mask: LimitMask to test against.
144
- detector: Detector type to use ('peak', 'quasi-peak', 'average', 'rms').
145
- frequency_range: Optional (min, max) frequency range to test.
146
- unit_conversion: Optional unit conversion ('V_to_dBuV', 'W_to_dBm', etc.)
138
+ trace_or_spectrum: Either WaveformTrace or (freq, mag) tuple.
139
+ detector: Detector type.
140
+ unit_conversion: Optional unit conversion.
147
141
 
148
142
  Returns:
149
- ComplianceResult with pass/fail status and violation details.
150
-
151
- Example:
152
- >>> mask = load_limit_mask('FCC_Part15_ClassB')
153
- >>> result = check_compliance(trace, mask)
154
- >>> print(result.summary())
143
+ Tuple of (frequency, spectrum_level_dB).
155
144
  """
156
145
  from oscura.core.types import WaveformTrace
157
146
 
158
- # Handle detector type
159
- if isinstance(detector, str):
160
- detector = DetectorType(detector.lower().replace("-", "_").replace(" ", "_"))
161
-
162
- # Get spectrum
163
147
  if isinstance(trace_or_spectrum, WaveformTrace):
164
148
  freq, mag = _compute_spectrum(trace_or_spectrum, detector)
165
149
  else:
@@ -167,19 +151,35 @@ def check_compliance(
167
151
 
168
152
  # Convert to dB if needed
169
153
  if unit_conversion == "V_to_dBuV":
170
- # dBuV = 20*log10(V * 1e6)
171
154
  spectrum_level = 20 * np.log10(np.abs(mag) * 1e6 + 1e-12)
172
155
  elif unit_conversion == "W_to_dBm":
173
- # dBm = 10*log10(W * 1000)
174
156
  spectrum_level = 10 * np.log10(np.abs(mag) * 1000 + 1e-12)
175
157
  elif mag.max() > 0 and mag.max() < 10:
176
- # Assume linear voltage, convert to dBuV
177
158
  spectrum_level = 20 * np.log10(np.abs(mag) * 1e6 + 1e-12)
178
159
  else:
179
- # Assume already in dB
180
160
  spectrum_level = mag
181
161
 
182
- # Apply frequency range filter
162
+ return freq, spectrum_level
163
+
164
+
165
+ def _apply_frequency_filters(
166
+ freq: NDArray[np.float64],
167
+ spectrum_level: NDArray[np.float64],
168
+ mask: LimitMask,
169
+ frequency_range: tuple[float, float] | None,
170
+ ) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
171
+ """Apply frequency range filters to spectrum.
172
+
173
+ Args:
174
+ freq: Frequency array.
175
+ spectrum_level: Spectrum level array.
176
+ mask: Limit mask.
177
+ frequency_range: Optional user-specified frequency range.
178
+
179
+ Returns:
180
+ Filtered (freq, spectrum_level) tuple.
181
+ """
182
+ # Apply user frequency range filter
183
183
  if frequency_range is not None:
184
184
  f_min, f_max = frequency_range
185
185
  mask_filter = (freq >= f_min) & (freq <= f_max)
@@ -192,30 +192,31 @@ def check_compliance(
192
192
  freq = freq[in_range]
193
193
  spectrum_level = spectrum_level[in_range]
194
194
 
195
- if len(freq) == 0:
196
- # No data in mask range
197
- return ComplianceResult(
198
- status="PASS",
199
- mask_name=mask.name,
200
- violations=[],
201
- margin_to_limit=np.inf,
202
- worst_frequency=0.0,
203
- worst_margin=np.inf,
204
- spectrum_freq=np.array([]),
205
- spectrum_level=np.array([]),
206
- limit_level=np.array([]),
207
- detector=detector.value,
208
- )
195
+ return freq, spectrum_level
209
196
 
210
- # Interpolate limit to spectrum frequencies
211
- limit_level = mask.interpolate(freq)
212
197
 
213
- # Calculate margin (positive = passing)
214
- margin = limit_level - spectrum_level
198
+ def _find_violations(
199
+ freq: NDArray[np.float64],
200
+ spectrum_level: NDArray[np.float64],
201
+ limit_level: NDArray[np.float64],
202
+ margin: NDArray[np.float64],
203
+ detector: DetectorType,
204
+ ) -> list[ComplianceViolation]:
205
+ """Find compliance violations.
215
206
 
216
- # Find violations
207
+ Args:
208
+ freq: Frequency array.
209
+ spectrum_level: Measured spectrum level.
210
+ limit_level: Limit level.
211
+ margin: Margin to limit (positive = passing).
212
+ detector: Detector type.
213
+
214
+ Returns:
215
+ List of violations.
216
+ """
217
217
  violations: list[ComplianceViolation] = []
218
218
  violation_mask = margin < 0
219
+
219
220
  if np.any(violation_mask):
220
221
  violation_indices = np.where(violation_mask)[0]
221
222
  for idx in violation_indices:
@@ -230,7 +231,68 @@ def check_compliance(
230
231
  )
231
232
  )
232
233
 
233
- # Overall results
234
+ return violations
235
+
236
+
237
+ def check_compliance(
238
+ trace_or_spectrum: WaveformTrace | tuple[NDArray[np.float64], NDArray[np.float64]],
239
+ mask: LimitMask,
240
+ *,
241
+ detector: DetectorType | str = DetectorType.PEAK,
242
+ frequency_range: tuple[float, float] | None = None,
243
+ unit_conversion: str | None = None,
244
+ ) -> ComplianceResult:
245
+ """Check signal against EMC limit mask.
246
+
247
+ Args:
248
+ trace_or_spectrum: Either a WaveformTrace to analyze, or a tuple of
249
+ (frequency_array, magnitude_array) if spectrum already computed.
250
+ mask: LimitMask to test against.
251
+ detector: Detector type to use ('peak', 'quasi-peak', 'average', 'rms').
252
+ frequency_range: Optional (min, max) frequency range to test.
253
+ unit_conversion: Optional unit conversion ('V_to_dBuV', 'W_to_dBm', etc.)
254
+
255
+ Returns:
256
+ ComplianceResult with pass/fail status and violation details.
257
+
258
+ Example:
259
+ >>> mask = load_limit_mask('FCC_Part15_ClassB')
260
+ >>> result = check_compliance(trace, mask)
261
+ >>> print(result.summary())
262
+ """
263
+ # Handle detector type
264
+ if isinstance(detector, str):
265
+ detector = DetectorType(detector.lower().replace("-", "_").replace(" ", "_"))
266
+
267
+ # Prepare spectrum
268
+ freq, spectrum_level = _prepare_spectrum(trace_or_spectrum, detector, unit_conversion)
269
+
270
+ # Apply frequency filters
271
+ freq, spectrum_level = _apply_frequency_filters(freq, spectrum_level, mask, frequency_range)
272
+
273
+ # Handle empty frequency range
274
+ if len(freq) == 0:
275
+ return ComplianceResult(
276
+ status="PASS",
277
+ mask_name=mask.name,
278
+ violations=[],
279
+ margin_to_limit=np.inf,
280
+ worst_frequency=0.0,
281
+ worst_margin=np.inf,
282
+ spectrum_freq=np.array([]),
283
+ spectrum_level=np.array([]),
284
+ limit_level=np.array([]),
285
+ detector=detector.value,
286
+ )
287
+
288
+ # Interpolate limit and calculate margin
289
+ limit_level = mask.interpolate(freq)
290
+ margin = limit_level - spectrum_level
291
+
292
+ # Find violations
293
+ violations = _find_violations(freq, spectrum_level, limit_level, margin, detector)
294
+
295
+ # Compute overall results
234
296
  status = "FAIL" if violations else "PASS"
235
297
  margin_to_limit = float(np.min(margin))
236
298
  worst_idx = int(np.argmin(margin))