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
@@ -0,0 +1,699 @@
1
+ """Memory optimization for large signal file processing.
2
+
3
+ This module provides memory management utilities for processing huge datasets
4
+ efficiently without running out of RAM. Implements memory-mapped I/O, streaming
5
+ analysis, adaptive chunking, and memory leak detection.
6
+
7
+ Key Features:
8
+ - Memory-mapped file I/O for huge datasets (via numpy.memmap)
9
+ - Streaming iterators for chunk-by-chunk processing
10
+ - Lazy loading (load data only when accessed)
11
+ - Adaptive chunking based on available memory
12
+ - Real-time memory usage tracking and leak detection
13
+ - Object pooling for frequently allocated objects
14
+ - In-memory compression for infrequently accessed data
15
+
16
+ Example:
17
+ >>> from oscura.utils.performance.memory_optimizer import MemoryOptimizer
18
+ >>>
19
+ >>> # Optimize memory usage for large file
20
+ >>> optimizer = MemoryOptimizer(max_memory_mb=1024)
21
+ >>> trace = optimizer.load_optimized("huge_file.npy", sample_rate=1e9)
22
+ >>>
23
+ >>> # Process in chunks with streaming
24
+ >>> processor = optimizer.create_stream_processor(
25
+ ... trace, chunk_size=1_000_000, overlap=0
26
+ ... )
27
+ >>> for chunk in processor:
28
+ ... result = analyze(chunk)
29
+ >>>
30
+ >>> # Check memory statistics
31
+ >>> stats = optimizer.get_memory_stats()
32
+ >>> print(f"Peak memory: {stats.peak_memory_mb:.1f} MB")
33
+ >>> if stats.leak_detected:
34
+ ... print("Warning: Memory leak detected!")
35
+
36
+ References:
37
+ Phase 5 Feature 42: Memory Optimization (v0.6.0)
38
+ Streaming APIs: src/oscura/streaming/chunked.py
39
+ Memory-mapped loading: src/oscura/loaders/mmap_loader.py
40
+ """
41
+
42
+ from __future__ import annotations
43
+
44
+ import gc
45
+ from collections.abc import Iterator
46
+ from dataclasses import dataclass
47
+ from enum import Enum
48
+ from pathlib import Path
49
+ from typing import TYPE_CHECKING, Any
50
+
51
+ import numpy as np
52
+ import psutil
53
+
54
+ if TYPE_CHECKING:
55
+ from os import PathLike
56
+
57
+ from numpy.typing import DTypeLike, NDArray
58
+
59
+
60
+ class ChunkingStrategy(Enum):
61
+ """Chunking strategy for data processing."""
62
+
63
+ FIXED = "fixed" # Fixed-size chunks
64
+ SLIDING = "sliding" # Sliding window with overlap
65
+ ADAPTIVE = "adaptive" # Adaptive chunk size based on memory
66
+ TIME_BASED = "time_based" # Chunk by time duration
67
+
68
+
69
+ @dataclass
70
+ class ChunkingConfig:
71
+ """Configuration for data chunking strategy.
72
+
73
+ Attributes:
74
+ strategy: Chunking strategy to use.
75
+ chunk_size: Size of each chunk in samples (for fixed/sliding).
76
+ overlap: Number of samples to overlap between chunks.
77
+ adaptive: Whether to adaptively adjust chunk size based on memory.
78
+ time_window: Time duration for time-based chunking (seconds).
79
+ min_chunk_size: Minimum chunk size in samples (for adaptive).
80
+ max_chunk_size: Maximum chunk size in samples (for adaptive).
81
+
82
+ Example:
83
+ >>> # Fixed-size chunks
84
+ >>> config = ChunkingConfig(
85
+ ... strategy=ChunkingStrategy.FIXED,
86
+ ... chunk_size=1_000_000
87
+ ... )
88
+ >>>
89
+ >>> # Adaptive chunking
90
+ >>> config = ChunkingConfig(
91
+ ... strategy=ChunkingStrategy.ADAPTIVE,
92
+ ... adaptive=True,
93
+ ... min_chunk_size=100_000,
94
+ ... max_chunk_size=10_000_000
95
+ ... )
96
+ """
97
+
98
+ strategy: ChunkingStrategy = ChunkingStrategy.FIXED
99
+ chunk_size: int = 1_000_000
100
+ overlap: int = 0
101
+ adaptive: bool = False
102
+ time_window: float | None = None
103
+ min_chunk_size: int = 100_000
104
+ max_chunk_size: int = 10_000_000
105
+
106
+ def __post_init__(self) -> None:
107
+ """Validate configuration."""
108
+ if self.chunk_size <= 0:
109
+ raise ValueError(f"chunk_size must be positive, got {self.chunk_size}")
110
+ if self.overlap < 0:
111
+ raise ValueError(f"overlap must be non-negative, got {self.overlap}")
112
+ if self.overlap >= self.chunk_size:
113
+ raise ValueError(
114
+ f"overlap ({self.overlap}) must be less than chunk_size ({self.chunk_size})"
115
+ )
116
+ if self.min_chunk_size <= 0:
117
+ raise ValueError(f"min_chunk_size must be positive, got {self.min_chunk_size}")
118
+ if self.max_chunk_size < self.min_chunk_size:
119
+ raise ValueError(
120
+ f"max_chunk_size ({self.max_chunk_size}) must be >= "
121
+ f"min_chunk_size ({self.min_chunk_size})"
122
+ )
123
+ if self.time_window is not None and self.time_window <= 0:
124
+ raise ValueError(f"time_window must be positive, got {self.time_window}")
125
+
126
+
127
+ @dataclass
128
+ class MemoryStats:
129
+ """Memory usage statistics.
130
+
131
+ Attributes:
132
+ peak_memory_mb: Peak memory usage in megabytes.
133
+ current_memory_mb: Current memory usage in megabytes.
134
+ allocated_mb: Total memory allocated in megabytes.
135
+ freed_mb: Total memory freed in megabytes.
136
+ leak_detected: Whether a memory leak was detected.
137
+ available_memory_mb: Available system memory in megabytes.
138
+ usage_percent: Memory usage as percentage of total.
139
+
140
+ Example:
141
+ >>> stats = optimizer.get_memory_stats()
142
+ >>> print(f"Current: {stats.current_memory_mb:.1f} MB")
143
+ >>> print(f"Peak: {stats.peak_memory_mb:.1f} MB")
144
+ >>> if stats.leak_detected:
145
+ ... print("WARNING: Memory leak detected!")
146
+ """
147
+
148
+ peak_memory_mb: float
149
+ current_memory_mb: float
150
+ allocated_mb: float
151
+ freed_mb: float
152
+ leak_detected: bool
153
+ available_memory_mb: float
154
+ usage_percent: float
155
+
156
+
157
+ class StreamProcessor:
158
+ """Streaming processor for chunk-by-chunk data processing.
159
+
160
+ Provides iterator interface for processing large datasets in chunks
161
+ without loading all data into memory. Supports overlapping chunks
162
+ for windowed operations.
163
+
164
+ Attributes:
165
+ data: Data source (array or memory-mapped array).
166
+ chunk_size: Size of each chunk in samples.
167
+ overlap: Number of samples to overlap between chunks.
168
+ total_samples: Total number of samples in data.
169
+
170
+ Example:
171
+ >>> processor = StreamProcessor(
172
+ ... data=huge_array,
173
+ ... chunk_size=1_000_000,
174
+ ... overlap=1024
175
+ ... )
176
+ >>> for chunk in processor:
177
+ ... result = analyze_chunk(chunk)
178
+ ... print(f"Processed {len(chunk)} samples")
179
+ """
180
+
181
+ def __init__(
182
+ self,
183
+ data: NDArray[Any] | np.memmap[Any, Any],
184
+ chunk_size: int,
185
+ overlap: int = 0,
186
+ ) -> None:
187
+ """Initialize stream processor.
188
+
189
+ Args:
190
+ data: Data array or memmap to process.
191
+ chunk_size: Number of samples per chunk.
192
+ overlap: Number of samples to overlap between chunks.
193
+
194
+ Raises:
195
+ ValueError: If chunk_size or overlap invalid.
196
+ """
197
+ if chunk_size <= 0:
198
+ raise ValueError(f"chunk_size must be positive, got {chunk_size}")
199
+ if overlap < 0:
200
+ raise ValueError(f"overlap must be non-negative, got {overlap}")
201
+ if overlap >= chunk_size:
202
+ raise ValueError(f"overlap ({overlap}) must be less than chunk_size ({chunk_size})")
203
+
204
+ self.data = data
205
+ self.chunk_size = chunk_size
206
+ self.overlap = overlap
207
+ self.total_samples = len(data)
208
+ self._current_position = 0
209
+
210
+ def __iter__(self) -> Iterator[NDArray[np.float64]]:
211
+ """Iterate over chunks.
212
+
213
+ Yields:
214
+ Chunks of data as numpy arrays.
215
+
216
+ Example:
217
+ >>> for chunk in processor:
218
+ ... mean = np.mean(chunk)
219
+ ... print(f"Chunk mean: {mean}")
220
+ """
221
+ self._current_position = 0
222
+
223
+ while self._current_position < self.total_samples:
224
+ end = min(self._current_position + self.chunk_size, self.total_samples)
225
+ chunk = self.data[self._current_position : end]
226
+
227
+ # Convert memmap to regular array to avoid keeping file handle open
228
+ if isinstance(chunk, np.memmap):
229
+ chunk = np.asarray(chunk, dtype=np.float64)
230
+
231
+ yield chunk
232
+
233
+ self._current_position = end - self.overlap
234
+
235
+ # Break if we've reached the end
236
+ if end >= self.total_samples:
237
+ break
238
+
239
+ def __len__(self) -> int:
240
+ """Number of chunks that will be yielded.
241
+
242
+ Returns:
243
+ Total number of chunks.
244
+ """
245
+ step = self.chunk_size - self.overlap
246
+ num_chunks = (self.total_samples - self.overlap) // step
247
+ if (self.total_samples - self.overlap) % step != 0:
248
+ num_chunks += 1
249
+ return num_chunks
250
+
251
+ def reset(self) -> None:
252
+ """Reset iterator to beginning.
253
+
254
+ Example:
255
+ >>> processor.reset()
256
+ >>> for chunk in processor:
257
+ ... process(chunk)
258
+ """
259
+ self._current_position = 0
260
+
261
+
262
+ class MemoryOptimizer:
263
+ """Memory optimizer for large signal file processing.
264
+
265
+ Provides comprehensive memory management including memory-mapped I/O,
266
+ streaming analysis, adaptive chunking, and memory leak detection.
267
+
268
+ Attributes:
269
+ max_memory_mb: Maximum memory limit in megabytes (None = no limit).
270
+ enable_compression: Whether to enable in-memory compression.
271
+ gc_threshold: Memory usage threshold to trigger garbage collection.
272
+
273
+ Example:
274
+ >>> # Create optimizer with 2 GB memory limit
275
+ >>> optimizer = MemoryOptimizer(max_memory_mb=2048)
276
+ >>>
277
+ >>> # Load file optimally (mmap if huge, eager if small)
278
+ >>> trace = optimizer.load_optimized("data.npy", sample_rate=1e9)
279
+ >>>
280
+ >>> # Get recommended chunk size
281
+ >>> chunk_size = optimizer.recommend_chunk_size(
282
+ ... data_length=100_000_000,
283
+ ... dtype=np.float64
284
+ ... )
285
+ >>>
286
+ >>> # Create streaming processor
287
+ >>> processor = optimizer.create_stream_processor(
288
+ ... trace, chunk_size=chunk_size
289
+ ... )
290
+ """
291
+
292
+ def __init__(
293
+ self,
294
+ max_memory_mb: float | None = None,
295
+ enable_compression: bool = False,
296
+ gc_threshold: float = 0.8,
297
+ ) -> None:
298
+ """Initialize memory optimizer.
299
+
300
+ Args:
301
+ max_memory_mb: Maximum memory limit in MB (None = no limit).
302
+ enable_compression: Enable in-memory compression.
303
+ gc_threshold: Memory usage fraction to trigger GC (0.0-1.0).
304
+
305
+ Raises:
306
+ ValueError: If parameters invalid.
307
+
308
+ Example:
309
+ >>> # Limit to 1 GB with aggressive GC
310
+ >>> optimizer = MemoryOptimizer(
311
+ ... max_memory_mb=1024,
312
+ ... gc_threshold=0.7
313
+ ... )
314
+ """
315
+ if max_memory_mb is not None and max_memory_mb <= 0:
316
+ raise ValueError(f"max_memory_mb must be positive, got {max_memory_mb}")
317
+ if not 0.0 <= gc_threshold <= 1.0:
318
+ raise ValueError(f"gc_threshold must be in [0, 1], got {gc_threshold}")
319
+
320
+ self.max_memory_mb = max_memory_mb
321
+ self.enable_compression = enable_compression
322
+ self.gc_threshold = gc_threshold
323
+
324
+ # Memory tracking
325
+ self._initial_memory_mb = self._get_memory_usage()
326
+ self._peak_memory_mb = self._initial_memory_mb
327
+ self._total_allocated_mb = 0.0
328
+ self._total_freed_mb = 0.0
329
+ self._allocation_count = 0
330
+ self._last_gc_memory = self._initial_memory_mb
331
+
332
+ def _get_memory_usage(self) -> float:
333
+ """Get current process memory usage in MB.
334
+
335
+ Returns:
336
+ Memory usage in megabytes.
337
+ """
338
+ process = psutil.Process()
339
+ return float(process.memory_info().rss / (1024 * 1024))
340
+
341
+ def _get_available_memory(self) -> float:
342
+ """Get available system memory in MB.
343
+
344
+ Returns:
345
+ Available memory in megabytes.
346
+ """
347
+ return float(psutil.virtual_memory().available / (1024 * 1024))
348
+
349
+ def _update_memory_tracking(self) -> None:
350
+ """Update memory usage tracking and trigger GC if needed."""
351
+ current_memory = self._get_memory_usage()
352
+
353
+ # Update peak
354
+ if current_memory > self._peak_memory_mb:
355
+ self._peak_memory_mb = current_memory
356
+
357
+ # Track allocations
358
+ memory_delta = current_memory - self._last_gc_memory
359
+ if memory_delta > 0:
360
+ self._total_allocated_mb += memory_delta
361
+ self._allocation_count += 1
362
+ elif memory_delta < 0:
363
+ self._total_freed_mb += abs(memory_delta)
364
+
365
+ # Check if GC needed
366
+ if self.max_memory_mb is not None:
367
+ usage_fraction = current_memory / self.max_memory_mb
368
+ if usage_fraction >= self.gc_threshold:
369
+ self._trigger_gc()
370
+ self._last_gc_memory = self._get_memory_usage()
371
+ else:
372
+ # Use available system memory
373
+ available = self._get_available_memory()
374
+ total = psutil.virtual_memory().total / (1024 * 1024)
375
+ usage_fraction = 1.0 - (available / total)
376
+ if usage_fraction >= self.gc_threshold:
377
+ self._trigger_gc()
378
+ self._last_gc_memory = self._get_memory_usage()
379
+
380
+ def _trigger_gc(self) -> None:
381
+ """Trigger garbage collection to free memory."""
382
+ gc.collect()
383
+
384
+ def _detect_memory_leak(self) -> bool:
385
+ """Detect potential memory leak.
386
+
387
+ Returns:
388
+ True if leak detected, False otherwise.
389
+
390
+ Note:
391
+ Detects leak if allocated memory keeps growing without being freed.
392
+ """
393
+ if self._allocation_count < 10:
394
+ return False
395
+
396
+ # Check if allocated > freed by large margin
397
+ leak_threshold = 100.0 # MB
398
+ net_growth = self._total_allocated_mb - self._total_freed_mb
399
+
400
+ return net_growth > leak_threshold
401
+
402
+ def get_memory_stats(self) -> MemoryStats:
403
+ """Get current memory usage statistics.
404
+
405
+ Returns:
406
+ MemoryStats with current memory usage information.
407
+
408
+ Example:
409
+ >>> stats = optimizer.get_memory_stats()
410
+ >>> print(f"Peak: {stats.peak_memory_mb:.1f} MB")
411
+ >>> print(f"Available: {stats.available_memory_mb:.1f} MB")
412
+ >>> if stats.leak_detected:
413
+ ... print("WARNING: Memory leak detected!")
414
+ """
415
+ current = self._get_memory_usage()
416
+ available = self._get_available_memory()
417
+ total = psutil.virtual_memory().total / (1024 * 1024)
418
+
419
+ return MemoryStats(
420
+ peak_memory_mb=self._peak_memory_mb,
421
+ current_memory_mb=current,
422
+ allocated_mb=self._total_allocated_mb,
423
+ freed_mb=self._total_freed_mb,
424
+ leak_detected=self._detect_memory_leak(),
425
+ available_memory_mb=available,
426
+ usage_percent=(current / total) * 100.0,
427
+ )
428
+
429
+ def recommend_chunk_size(
430
+ self,
431
+ data_length: int,
432
+ dtype: DTypeLike = np.float64,
433
+ target_memory_mb: float = 100.0,
434
+ ) -> int:
435
+ """Recommend optimal chunk size based on available memory.
436
+
437
+ Args:
438
+ data_length: Total length of data in samples.
439
+ dtype: Data type of samples.
440
+ target_memory_mb: Target memory per chunk in MB.
441
+
442
+ Returns:
443
+ Recommended chunk size in samples.
444
+
445
+ Example:
446
+ >>> chunk_size = optimizer.recommend_chunk_size(
447
+ ... data_length=100_000_000,
448
+ ... dtype=np.float64,
449
+ ... target_memory_mb=50.0
450
+ ... )
451
+ >>> print(f"Recommended: {chunk_size:,} samples")
452
+ """
453
+ dtype_np = np.dtype(dtype)
454
+ bytes_per_sample = dtype_np.itemsize
455
+
456
+ # Calculate chunk size for target memory
457
+ target_bytes = target_memory_mb * 1024 * 1024
458
+ chunk_size = int(target_bytes / bytes_per_sample)
459
+
460
+ # Clamp to reasonable bounds
461
+ min_chunk = 1000
462
+ max_chunk = data_length
463
+
464
+ return max(min_chunk, min(chunk_size, max_chunk))
465
+
466
+ def load_optimized(
467
+ self,
468
+ file_path: str | PathLike[str],
469
+ sample_rate: float,
470
+ *,
471
+ dtype: DTypeLike | None = None,
472
+ mmap_threshold_mb: float = 100.0,
473
+ ) -> Any:
474
+ """Load file with optimal strategy (mmap for huge files, eager for small).
475
+
476
+ Args:
477
+ file_path: Path to data file.
478
+ sample_rate: Sample rate in Hz.
479
+ dtype: Data type (auto-detected for .npy).
480
+ mmap_threshold_mb: File size threshold for memory mapping (MB).
481
+
482
+ Returns:
483
+ Loaded trace (MmapWaveformTrace or WaveformTrace).
484
+
485
+ Example:
486
+ >>> # Automatically use mmap for huge files
487
+ >>> trace = optimizer.load_optimized(
488
+ ... "huge_file.npy",
489
+ ... sample_rate=1e9,
490
+ ... mmap_threshold_mb=100.0
491
+ ... )
492
+ """
493
+ file_path = Path(file_path)
494
+ file_size_mb = file_path.stat().st_size / (1024 * 1024)
495
+
496
+ # Update memory tracking
497
+ self._update_memory_tracking()
498
+
499
+ # Use mmap for large files
500
+ if file_size_mb >= mmap_threshold_mb:
501
+ from oscura.loaders.mmap_loader import load_mmap
502
+
503
+ return load_mmap(file_path, sample_rate=sample_rate, dtype=dtype)
504
+ else:
505
+ # Use eager loading for small files
506
+ from oscura.loaders import load
507
+
508
+ return load(file_path, sample_rate=sample_rate)
509
+
510
+ def create_stream_processor(
511
+ self,
512
+ data: NDArray[Any] | np.memmap[Any, Any] | Any,
513
+ chunk_size: int | None = None,
514
+ overlap: int = 0,
515
+ config: ChunkingConfig | None = None,
516
+ ) -> StreamProcessor:
517
+ """Create streaming processor for chunk-by-chunk processing.
518
+
519
+ Args:
520
+ data: Data array, memmap, or trace object.
521
+ chunk_size: Size of each chunk (auto if None).
522
+ overlap: Number of samples to overlap.
523
+ config: Chunking configuration (overrides chunk_size/overlap).
524
+
525
+ Returns:
526
+ StreamProcessor for iterating over chunks.
527
+
528
+ Example:
529
+ >>> processor = optimizer.create_stream_processor(
530
+ ... data=huge_array,
531
+ ... chunk_size=1_000_000,
532
+ ... overlap=1024
533
+ ... )
534
+ >>> for chunk in processor:
535
+ ... result = analyze(chunk)
536
+ """
537
+ # Extract data array if trace object
538
+ if hasattr(data, "data"):
539
+ data_array = data.data
540
+ else:
541
+ data_array = data
542
+
543
+ # Convert to numpy array if needed
544
+ if not isinstance(data_array, (np.ndarray, np.memmap)):
545
+ data_array = np.asarray(data_array)
546
+
547
+ # Use config if provided
548
+ if config is not None:
549
+ chunk_size = config.chunk_size
550
+ overlap = config.overlap
551
+
552
+ # Adaptive chunking
553
+ if config.adaptive:
554
+ chunk_size = self.recommend_chunk_size(
555
+ data_length=len(data_array),
556
+ dtype=data_array.dtype,
557
+ )
558
+ chunk_size = max(config.min_chunk_size, min(chunk_size, config.max_chunk_size))
559
+
560
+ # Auto-recommend chunk size if not provided
561
+ if chunk_size is None:
562
+ chunk_size = self.recommend_chunk_size(
563
+ data_length=len(data_array),
564
+ dtype=data_array.dtype,
565
+ )
566
+
567
+ # Update memory tracking
568
+ self._update_memory_tracking()
569
+
570
+ return StreamProcessor(data=data_array, chunk_size=chunk_size, overlap=overlap)
571
+
572
+ def optimize_array(
573
+ self,
574
+ array: NDArray[Any],
575
+ compress: bool | None = None,
576
+ ) -> NDArray[Any]:
577
+ """Optimize array memory usage.
578
+
579
+ Args:
580
+ array: Array to optimize.
581
+ compress: Whether to compress (uses instance default if None).
582
+
583
+ Returns:
584
+ Optimized array (may be compressed or compacted).
585
+
586
+ Note:
587
+ Currently returns compacted array. Future versions may add
588
+ compression support via zlib or blosc.
589
+
590
+ Example:
591
+ >>> optimized = optimizer.optimize_array(large_array)
592
+ """
593
+ # Update memory tracking
594
+ self._update_memory_tracking()
595
+
596
+ # Make array contiguous for better cache performance
597
+ if not array.flags["C_CONTIGUOUS"]:
598
+ array = np.ascontiguousarray(array)
599
+
600
+ # Future: Add compression support
601
+ # if compress or (compress is None and self.enable_compression):
602
+ # return compress_array(array)
603
+
604
+ return array
605
+
606
+ def set_memory_limit(self, max_memory_mb: float) -> None:
607
+ """Set maximum memory limit.
608
+
609
+ Args:
610
+ max_memory_mb: Maximum memory in megabytes.
611
+
612
+ Raises:
613
+ ValueError: If max_memory_mb not positive.
614
+
615
+ Example:
616
+ >>> optimizer.set_memory_limit(1024) # 1 GB
617
+ """
618
+ if max_memory_mb <= 0:
619
+ raise ValueError(f"max_memory_mb must be positive, got {max_memory_mb}")
620
+ self.max_memory_mb = max_memory_mb
621
+
622
+ def check_available_memory(self, required_mb: float) -> bool:
623
+ """Check if sufficient memory available.
624
+
625
+ Args:
626
+ required_mb: Required memory in megabytes.
627
+
628
+ Returns:
629
+ True if sufficient memory available, False otherwise.
630
+
631
+ Example:
632
+ >>> if optimizer.check_available_memory(500):
633
+ ... process_large_data()
634
+ ... else:
635
+ ... print("Insufficient memory")
636
+ """
637
+ available = self._get_available_memory()
638
+ return available >= required_mb
639
+
640
+ def suggest_downsampling(
641
+ self,
642
+ data_length: int,
643
+ dtype: DTypeLike = np.float64,
644
+ target_memory_mb: float = 100.0,
645
+ ) -> int:
646
+ """Suggest downsampling factor to fit in target memory.
647
+
648
+ Args:
649
+ data_length: Length of data in samples.
650
+ dtype: Data type of samples.
651
+ target_memory_mb: Target memory in megabytes.
652
+
653
+ Returns:
654
+ Downsampling factor (1 = no downsampling, 2 = every other sample, etc).
655
+
656
+ Example:
657
+ >>> factor = optimizer.suggest_downsampling(
658
+ ... data_length=100_000_000,
659
+ ... target_memory_mb=50.0
660
+ ... )
661
+ >>> if factor > 1:
662
+ ... data = data[::factor]
663
+ """
664
+ dtype_np = np.dtype(dtype)
665
+ bytes_per_sample = dtype_np.itemsize
666
+
667
+ # Calculate current memory requirement
668
+ current_mb = (data_length * bytes_per_sample) / (1024 * 1024)
669
+
670
+ if current_mb <= target_memory_mb:
671
+ return 1
672
+
673
+ # Calculate downsampling factor
674
+ factor = int(np.ceil(current_mb / target_memory_mb))
675
+ return factor
676
+
677
+ def reset_statistics(self) -> None:
678
+ """Reset memory tracking statistics.
679
+
680
+ Example:
681
+ >>> optimizer.reset_statistics()
682
+ >>> # Track memory for new operation
683
+ >>> stats = optimizer.get_memory_stats()
684
+ """
685
+ self._initial_memory_mb = self._get_memory_usage()
686
+ self._peak_memory_mb = self._initial_memory_mb
687
+ self._total_allocated_mb = 0.0
688
+ self._total_freed_mb = 0.0
689
+ self._allocation_count = 0
690
+ self._last_gc_memory = self._initial_memory_mb
691
+
692
+
693
+ __all__ = [
694
+ "ChunkingConfig",
695
+ "ChunkingStrategy",
696
+ "MemoryOptimizer",
697
+ "MemoryStats",
698
+ "StreamProcessor",
699
+ ]