oscura 0.5.1__py3-none-any.whl → 0.7.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 (497) 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/edges.py +325 -65
  5. oscura/analyzers/digital/quality.py +293 -166
  6. oscura/analyzers/digital/timing.py +260 -115
  7. oscura/analyzers/digital/timing_numba.py +334 -0
  8. oscura/analyzers/entropy.py +605 -0
  9. oscura/analyzers/eye/diagram.py +176 -109
  10. oscura/analyzers/eye/metrics.py +5 -5
  11. oscura/analyzers/jitter/__init__.py +6 -4
  12. oscura/analyzers/jitter/ber.py +52 -52
  13. oscura/analyzers/jitter/classification.py +156 -0
  14. oscura/analyzers/jitter/decomposition.py +163 -113
  15. oscura/analyzers/jitter/spectrum.py +80 -64
  16. oscura/analyzers/ml/__init__.py +39 -0
  17. oscura/analyzers/ml/features.py +600 -0
  18. oscura/analyzers/ml/signal_classifier.py +604 -0
  19. oscura/analyzers/packet/daq.py +246 -158
  20. oscura/analyzers/packet/parser.py +12 -1
  21. oscura/analyzers/packet/payload.py +50 -2110
  22. oscura/analyzers/packet/payload_analysis.py +361 -181
  23. oscura/analyzers/packet/payload_patterns.py +133 -70
  24. oscura/analyzers/packet/stream.py +84 -23
  25. oscura/analyzers/patterns/__init__.py +26 -5
  26. oscura/analyzers/patterns/anomaly_detection.py +908 -0
  27. oscura/analyzers/patterns/clustering.py +169 -108
  28. oscura/analyzers/patterns/clustering_optimized.py +227 -0
  29. oscura/analyzers/patterns/discovery.py +1 -1
  30. oscura/analyzers/patterns/matching.py +581 -197
  31. oscura/analyzers/patterns/pattern_mining.py +778 -0
  32. oscura/analyzers/patterns/periodic.py +121 -38
  33. oscura/analyzers/patterns/sequences.py +175 -78
  34. oscura/analyzers/power/conduction.py +1 -1
  35. oscura/analyzers/power/soa.py +6 -6
  36. oscura/analyzers/power/switching.py +250 -110
  37. oscura/analyzers/protocol/__init__.py +17 -1
  38. oscura/analyzers/protocols/base.py +6 -6
  39. oscura/analyzers/protocols/ble/__init__.py +38 -0
  40. oscura/analyzers/protocols/ble/analyzer.py +809 -0
  41. oscura/analyzers/protocols/ble/uuids.py +288 -0
  42. oscura/analyzers/protocols/can.py +257 -127
  43. oscura/analyzers/protocols/can_fd.py +107 -80
  44. oscura/analyzers/protocols/flexray.py +139 -80
  45. oscura/analyzers/protocols/hdlc.py +93 -58
  46. oscura/analyzers/protocols/i2c.py +247 -106
  47. oscura/analyzers/protocols/i2s.py +138 -86
  48. oscura/analyzers/protocols/industrial/__init__.py +40 -0
  49. oscura/analyzers/protocols/industrial/bacnet/__init__.py +33 -0
  50. oscura/analyzers/protocols/industrial/bacnet/analyzer.py +708 -0
  51. oscura/analyzers/protocols/industrial/bacnet/encoding.py +412 -0
  52. oscura/analyzers/protocols/industrial/bacnet/services.py +622 -0
  53. oscura/analyzers/protocols/industrial/ethercat/__init__.py +30 -0
  54. oscura/analyzers/protocols/industrial/ethercat/analyzer.py +474 -0
  55. oscura/analyzers/protocols/industrial/ethercat/mailbox.py +339 -0
  56. oscura/analyzers/protocols/industrial/ethercat/topology.py +166 -0
  57. oscura/analyzers/protocols/industrial/modbus/__init__.py +31 -0
  58. oscura/analyzers/protocols/industrial/modbus/analyzer.py +525 -0
  59. oscura/analyzers/protocols/industrial/modbus/crc.py +79 -0
  60. oscura/analyzers/protocols/industrial/modbus/functions.py +436 -0
  61. oscura/analyzers/protocols/industrial/opcua/__init__.py +21 -0
  62. oscura/analyzers/protocols/industrial/opcua/analyzer.py +552 -0
  63. oscura/analyzers/protocols/industrial/opcua/datatypes.py +446 -0
  64. oscura/analyzers/protocols/industrial/opcua/services.py +264 -0
  65. oscura/analyzers/protocols/industrial/profinet/__init__.py +23 -0
  66. oscura/analyzers/protocols/industrial/profinet/analyzer.py +441 -0
  67. oscura/analyzers/protocols/industrial/profinet/dcp.py +263 -0
  68. oscura/analyzers/protocols/industrial/profinet/ptcp.py +200 -0
  69. oscura/analyzers/protocols/jtag.py +180 -98
  70. oscura/analyzers/protocols/lin.py +219 -114
  71. oscura/analyzers/protocols/manchester.py +4 -4
  72. oscura/analyzers/protocols/onewire.py +253 -149
  73. oscura/analyzers/protocols/parallel_bus/__init__.py +20 -0
  74. oscura/analyzers/protocols/parallel_bus/centronics.py +92 -0
  75. oscura/analyzers/protocols/parallel_bus/gpib.py +137 -0
  76. oscura/analyzers/protocols/spi.py +192 -95
  77. oscura/analyzers/protocols/swd.py +321 -167
  78. oscura/analyzers/protocols/uart.py +267 -125
  79. oscura/analyzers/protocols/usb.py +235 -131
  80. oscura/analyzers/side_channel/power.py +17 -12
  81. oscura/analyzers/signal/__init__.py +15 -0
  82. oscura/analyzers/signal/timing_analysis.py +1086 -0
  83. oscura/analyzers/signal_integrity/__init__.py +4 -1
  84. oscura/analyzers/signal_integrity/sparams.py +2 -19
  85. oscura/analyzers/spectral/chunked.py +129 -60
  86. oscura/analyzers/spectral/chunked_fft.py +300 -94
  87. oscura/analyzers/spectral/chunked_wavelet.py +100 -80
  88. oscura/analyzers/statistical/checksum.py +376 -217
  89. oscura/analyzers/statistical/classification.py +229 -107
  90. oscura/analyzers/statistical/entropy.py +78 -53
  91. oscura/analyzers/statistics/correlation.py +407 -211
  92. oscura/analyzers/statistics/outliers.py +2 -2
  93. oscura/analyzers/statistics/streaming.py +30 -5
  94. oscura/analyzers/validation.py +216 -101
  95. oscura/analyzers/waveform/measurements.py +9 -0
  96. oscura/analyzers/waveform/measurements_with_uncertainty.py +31 -15
  97. oscura/analyzers/waveform/spectral.py +500 -228
  98. oscura/api/__init__.py +31 -5
  99. oscura/api/dsl/__init__.py +582 -0
  100. oscura/{dsl → api/dsl}/commands.py +43 -76
  101. oscura/{dsl → api/dsl}/interpreter.py +26 -51
  102. oscura/{dsl → api/dsl}/parser.py +107 -77
  103. oscura/{dsl → api/dsl}/repl.py +2 -2
  104. oscura/api/dsl.py +1 -1
  105. oscura/{integrations → api/integrations}/__init__.py +1 -1
  106. oscura/{integrations → api/integrations}/llm.py +201 -102
  107. oscura/api/operators.py +3 -3
  108. oscura/api/optimization.py +144 -30
  109. oscura/api/rest_server.py +921 -0
  110. oscura/api/server/__init__.py +17 -0
  111. oscura/api/server/dashboard.py +850 -0
  112. oscura/api/server/static/README.md +34 -0
  113. oscura/api/server/templates/base.html +181 -0
  114. oscura/api/server/templates/export.html +120 -0
  115. oscura/api/server/templates/home.html +284 -0
  116. oscura/api/server/templates/protocols.html +58 -0
  117. oscura/api/server/templates/reports.html +43 -0
  118. oscura/api/server/templates/session_detail.html +89 -0
  119. oscura/api/server/templates/sessions.html +83 -0
  120. oscura/api/server/templates/waveforms.html +73 -0
  121. oscura/automotive/__init__.py +8 -1
  122. oscura/automotive/can/__init__.py +10 -0
  123. oscura/automotive/can/checksum.py +3 -1
  124. oscura/automotive/can/dbc_generator.py +590 -0
  125. oscura/automotive/can/message_wrapper.py +121 -74
  126. oscura/automotive/can/patterns.py +98 -21
  127. oscura/automotive/can/session.py +292 -56
  128. oscura/automotive/can/state_machine.py +6 -3
  129. oscura/automotive/can/stimulus_response.py +97 -75
  130. oscura/automotive/dbc/__init__.py +10 -2
  131. oscura/automotive/dbc/generator.py +84 -56
  132. oscura/automotive/dbc/parser.py +6 -6
  133. oscura/automotive/dtc/data.json +17 -102
  134. oscura/automotive/dtc/database.py +2 -2
  135. oscura/automotive/flexray/__init__.py +31 -0
  136. oscura/automotive/flexray/analyzer.py +504 -0
  137. oscura/automotive/flexray/crc.py +185 -0
  138. oscura/automotive/flexray/fibex.py +449 -0
  139. oscura/automotive/j1939/__init__.py +45 -8
  140. oscura/automotive/j1939/analyzer.py +605 -0
  141. oscura/automotive/j1939/spns.py +326 -0
  142. oscura/automotive/j1939/transport.py +306 -0
  143. oscura/automotive/lin/__init__.py +47 -0
  144. oscura/automotive/lin/analyzer.py +612 -0
  145. oscura/automotive/loaders/blf.py +13 -2
  146. oscura/automotive/loaders/csv_can.py +143 -72
  147. oscura/automotive/loaders/dispatcher.py +50 -2
  148. oscura/automotive/loaders/mdf.py +86 -45
  149. oscura/automotive/loaders/pcap.py +111 -61
  150. oscura/automotive/uds/__init__.py +4 -0
  151. oscura/automotive/uds/analyzer.py +725 -0
  152. oscura/automotive/uds/decoder.py +140 -58
  153. oscura/automotive/uds/models.py +7 -1
  154. oscura/automotive/visualization.py +1 -1
  155. oscura/cli/analyze.py +348 -0
  156. oscura/cli/batch.py +142 -122
  157. oscura/cli/benchmark.py +275 -0
  158. oscura/cli/characterize.py +137 -82
  159. oscura/cli/compare.py +224 -131
  160. oscura/cli/completion.py +250 -0
  161. oscura/cli/config_cmd.py +361 -0
  162. oscura/cli/decode.py +164 -87
  163. oscura/cli/export.py +286 -0
  164. oscura/cli/main.py +115 -31
  165. oscura/{onboarding → cli/onboarding}/__init__.py +3 -3
  166. oscura/{onboarding → cli/onboarding}/help.py +80 -58
  167. oscura/{onboarding → cli/onboarding}/tutorials.py +97 -72
  168. oscura/{onboarding → cli/onboarding}/wizard.py +55 -36
  169. oscura/cli/progress.py +147 -0
  170. oscura/cli/shell.py +157 -135
  171. oscura/cli/validate_cmd.py +204 -0
  172. oscura/cli/visualize.py +158 -0
  173. oscura/convenience.py +125 -79
  174. oscura/core/__init__.py +4 -2
  175. oscura/core/backend_selector.py +3 -3
  176. oscura/core/cache.py +126 -15
  177. oscura/core/cancellation.py +1 -1
  178. oscura/{config → core/config}/__init__.py +20 -11
  179. oscura/{config → core/config}/defaults.py +1 -1
  180. oscura/{config → core/config}/loader.py +7 -5
  181. oscura/{config → core/config}/memory.py +5 -5
  182. oscura/{config → core/config}/migration.py +1 -1
  183. oscura/{config → core/config}/pipeline.py +99 -23
  184. oscura/{config → core/config}/preferences.py +1 -1
  185. oscura/{config → core/config}/protocol.py +3 -3
  186. oscura/{config → core/config}/schema.py +426 -272
  187. oscura/{config → core/config}/settings.py +1 -1
  188. oscura/{config → core/config}/thresholds.py +195 -153
  189. oscura/core/correlation.py +5 -6
  190. oscura/core/cross_domain.py +0 -2
  191. oscura/core/debug.py +9 -5
  192. oscura/{extensibility → core/extensibility}/docs.py +158 -70
  193. oscura/{extensibility → core/extensibility}/extensions.py +160 -76
  194. oscura/{extensibility → core/extensibility}/logging.py +1 -1
  195. oscura/{extensibility → core/extensibility}/measurements.py +1 -1
  196. oscura/{extensibility → core/extensibility}/plugins.py +1 -1
  197. oscura/{extensibility → core/extensibility}/templates.py +73 -3
  198. oscura/{extensibility → core/extensibility}/validation.py +1 -1
  199. oscura/core/gpu_backend.py +11 -7
  200. oscura/core/log_query.py +101 -11
  201. oscura/core/logging.py +126 -54
  202. oscura/core/logging_advanced.py +5 -5
  203. oscura/core/memory_limits.py +108 -70
  204. oscura/core/memory_monitor.py +2 -2
  205. oscura/core/memory_progress.py +7 -7
  206. oscura/core/memory_warnings.py +1 -1
  207. oscura/core/numba_backend.py +13 -13
  208. oscura/{plugins → core/plugins}/__init__.py +9 -9
  209. oscura/{plugins → core/plugins}/base.py +7 -7
  210. oscura/{plugins → core/plugins}/cli.py +3 -3
  211. oscura/{plugins → core/plugins}/discovery.py +186 -106
  212. oscura/{plugins → core/plugins}/lifecycle.py +1 -1
  213. oscura/{plugins → core/plugins}/manager.py +7 -7
  214. oscura/{plugins → core/plugins}/registry.py +3 -3
  215. oscura/{plugins → core/plugins}/versioning.py +1 -1
  216. oscura/core/progress.py +16 -1
  217. oscura/core/provenance.py +8 -2
  218. oscura/{schemas → core/schemas}/__init__.py +2 -2
  219. oscura/{schemas → core/schemas}/device_mapping.json +2 -8
  220. oscura/{schemas → core/schemas}/packet_format.json +4 -24
  221. oscura/{schemas → core/schemas}/protocol_definition.json +2 -12
  222. oscura/core/types.py +4 -0
  223. oscura/core/uncertainty.py +3 -3
  224. oscura/correlation/__init__.py +52 -0
  225. oscura/correlation/multi_protocol.py +811 -0
  226. oscura/discovery/auto_decoder.py +117 -35
  227. oscura/discovery/comparison.py +191 -86
  228. oscura/discovery/quality_validator.py +155 -68
  229. oscura/discovery/signal_detector.py +196 -79
  230. oscura/export/__init__.py +18 -8
  231. oscura/export/kaitai_struct.py +513 -0
  232. oscura/export/scapy_layer.py +801 -0
  233. oscura/export/wireshark/generator.py +1 -1
  234. oscura/export/wireshark/templates/dissector.lua.j2 +2 -2
  235. oscura/export/wireshark_dissector.py +746 -0
  236. oscura/guidance/wizard.py +207 -111
  237. oscura/hardware/__init__.py +19 -0
  238. oscura/{acquisition → hardware/acquisition}/__init__.py +4 -4
  239. oscura/{acquisition → hardware/acquisition}/file.py +2 -2
  240. oscura/{acquisition → hardware/acquisition}/hardware.py +7 -7
  241. oscura/{acquisition → hardware/acquisition}/saleae.py +15 -12
  242. oscura/{acquisition → hardware/acquisition}/socketcan.py +1 -1
  243. oscura/{acquisition → hardware/acquisition}/streaming.py +2 -2
  244. oscura/{acquisition → hardware/acquisition}/synthetic.py +3 -3
  245. oscura/{acquisition → hardware/acquisition}/visa.py +33 -11
  246. oscura/hardware/firmware/__init__.py +29 -0
  247. oscura/hardware/firmware/pattern_recognition.py +874 -0
  248. oscura/hardware/hal_detector.py +736 -0
  249. oscura/hardware/security/__init__.py +37 -0
  250. oscura/hardware/security/side_channel_detector.py +1126 -0
  251. oscura/inference/__init__.py +4 -0
  252. oscura/inference/active_learning/observation_table.py +4 -1
  253. oscura/inference/alignment.py +216 -123
  254. oscura/inference/bayesian.py +113 -33
  255. oscura/inference/crc_reverse.py +101 -55
  256. oscura/inference/logic.py +6 -2
  257. oscura/inference/message_format.py +342 -183
  258. oscura/inference/protocol.py +95 -44
  259. oscura/inference/protocol_dsl.py +180 -82
  260. oscura/inference/signal_intelligence.py +1439 -706
  261. oscura/inference/spectral.py +99 -57
  262. oscura/inference/state_machine.py +810 -158
  263. oscura/inference/stream.py +270 -110
  264. oscura/iot/__init__.py +34 -0
  265. oscura/iot/coap/__init__.py +32 -0
  266. oscura/iot/coap/analyzer.py +668 -0
  267. oscura/iot/coap/options.py +212 -0
  268. oscura/iot/lorawan/__init__.py +21 -0
  269. oscura/iot/lorawan/crypto.py +206 -0
  270. oscura/iot/lorawan/decoder.py +801 -0
  271. oscura/iot/lorawan/mac_commands.py +341 -0
  272. oscura/iot/mqtt/__init__.py +27 -0
  273. oscura/iot/mqtt/analyzer.py +999 -0
  274. oscura/iot/mqtt/properties.py +315 -0
  275. oscura/iot/zigbee/__init__.py +31 -0
  276. oscura/iot/zigbee/analyzer.py +615 -0
  277. oscura/iot/zigbee/security.py +153 -0
  278. oscura/iot/zigbee/zcl.py +349 -0
  279. oscura/jupyter/display.py +125 -45
  280. oscura/{exploratory → jupyter/exploratory}/__init__.py +8 -8
  281. oscura/{exploratory → jupyter/exploratory}/error_recovery.py +298 -141
  282. oscura/jupyter/exploratory/fuzzy.py +746 -0
  283. oscura/{exploratory → jupyter/exploratory}/fuzzy_advanced.py +258 -100
  284. oscura/{exploratory → jupyter/exploratory}/legacy.py +464 -242
  285. oscura/{exploratory → jupyter/exploratory}/parse.py +167 -145
  286. oscura/{exploratory → jupyter/exploratory}/recovery.py +119 -87
  287. oscura/jupyter/exploratory/sync.py +612 -0
  288. oscura/{exploratory → jupyter/exploratory}/unknown.py +299 -176
  289. oscura/jupyter/magic.py +4 -4
  290. oscura/{ui → jupyter/ui}/__init__.py +2 -2
  291. oscura/{ui → jupyter/ui}/formatters.py +3 -3
  292. oscura/{ui → jupyter/ui}/progressive_display.py +153 -82
  293. oscura/loaders/__init__.py +183 -67
  294. oscura/loaders/binary.py +88 -1
  295. oscura/loaders/chipwhisperer.py +153 -137
  296. oscura/loaders/configurable.py +208 -86
  297. oscura/loaders/csv_loader.py +458 -215
  298. oscura/loaders/hdf5_loader.py +278 -119
  299. oscura/loaders/lazy.py +87 -54
  300. oscura/loaders/mmap_loader.py +1 -1
  301. oscura/loaders/numpy_loader.py +253 -116
  302. oscura/loaders/pcap.py +226 -151
  303. oscura/loaders/rigol.py +110 -49
  304. oscura/loaders/sigrok.py +201 -78
  305. oscura/loaders/tdms.py +81 -58
  306. oscura/loaders/tektronix.py +291 -174
  307. oscura/loaders/touchstone.py +182 -87
  308. oscura/loaders/tss.py +456 -0
  309. oscura/loaders/vcd.py +215 -117
  310. oscura/loaders/wav.py +155 -68
  311. oscura/reporting/__init__.py +9 -0
  312. oscura/reporting/analyze.py +352 -146
  313. oscura/reporting/argument_preparer.py +69 -14
  314. oscura/reporting/auto_report.py +97 -61
  315. oscura/reporting/batch.py +131 -58
  316. oscura/reporting/chart_selection.py +57 -45
  317. oscura/reporting/comparison.py +63 -17
  318. oscura/reporting/content/executive.py +76 -24
  319. oscura/reporting/core_formats/multi_format.py +11 -8
  320. oscura/reporting/engine.py +312 -158
  321. oscura/reporting/enhanced_reports.py +949 -0
  322. oscura/reporting/export.py +86 -43
  323. oscura/reporting/formatting/numbers.py +69 -42
  324. oscura/reporting/html.py +139 -58
  325. oscura/reporting/index.py +137 -65
  326. oscura/reporting/output.py +158 -67
  327. oscura/reporting/pdf.py +67 -102
  328. oscura/reporting/plots.py +191 -112
  329. oscura/reporting/sections.py +88 -47
  330. oscura/reporting/standards.py +104 -61
  331. oscura/reporting/summary_generator.py +75 -55
  332. oscura/reporting/tables.py +138 -54
  333. oscura/reporting/templates/enhanced/protocol_re.html +525 -0
  334. oscura/sessions/__init__.py +14 -23
  335. oscura/sessions/base.py +3 -3
  336. oscura/sessions/blackbox.py +106 -10
  337. oscura/sessions/generic.py +2 -2
  338. oscura/sessions/legacy.py +783 -0
  339. oscura/side_channel/__init__.py +63 -0
  340. oscura/side_channel/dpa.py +1025 -0
  341. oscura/utils/__init__.py +15 -1
  342. oscura/utils/bitwise.py +118 -0
  343. oscura/{builders → utils/builders}/__init__.py +1 -1
  344. oscura/{comparison → utils/comparison}/__init__.py +6 -6
  345. oscura/{comparison → utils/comparison}/compare.py +202 -101
  346. oscura/{comparison → utils/comparison}/golden.py +83 -63
  347. oscura/{comparison → utils/comparison}/limits.py +313 -89
  348. oscura/{comparison → utils/comparison}/mask.py +151 -45
  349. oscura/{comparison → utils/comparison}/trace_diff.py +1 -1
  350. oscura/{comparison → utils/comparison}/visualization.py +147 -89
  351. oscura/{component → utils/component}/__init__.py +3 -3
  352. oscura/{component → utils/component}/impedance.py +122 -58
  353. oscura/{component → utils/component}/reactive.py +165 -168
  354. oscura/{component → utils/component}/transmission_line.py +3 -3
  355. oscura/{filtering → utils/filtering}/__init__.py +6 -6
  356. oscura/{filtering → utils/filtering}/base.py +1 -1
  357. oscura/{filtering → utils/filtering}/convenience.py +2 -2
  358. oscura/{filtering → utils/filtering}/design.py +169 -93
  359. oscura/{filtering → utils/filtering}/filters.py +2 -2
  360. oscura/{filtering → utils/filtering}/introspection.py +2 -2
  361. oscura/utils/geometry.py +31 -0
  362. oscura/utils/imports.py +184 -0
  363. oscura/utils/lazy.py +1 -1
  364. oscura/{math → utils/math}/__init__.py +2 -2
  365. oscura/{math → utils/math}/arithmetic.py +114 -48
  366. oscura/{math → utils/math}/interpolation.py +139 -106
  367. oscura/utils/memory.py +129 -66
  368. oscura/utils/memory_advanced.py +92 -9
  369. oscura/utils/memory_extensions.py +10 -8
  370. oscura/{optimization → utils/optimization}/__init__.py +1 -1
  371. oscura/{optimization → utils/optimization}/search.py +2 -2
  372. oscura/utils/performance/__init__.py +58 -0
  373. oscura/utils/performance/caching.py +889 -0
  374. oscura/utils/performance/lsh_clustering.py +333 -0
  375. oscura/utils/performance/memory_optimizer.py +699 -0
  376. oscura/utils/performance/optimizations.py +675 -0
  377. oscura/utils/performance/parallel.py +654 -0
  378. oscura/utils/performance/profiling.py +661 -0
  379. oscura/{pipeline → utils/pipeline}/base.py +1 -1
  380. oscura/{pipeline → utils/pipeline}/composition.py +1 -1
  381. oscura/{pipeline → utils/pipeline}/parallel.py +3 -2
  382. oscura/{pipeline → utils/pipeline}/pipeline.py +1 -1
  383. oscura/{pipeline → utils/pipeline}/reverse_engineering.py +412 -221
  384. oscura/{search → utils/search}/__init__.py +3 -3
  385. oscura/{search → utils/search}/anomaly.py +188 -58
  386. oscura/utils/search/context.py +294 -0
  387. oscura/{search → utils/search}/pattern.py +138 -10
  388. oscura/utils/serial.py +51 -0
  389. oscura/utils/storage/__init__.py +61 -0
  390. oscura/utils/storage/database.py +1166 -0
  391. oscura/{streaming → utils/streaming}/chunked.py +302 -143
  392. oscura/{streaming → utils/streaming}/progressive.py +1 -1
  393. oscura/{streaming → utils/streaming}/realtime.py +3 -2
  394. oscura/{triggering → utils/triggering}/__init__.py +6 -6
  395. oscura/{triggering → utils/triggering}/base.py +6 -6
  396. oscura/{triggering → utils/triggering}/edge.py +2 -2
  397. oscura/{triggering → utils/triggering}/pattern.py +2 -2
  398. oscura/{triggering → utils/triggering}/pulse.py +115 -74
  399. oscura/{triggering → utils/triggering}/window.py +2 -2
  400. oscura/utils/validation.py +32 -0
  401. oscura/validation/__init__.py +121 -0
  402. oscura/{compliance → validation/compliance}/__init__.py +5 -5
  403. oscura/{compliance → validation/compliance}/advanced.py +5 -5
  404. oscura/{compliance → validation/compliance}/masks.py +1 -1
  405. oscura/{compliance → validation/compliance}/reporting.py +127 -53
  406. oscura/{compliance → validation/compliance}/testing.py +114 -52
  407. oscura/validation/compliance_tests.py +915 -0
  408. oscura/validation/fuzzer.py +990 -0
  409. oscura/validation/grammar_tests.py +596 -0
  410. oscura/validation/grammar_validator.py +904 -0
  411. oscura/validation/hil_testing.py +977 -0
  412. oscura/{quality → validation/quality}/__init__.py +4 -4
  413. oscura/{quality → validation/quality}/ensemble.py +251 -171
  414. oscura/{quality → validation/quality}/explainer.py +3 -3
  415. oscura/{quality → validation/quality}/scoring.py +1 -1
  416. oscura/{quality → validation/quality}/warnings.py +4 -4
  417. oscura/validation/regression_suite.py +808 -0
  418. oscura/validation/replay.py +788 -0
  419. oscura/{testing → validation/testing}/__init__.py +2 -2
  420. oscura/{testing → validation/testing}/synthetic.py +5 -5
  421. oscura/visualization/__init__.py +9 -0
  422. oscura/visualization/accessibility.py +1 -1
  423. oscura/visualization/annotations.py +64 -67
  424. oscura/visualization/colors.py +7 -7
  425. oscura/visualization/digital.py +180 -81
  426. oscura/visualization/eye.py +236 -85
  427. oscura/visualization/interactive.py +320 -143
  428. oscura/visualization/jitter.py +587 -247
  429. oscura/visualization/layout.py +169 -134
  430. oscura/visualization/optimization.py +103 -52
  431. oscura/visualization/palettes.py +1 -1
  432. oscura/visualization/power.py +427 -211
  433. oscura/visualization/power_extended.py +626 -297
  434. oscura/visualization/presets.py +2 -0
  435. oscura/visualization/protocols.py +495 -181
  436. oscura/visualization/render.py +79 -63
  437. oscura/visualization/reverse_engineering.py +171 -124
  438. oscura/visualization/signal_integrity.py +460 -279
  439. oscura/visualization/specialized.py +190 -100
  440. oscura/visualization/spectral.py +670 -255
  441. oscura/visualization/thumbnails.py +166 -137
  442. oscura/visualization/waveform.py +150 -63
  443. oscura/workflows/__init__.py +3 -0
  444. oscura/{batch → workflows/batch}/__init__.py +5 -5
  445. oscura/{batch → workflows/batch}/advanced.py +150 -75
  446. oscura/workflows/batch/aggregate.py +531 -0
  447. oscura/workflows/batch/analyze.py +236 -0
  448. oscura/{batch → workflows/batch}/logging.py +2 -2
  449. oscura/{batch → workflows/batch}/metrics.py +1 -1
  450. oscura/workflows/complete_re.py +1144 -0
  451. oscura/workflows/compliance.py +44 -54
  452. oscura/workflows/digital.py +197 -51
  453. oscura/workflows/legacy/__init__.py +12 -0
  454. oscura/{workflow → workflows/legacy}/dag.py +4 -1
  455. oscura/workflows/multi_trace.py +9 -9
  456. oscura/workflows/power.py +42 -62
  457. oscura/workflows/protocol.py +82 -49
  458. oscura/workflows/reverse_engineering.py +351 -150
  459. oscura/workflows/signal_integrity.py +157 -82
  460. oscura-0.7.0.dist-info/METADATA +661 -0
  461. oscura-0.7.0.dist-info/RECORD +591 -0
  462. oscura/batch/aggregate.py +0 -300
  463. oscura/batch/analyze.py +0 -139
  464. oscura/dsl/__init__.py +0 -73
  465. oscura/exceptions.py +0 -59
  466. oscura/exploratory/fuzzy.py +0 -513
  467. oscura/exploratory/sync.py +0 -384
  468. oscura/exporters/__init__.py +0 -94
  469. oscura/exporters/csv.py +0 -303
  470. oscura/exporters/exporters.py +0 -44
  471. oscura/exporters/hdf5.py +0 -217
  472. oscura/exporters/html_export.py +0 -701
  473. oscura/exporters/json_export.py +0 -291
  474. oscura/exporters/markdown_export.py +0 -367
  475. oscura/exporters/matlab_export.py +0 -354
  476. oscura/exporters/npz_export.py +0 -219
  477. oscura/exporters/spice_export.py +0 -210
  478. oscura/search/context.py +0 -149
  479. oscura/session/__init__.py +0 -34
  480. oscura/session/annotations.py +0 -289
  481. oscura/session/history.py +0 -313
  482. oscura/session/session.py +0 -520
  483. oscura/workflow/__init__.py +0 -13
  484. oscura-0.5.1.dist-info/METADATA +0 -583
  485. oscura-0.5.1.dist-info/RECORD +0 -481
  486. /oscura/core/{config.py → config/legacy.py} +0 -0
  487. /oscura/{extensibility → core/extensibility}/__init__.py +0 -0
  488. /oscura/{extensibility → core/extensibility}/registry.py +0 -0
  489. /oscura/{plugins → core/plugins}/isolation.py +0 -0
  490. /oscura/{schemas → core/schemas}/bus_configuration.json +0 -0
  491. /oscura/{builders → utils/builders}/signal_builder.py +0 -0
  492. /oscura/{optimization → utils/optimization}/parallel.py +0 -0
  493. /oscura/{pipeline → utils/pipeline}/__init__.py +0 -0
  494. /oscura/{streaming → utils/streaming}/__init__.py +0 -0
  495. {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/WHEEL +0 -0
  496. {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/entry_points.txt +0 -0
  497. {oscura-0.5.1.dist-info → oscura-0.7.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
+ ]