scitex 2.5.0__py3-none-any.whl → 2.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 (1091) hide show
  1. scitex/__init__.py +19 -8
  2. scitex/__main__.py +2 -1
  3. scitex/__version__.py +1 -1
  4. scitex/_optional_deps.py +13 -20
  5. scitex/ai/__init__.py +5 -0
  6. scitex/ai/_gen_ai/_Anthropic.py +3 -1
  7. scitex/ai/_gen_ai/_BaseGenAI.py +3 -2
  8. scitex/ai/_gen_ai/_DeepSeek.py +1 -1
  9. scitex/ai/_gen_ai/_Google.py +3 -2
  10. scitex/ai/_gen_ai/_Llama.py +4 -2
  11. scitex/ai/_gen_ai/_OpenAI.py +3 -1
  12. scitex/ai/_gen_ai/_PARAMS.py +1 -0
  13. scitex/ai/_gen_ai/_Perplexity.py +3 -1
  14. scitex/ai/_gen_ai/__init__.py +1 -0
  15. scitex/ai/_gen_ai/_format_output_func.py +3 -1
  16. scitex/ai/classification/CrossValidationExperiment.py +8 -14
  17. scitex/ai/classification/examples/timeseries_cv_demo.py +128 -112
  18. scitex/ai/classification/reporters/_BaseClassificationReporter.py +2 -0
  19. scitex/ai/classification/reporters/_ClassificationReporter.py +30 -45
  20. scitex/ai/classification/reporters/_MultiClassificationReporter.py +8 -11
  21. scitex/ai/classification/reporters/_SingleClassificationReporter.py +126 -182
  22. scitex/ai/classification/reporters/__init__.py +1 -1
  23. scitex/ai/classification/reporters/reporter_utils/_Plotter.py +213 -119
  24. scitex/ai/classification/reporters/reporter_utils/__init__.py +28 -36
  25. scitex/ai/classification/reporters/reporter_utils/aggregation.py +125 -143
  26. scitex/ai/classification/reporters/reporter_utils/data_models.py +128 -120
  27. scitex/ai/classification/reporters/reporter_utils/reporting.py +507 -340
  28. scitex/ai/classification/reporters/reporter_utils/storage.py +4 -1
  29. scitex/ai/classification/reporters/reporter_utils/validation.py +141 -154
  30. scitex/ai/classification/timeseries/_TimeSeriesBlockingSplit.py +204 -129
  31. scitex/ai/classification/timeseries/_TimeSeriesCalendarSplit.py +215 -171
  32. scitex/ai/classification/timeseries/_TimeSeriesMetadata.py +17 -17
  33. scitex/ai/classification/timeseries/_TimeSeriesSlidingWindowSplit.py +67 -143
  34. scitex/ai/classification/timeseries/_TimeSeriesSlidingWindowSplit_v01-not-using-n_splits.py +67 -143
  35. scitex/ai/classification/timeseries/_TimeSeriesStrategy.py +12 -13
  36. scitex/ai/classification/timeseries/_TimeSeriesStratifiedSplit.py +231 -144
  37. scitex/ai/classification/timeseries/__init__.py +2 -4
  38. scitex/ai/classification/timeseries/_normalize_timestamp.py +3 -0
  39. scitex/ai/clustering/_pca.py +0 -1
  40. scitex/ai/clustering/_umap.py +1 -2
  41. scitex/ai/feature_extraction/__init__.py +10 -8
  42. scitex/ai/feature_extraction/vit.py +0 -1
  43. scitex/ai/feature_selection/feature_selection.py +3 -8
  44. scitex/ai/metrics/_calc_conf_mat.py +2 -0
  45. scitex/ai/metrics/_calc_feature_importance.py +3 -7
  46. scitex/ai/metrics/_calc_pre_rec_auc.py +5 -5
  47. scitex/ai/metrics/_calc_roc_auc.py +4 -2
  48. scitex/ai/metrics/_calc_seizure_prediction_metrics.py +35 -20
  49. scitex/ai/metrics/_calc_silhouette_score.py +1 -3
  50. scitex/ai/optim/Ranger_Deep_Learning_Optimizer/ranger/ranger.py +0 -3
  51. scitex/ai/optim/Ranger_Deep_Learning_Optimizer/ranger/ranger2020.py +0 -3
  52. scitex/ai/optim/Ranger_Deep_Learning_Optimizer/ranger/ranger913A.py +0 -3
  53. scitex/ai/optim/_optimizers.py +1 -1
  54. scitex/ai/plt/__init__.py +6 -1
  55. scitex/ai/plt/_plot_feature_importance.py +1 -3
  56. scitex/ai/plt/_plot_learning_curve.py +9 -24
  57. scitex/ai/plt/_plot_optuna_study.py +4 -3
  58. scitex/ai/plt/_plot_pre_rec_curve.py +9 -15
  59. scitex/ai/plt/_plot_roc_curve.py +6 -8
  60. scitex/ai/plt/_stx_conf_mat.py +121 -122
  61. scitex/ai/sampling/undersample.py +3 -2
  62. scitex/ai/sklearn/__init__.py +2 -2
  63. scitex/ai/training/_LearningCurveLogger.py +23 -10
  64. scitex/ai/utils/_check_params.py +0 -1
  65. scitex/benchmark/__init__.py +15 -25
  66. scitex/benchmark/benchmark.py +124 -117
  67. scitex/benchmark/monitor.py +117 -107
  68. scitex/benchmark/profiler.py +61 -58
  69. scitex/bridge/__init__.py +110 -0
  70. scitex/bridge/_helpers.py +149 -0
  71. scitex/bridge/_plt_vis.py +529 -0
  72. scitex/bridge/_protocol.py +283 -0
  73. scitex/bridge/_stats_plt.py +261 -0
  74. scitex/bridge/_stats_vis.py +265 -0
  75. scitex/browser/__init__.py +0 -2
  76. scitex/browser/auth/__init__.py +0 -0
  77. scitex/browser/auth/google.py +16 -11
  78. scitex/browser/automation/CookieHandler.py +2 -3
  79. scitex/browser/collaboration/__init__.py +3 -0
  80. scitex/browser/collaboration/auth_helpers.py +3 -1
  81. scitex/browser/collaboration/collaborative_agent.py +2 -0
  82. scitex/browser/collaboration/interactive_panel.py +2 -2
  83. scitex/browser/collaboration/shared_session.py +20 -11
  84. scitex/browser/collaboration/standard_interactions.py +1 -0
  85. scitex/browser/core/BrowserMixin.py +12 -30
  86. scitex/browser/core/ChromeProfileManager.py +9 -24
  87. scitex/browser/debugging/_browser_logger.py +15 -25
  88. scitex/browser/debugging/_failure_capture.py +9 -2
  89. scitex/browser/debugging/_highlight_element.py +15 -6
  90. scitex/browser/debugging/_show_grid.py +5 -6
  91. scitex/browser/debugging/_sync_session.py +4 -3
  92. scitex/browser/debugging/_test_monitor.py +14 -5
  93. scitex/browser/debugging/_visual_cursor.py +46 -35
  94. scitex/browser/interaction/click_center.py +4 -3
  95. scitex/browser/interaction/click_with_fallbacks.py +7 -10
  96. scitex/browser/interaction/close_popups.py +79 -66
  97. scitex/browser/interaction/fill_with_fallbacks.py +8 -8
  98. scitex/browser/pdf/__init__.py +3 -1
  99. scitex/browser/pdf/click_download_for_chrome_pdf_viewer.py +11 -10
  100. scitex/browser/pdf/detect_chrome_pdf_viewer.py +3 -6
  101. scitex/browser/remote/CaptchaHandler.py +109 -96
  102. scitex/browser/remote/ZenRowsAPIClient.py +91 -97
  103. scitex/browser/remote/ZenRowsBrowserManager.py +138 -112
  104. scitex/browser/stealth/HumanBehavior.py +4 -9
  105. scitex/browser/stealth/StealthManager.py +11 -26
  106. scitex/capture/__init__.py +17 -17
  107. scitex/capture/__main__.py +2 -3
  108. scitex/capture/capture.py +23 -51
  109. scitex/capture/cli.py +14 -39
  110. scitex/capture/gif.py +5 -9
  111. scitex/capture/mcp_server.py +7 -20
  112. scitex/capture/session.py +4 -3
  113. scitex/capture/utils.py +18 -53
  114. scitex/cli/__init__.py +1 -1
  115. scitex/cli/cloud.py +158 -116
  116. scitex/cli/config.py +224 -0
  117. scitex/cli/main.py +41 -40
  118. scitex/cli/scholar.py +60 -27
  119. scitex/cli/security.py +14 -20
  120. scitex/cli/web.py +87 -90
  121. scitex/cli/writer.py +51 -45
  122. scitex/cloud/__init__.py +14 -11
  123. scitex/cloud/_matplotlib_hook.py +6 -6
  124. scitex/config/README.md +313 -0
  125. scitex/config/{PriorityConfig.py → _PriorityConfig.py} +114 -17
  126. scitex/config/_ScitexConfig.py +319 -0
  127. scitex/config/__init__.py +41 -9
  128. scitex/config/_paths.py +325 -0
  129. scitex/config/default.yaml +81 -0
  130. scitex/context/_suppress_output.py +2 -3
  131. scitex/db/_BaseMixins/_BaseBackupMixin.py +3 -1
  132. scitex/db/_BaseMixins/_BaseBatchMixin.py +3 -1
  133. scitex/db/_BaseMixins/_BaseBlobMixin.py +3 -1
  134. scitex/db/_BaseMixins/_BaseImportExportMixin.py +1 -3
  135. scitex/db/_BaseMixins/_BaseIndexMixin.py +3 -1
  136. scitex/db/_BaseMixins/_BaseMaintenanceMixin.py +1 -3
  137. scitex/db/_BaseMixins/_BaseQueryMixin.py +3 -1
  138. scitex/db/_BaseMixins/_BaseRowMixin.py +3 -1
  139. scitex/db/_BaseMixins/_BaseTableMixin.py +3 -1
  140. scitex/db/_BaseMixins/_BaseTransactionMixin.py +1 -3
  141. scitex/db/_BaseMixins/__init__.py +1 -1
  142. scitex/db/__init__.py +9 -1
  143. scitex/db/__main__.py +8 -21
  144. scitex/db/_check_health.py +15 -31
  145. scitex/db/_delete_duplicates.py +7 -4
  146. scitex/db/_inspect.py +22 -38
  147. scitex/db/_inspect_optimized.py +89 -85
  148. scitex/db/_postgresql/_PostgreSQL.py +0 -1
  149. scitex/db/_postgresql/_PostgreSQLMixins/_BlobMixin.py +3 -1
  150. scitex/db/_postgresql/_PostgreSQLMixins/_ConnectionMixin.py +1 -3
  151. scitex/db/_postgresql/_PostgreSQLMixins/_ImportExportMixin.py +1 -3
  152. scitex/db/_postgresql/_PostgreSQLMixins/_MaintenanceMixin.py +1 -4
  153. scitex/db/_postgresql/_PostgreSQLMixins/_QueryMixin.py +3 -3
  154. scitex/db/_postgresql/_PostgreSQLMixins/_RowMixin.py +3 -1
  155. scitex/db/_postgresql/_PostgreSQLMixins/_TransactionMixin.py +1 -3
  156. scitex/db/_postgresql/__init__.py +1 -1
  157. scitex/db/_sqlite3/_SQLite3.py +2 -4
  158. scitex/db/_sqlite3/_SQLite3Mixins/_ArrayMixin.py +11 -12
  159. scitex/db/_sqlite3/_SQLite3Mixins/_ArrayMixin_v01-need-_hash-col.py +19 -14
  160. scitex/db/_sqlite3/_SQLite3Mixins/_BatchMixin.py +3 -1
  161. scitex/db/_sqlite3/_SQLite3Mixins/_BlobMixin.py +7 -7
  162. scitex/db/_sqlite3/_SQLite3Mixins/_ColumnMixin.py +118 -111
  163. scitex/db/_sqlite3/_SQLite3Mixins/_ConnectionMixin.py +8 -10
  164. scitex/db/_sqlite3/_SQLite3Mixins/_GitMixin.py +17 -45
  165. scitex/db/_sqlite3/_SQLite3Mixins/_ImportExportMixin.py +1 -3
  166. scitex/db/_sqlite3/_SQLite3Mixins/_IndexMixin.py +3 -1
  167. scitex/db/_sqlite3/_SQLite3Mixins/_QueryMixin.py +3 -4
  168. scitex/db/_sqlite3/_SQLite3Mixins/_RowMixin.py +9 -9
  169. scitex/db/_sqlite3/_SQLite3Mixins/_TableMixin.py +18 -11
  170. scitex/db/_sqlite3/_SQLite3Mixins/__init__.py +1 -0
  171. scitex/db/_sqlite3/__init__.py +1 -1
  172. scitex/db/_sqlite3/_delete_duplicates.py +13 -11
  173. scitex/decorators/__init__.py +29 -4
  174. scitex/decorators/_auto_order.py +43 -43
  175. scitex/decorators/_batch_fn.py +12 -6
  176. scitex/decorators/_cache_disk.py +8 -9
  177. scitex/decorators/_cache_disk_async.py +8 -7
  178. scitex/decorators/_combined.py +19 -13
  179. scitex/decorators/_converters.py +16 -3
  180. scitex/decorators/_deprecated.py +32 -22
  181. scitex/decorators/_numpy_fn.py +18 -4
  182. scitex/decorators/_pandas_fn.py +17 -5
  183. scitex/decorators/_signal_fn.py +17 -3
  184. scitex/decorators/_torch_fn.py +32 -15
  185. scitex/decorators/_xarray_fn.py +23 -9
  186. scitex/dev/_analyze_code_flow.py +0 -2
  187. scitex/dict/_DotDict.py +15 -19
  188. scitex/dict/_flatten.py +1 -0
  189. scitex/dict/_listed_dict.py +1 -0
  190. scitex/dict/_pop_keys.py +1 -0
  191. scitex/dict/_replace.py +1 -0
  192. scitex/dict/_safe_merge.py +1 -0
  193. scitex/dict/_to_str.py +2 -3
  194. scitex/dsp/__init__.py +13 -4
  195. scitex/dsp/_crop.py +3 -1
  196. scitex/dsp/_detect_ripples.py +3 -1
  197. scitex/dsp/_modulation_index.py +3 -1
  198. scitex/dsp/_time.py +3 -1
  199. scitex/dsp/_wavelet.py +0 -1
  200. scitex/dsp/example.py +0 -5
  201. scitex/dsp/filt.py +4 -0
  202. scitex/dsp/utils/__init__.py +4 -1
  203. scitex/dsp/utils/pac.py +3 -3
  204. scitex/dt/_normalize_timestamp.py +4 -1
  205. scitex/errors.py +3 -6
  206. scitex/etc/__init__.py +1 -1
  207. scitex/gen/_DimHandler.py +6 -6
  208. scitex/gen/__init__.py +5 -1
  209. scitex/gen/_deprecated_close.py +1 -0
  210. scitex/gen/_deprecated_start.py +5 -3
  211. scitex/gen/_detect_environment.py +44 -41
  212. scitex/gen/_detect_notebook_path.py +51 -47
  213. scitex/gen/_embed.py +1 -1
  214. scitex/gen/_get_notebook_path.py +81 -62
  215. scitex/gen/_inspect_module.py +0 -1
  216. scitex/gen/_norm.py +16 -7
  217. scitex/gen/_norm_cache.py +78 -65
  218. scitex/gen/_print_config.py +0 -3
  219. scitex/gen/_src.py +2 -3
  220. scitex/gen/_title_case.py +3 -2
  221. scitex/gen/_to_even.py +8 -8
  222. scitex/gen/_transpose.py +3 -3
  223. scitex/gen/misc.py +0 -3
  224. scitex/gists/_SigMacro_processFigure_S.py +2 -2
  225. scitex/gists/_SigMacro_toBlue.py +2 -2
  226. scitex/gists/__init__.py +4 -1
  227. scitex/git/_branch.py +19 -11
  228. scitex/git/_clone.py +23 -15
  229. scitex/git/_commit.py +10 -12
  230. scitex/git/_init.py +15 -38
  231. scitex/git/_remote.py +9 -3
  232. scitex/git/_result.py +3 -0
  233. scitex/git/_retry.py +2 -5
  234. scitex/git/_types.py +4 -0
  235. scitex/git/_validation.py +8 -8
  236. scitex/git/_workflow.py +4 -4
  237. scitex/io/__init__.py +2 -1
  238. scitex/io/_glob.py +2 -2
  239. scitex/io/_json2md.py +3 -3
  240. scitex/io/_load.py +6 -8
  241. scitex/io/_load_cache.py +71 -71
  242. scitex/io/_load_configs.py +2 -3
  243. scitex/io/_load_modules/_H5Explorer.py +6 -12
  244. scitex/io/_load_modules/_ZarrExplorer.py +3 -3
  245. scitex/io/_load_modules/_bibtex.py +62 -63
  246. scitex/io/_load_modules/_canvas.py +4 -9
  247. scitex/io/_load_modules/_catboost.py +7 -2
  248. scitex/io/_load_modules/_hdf5.py +2 -0
  249. scitex/io/_load_modules/_image.py +5 -1
  250. scitex/io/_load_modules/_matlab.py +3 -1
  251. scitex/io/_load_modules/_optuna.py +0 -1
  252. scitex/io/_load_modules/_pdf.py +38 -29
  253. scitex/io/_load_modules/_sqlite3.py +1 -0
  254. scitex/io/_load_modules/_txt.py +2 -0
  255. scitex/io/_load_modules/_xml.py +9 -9
  256. scitex/io/_load_modules/_zarr.py +12 -10
  257. scitex/io/_metadata.py +76 -37
  258. scitex/io/_qr_utils.py +18 -13
  259. scitex/io/_save.py +220 -63
  260. scitex/io/_save_modules/__init__.py +7 -2
  261. scitex/io/_save_modules/_bibtex.py +66 -61
  262. scitex/io/_save_modules/_canvas.py +5 -6
  263. scitex/io/_save_modules/_catboost.py +2 -2
  264. scitex/io/_save_modules/_csv.py +4 -4
  265. scitex/io/_save_modules/_excel.py +5 -9
  266. scitex/io/_save_modules/_hdf5.py +9 -21
  267. scitex/io/_save_modules/_html.py +5 -5
  268. scitex/io/_save_modules/_image.py +105 -8
  269. scitex/io/_save_modules/_joblib.py +2 -2
  270. scitex/io/_save_modules/_json.py +51 -6
  271. scitex/io/_save_modules/_listed_dfs_as_csv.py +2 -1
  272. scitex/io/_save_modules/_listed_scalars_as_csv.py +2 -1
  273. scitex/io/_save_modules/_matlab.py +2 -2
  274. scitex/io/_save_modules/_numpy.py +6 -8
  275. scitex/io/_save_modules/_pickle.py +4 -4
  276. scitex/io/_save_modules/_plotly.py +3 -3
  277. scitex/io/_save_modules/_tex.py +23 -25
  278. scitex/io/_save_modules/_text.py +2 -2
  279. scitex/io/_save_modules/_yaml.py +9 -9
  280. scitex/io/_save_modules/_zarr.py +15 -15
  281. scitex/io/utils/__init__.py +2 -1
  282. scitex/io/utils/h5_to_zarr.py +173 -155
  283. scitex/linalg/__init__.py +1 -1
  284. scitex/linalg/_geometric_median.py +4 -3
  285. scitex/logging/_Tee.py +5 -7
  286. scitex/logging/__init__.py +18 -19
  287. scitex/logging/_config.py +4 -1
  288. scitex/logging/_context.py +6 -5
  289. scitex/logging/_formatters.py +2 -3
  290. scitex/logging/_handlers.py +19 -20
  291. scitex/logging/_levels.py +9 -17
  292. scitex/logging/_logger.py +74 -15
  293. scitex/logging/_print_capture.py +17 -17
  294. scitex/nn/_BNet.py +1 -3
  295. scitex/nn/_Filters.py +6 -2
  296. scitex/nn/_ModulationIndex.py +3 -1
  297. scitex/nn/_PAC.py +3 -2
  298. scitex/nn/_PSD.py +0 -1
  299. scitex/nn/__init__.py +16 -3
  300. scitex/path/_clean.py +10 -8
  301. scitex/path/_find.py +1 -1
  302. scitex/path/_get_spath.py +1 -2
  303. scitex/path/_mk_spath.py +1 -1
  304. scitex/path/_symlink.py +5 -10
  305. scitex/pd/__init__.py +4 -1
  306. scitex/pd/_force_df.py +24 -24
  307. scitex/pd/_get_unique.py +1 -0
  308. scitex/pd/_merge_columns.py +1 -1
  309. scitex/pd/_round.py +11 -7
  310. scitex/pd/_to_xy.py +0 -1
  311. scitex/plt/REQUESTS.md +191 -0
  312. scitex/plt/__init__.py +185 -87
  313. scitex/plt/_subplots/_AxesWrapper.py +22 -6
  314. scitex/plt/_subplots/_AxisWrapper.py +100 -39
  315. scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin.py +74 -52
  316. scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin.py +183 -73
  317. scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin.py +61 -45
  318. scitex/plt/_subplots/_AxisWrapperMixins/_TrackingMixin.py +26 -14
  319. scitex/plt/_subplots/_AxisWrapperMixins/_UnitAwareMixin.py +80 -73
  320. scitex/plt/_subplots/_FigWrapper.py +93 -60
  321. scitex/plt/_subplots/_SubplotsWrapper.py +135 -68
  322. scitex/plt/_subplots/__init__.py +10 -0
  323. scitex/plt/_subplots/_export_as_csv.py +89 -47
  324. scitex/plt/_subplots/_export_as_csv_formatters/__init__.py +1 -0
  325. scitex/plt/_subplots/_export_as_csv_formatters/_format_annotate.py +6 -4
  326. scitex/plt/_subplots/_export_as_csv_formatters/_format_bar.py +88 -38
  327. scitex/plt/_subplots/_export_as_csv_formatters/_format_barh.py +25 -31
  328. scitex/plt/_subplots/_export_as_csv_formatters/_format_boxplot.py +53 -23
  329. scitex/plt/_subplots/_export_as_csv_formatters/_format_contour.py +38 -25
  330. scitex/plt/_subplots/_export_as_csv_formatters/_format_contourf.py +17 -9
  331. scitex/plt/_subplots/_export_as_csv_formatters/_format_errorbar.py +70 -124
  332. scitex/plt/_subplots/_export_as_csv_formatters/_format_eventplot.py +12 -10
  333. scitex/plt/_subplots/_export_as_csv_formatters/_format_fill.py +31 -17
  334. scitex/plt/_subplots/_export_as_csv_formatters/_format_fill_between.py +33 -21
  335. scitex/plt/_subplots/_export_as_csv_formatters/_format_hexbin.py +14 -4
  336. scitex/plt/_subplots/_export_as_csv_formatters/_format_hist.py +43 -29
  337. scitex/plt/_subplots/_export_as_csv_formatters/_format_hist2d.py +14 -4
  338. scitex/plt/_subplots/_export_as_csv_formatters/_format_imshow.py +27 -11
  339. scitex/plt/_subplots/_export_as_csv_formatters/_format_imshow2d.py +7 -5
  340. scitex/plt/_subplots/_export_as_csv_formatters/_format_matshow.py +9 -7
  341. scitex/plt/_subplots/_export_as_csv_formatters/_format_pie.py +15 -6
  342. scitex/plt/_subplots/_export_as_csv_formatters/_format_plot.py +85 -46
  343. scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_box.py +52 -27
  344. scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_imshow.py +1 -0
  345. scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_kde.py +16 -17
  346. scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_scatter.py +7 -5
  347. scitex/plt/_subplots/_export_as_csv_formatters/_format_quiver.py +10 -8
  348. scitex/plt/_subplots/_export_as_csv_formatters/_format_scatter.py +17 -6
  349. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_barplot.py +43 -26
  350. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_boxplot.py +68 -47
  351. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_heatmap.py +52 -64
  352. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_histplot.py +55 -50
  353. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_jointplot.py +9 -11
  354. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_kdeplot.py +63 -29
  355. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_lineplot.py +4 -4
  356. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_pairplot.py +6 -4
  357. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_scatterplot.py +44 -40
  358. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_stripplot.py +46 -39
  359. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_swarmplot.py +46 -39
  360. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_violinplot.py +75 -94
  361. scitex/plt/_subplots/_export_as_csv_formatters/_format_stem.py +12 -3
  362. scitex/plt/_subplots/_export_as_csv_formatters/_format_step.py +12 -3
  363. scitex/plt/_subplots/_export_as_csv_formatters/_format_streamplot.py +10 -8
  364. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_conf_mat.py +17 -15
  365. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_ecdf.py +10 -9
  366. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_fillv.py +35 -31
  367. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_heatmap.py +18 -18
  368. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_image.py +24 -18
  369. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_joyplot.py +9 -7
  370. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_line.py +34 -23
  371. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_mean_ci.py +15 -13
  372. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_mean_std.py +12 -10
  373. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_median_iqr.py +15 -13
  374. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_raster.py +11 -9
  375. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_rectangle.py +84 -56
  376. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_scatter_hist.py +35 -32
  377. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_shaded_line.py +46 -30
  378. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_violin.py +51 -51
  379. scitex/plt/_subplots/_export_as_csv_formatters/_format_text.py +32 -31
  380. scitex/plt/_subplots/_export_as_csv_formatters/_format_violin.py +34 -31
  381. scitex/plt/_subplots/_export_as_csv_formatters/_format_violinplot.py +44 -37
  382. scitex/plt/_subplots/_export_as_csv_formatters/verify_formatters.py +91 -74
  383. scitex/plt/_tpl.py +6 -5
  384. scitex/plt/ax/_plot/__init__.py +24 -0
  385. scitex/plt/ax/_plot/_add_fitted_line.py +12 -11
  386. scitex/plt/ax/_plot/_plot_circular_hist.py +3 -1
  387. scitex/plt/ax/_plot/_plot_statistical_shaded_line.py +25 -19
  388. scitex/plt/ax/_plot/_stx_conf_mat.py +6 -3
  389. scitex/plt/ax/_plot/_stx_ecdf.py +5 -3
  390. scitex/plt/ax/_plot/_stx_fillv.py +4 -2
  391. scitex/plt/ax/_plot/_stx_heatmap.py +7 -4
  392. scitex/plt/ax/_plot/_stx_image.py +7 -5
  393. scitex/plt/ax/_plot/_stx_joyplot.py +32 -10
  394. scitex/plt/ax/_plot/_stx_raster.py +26 -11
  395. scitex/plt/ax/_plot/_stx_rectangle.py +2 -2
  396. scitex/plt/ax/_plot/_stx_shaded_line.py +15 -11
  397. scitex/plt/ax/_plot/_stx_violin.py +3 -1
  398. scitex/plt/ax/_style/_add_marginal_ax.py +6 -4
  399. scitex/plt/ax/_style/_auto_scale_axis.py +14 -10
  400. scitex/plt/ax/_style/_extend.py +3 -1
  401. scitex/plt/ax/_style/_force_aspect.py +5 -3
  402. scitex/plt/ax/_style/_format_units.py +2 -2
  403. scitex/plt/ax/_style/_hide_spines.py +5 -1
  404. scitex/plt/ax/_style/_map_ticks.py +5 -3
  405. scitex/plt/ax/_style/_rotate_labels.py +5 -4
  406. scitex/plt/ax/_style/_rotate_labels_v01.py +73 -63
  407. scitex/plt/ax/_style/_set_log_scale.py +120 -85
  408. scitex/plt/ax/_style/_set_meta.py +99 -76
  409. scitex/plt/ax/_style/_set_supxyt.py +33 -16
  410. scitex/plt/ax/_style/_set_xyt.py +27 -18
  411. scitex/plt/ax/_style/_share_axes.py +15 -5
  412. scitex/plt/ax/_style/_show_spines.py +58 -57
  413. scitex/plt/ax/_style/_style_barplot.py +1 -1
  414. scitex/plt/ax/_style/_style_boxplot.py +25 -14
  415. scitex/plt/ax/_style/_style_errorbar.py +0 -0
  416. scitex/plt/ax/_style/_style_scatter.py +1 -1
  417. scitex/plt/ax/_style/_style_suptitles.py +3 -3
  418. scitex/plt/ax/_style/_style_violinplot.py +8 -2
  419. scitex/plt/color/__init__.py +34 -2
  420. scitex/plt/color/_add_hue_col.py +1 -0
  421. scitex/plt/color/_colors.py +0 -1
  422. scitex/plt/color/_get_colors_from_conf_matap.py +3 -1
  423. scitex/plt/color/_vizualize_colors.py +0 -1
  424. scitex/plt/docs/FIGURE_ARCHITECTURE.md +155 -97
  425. scitex/plt/gallery/README.md +75 -0
  426. scitex/plt/gallery/__init__.py +29 -0
  427. scitex/plt/gallery/_generate.py +153 -0
  428. scitex/plt/gallery/_plots.py +594 -0
  429. scitex/plt/gallery/_registry.py +153 -0
  430. scitex/plt/styles/__init__.py +9 -9
  431. scitex/plt/styles/_plot_defaults.py +62 -61
  432. scitex/plt/styles/_plot_postprocess.py +126 -77
  433. scitex/plt/styles/_style_loader.py +0 -0
  434. scitex/plt/styles/presets.py +43 -18
  435. scitex/plt/templates/research-master/scitex/vis/gallery/area/fill_between.json +110 -0
  436. scitex/plt/templates/research-master/scitex/vis/gallery/area/fill_betweenx.json +88 -0
  437. scitex/plt/templates/research-master/scitex/vis/gallery/area/stx_fill_between.json +103 -0
  438. scitex/plt/templates/research-master/scitex/vis/gallery/area/stx_fillv.json +106 -0
  439. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/bar.json +92 -0
  440. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/barh.json +92 -0
  441. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/boxplot.json +92 -0
  442. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_bar.json +84 -0
  443. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_barh.json +84 -0
  444. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_box.json +83 -0
  445. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_boxplot.json +93 -0
  446. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_violin.json +91 -0
  447. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_violinplot.json +91 -0
  448. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/violinplot.json +91 -0
  449. scitex/plt/templates/research-master/scitex/vis/gallery/contour/contour.json +97 -0
  450. scitex/plt/templates/research-master/scitex/vis/gallery/contour/contourf.json +98 -0
  451. scitex/plt/templates/research-master/scitex/vis/gallery/contour/stx_contour.json +84 -0
  452. scitex/plt/templates/research-master/scitex/vis/gallery/distribution/hist.json +101 -0
  453. scitex/plt/templates/research-master/scitex/vis/gallery/distribution/hist2d.json +96 -0
  454. scitex/plt/templates/research-master/scitex/vis/gallery/distribution/stx_ecdf.json +95 -0
  455. scitex/plt/templates/research-master/scitex/vis/gallery/distribution/stx_joyplot.json +95 -0
  456. scitex/plt/templates/research-master/scitex/vis/gallery/distribution/stx_kde.json +93 -0
  457. scitex/plt/templates/research-master/scitex/vis/gallery/grid/imshow.json +95 -0
  458. scitex/plt/templates/research-master/scitex/vis/gallery/grid/matshow.json +95 -0
  459. scitex/plt/templates/research-master/scitex/vis/gallery/grid/stx_conf_mat.json +83 -0
  460. scitex/plt/templates/research-master/scitex/vis/gallery/grid/stx_heatmap.json +92 -0
  461. scitex/plt/templates/research-master/scitex/vis/gallery/grid/stx_image.json +121 -0
  462. scitex/plt/templates/research-master/scitex/vis/gallery/grid/stx_imshow.json +84 -0
  463. scitex/plt/templates/research-master/scitex/vis/gallery/line/plot.json +110 -0
  464. scitex/plt/templates/research-master/scitex/vis/gallery/line/step.json +92 -0
  465. scitex/plt/templates/research-master/scitex/vis/gallery/line/stx_line.json +95 -0
  466. scitex/plt/templates/research-master/scitex/vis/gallery/line/stx_shaded_line.json +96 -0
  467. scitex/plt/templates/research-master/scitex/vis/gallery/scatter/hexbin.json +95 -0
  468. scitex/plt/templates/research-master/scitex/vis/gallery/scatter/scatter.json +95 -0
  469. scitex/plt/templates/research-master/scitex/vis/gallery/scatter/stem.json +92 -0
  470. scitex/plt/templates/research-master/scitex/vis/gallery/scatter/stx_scatter.json +84 -0
  471. scitex/plt/templates/research-master/scitex/vis/gallery/special/pie.json +94 -0
  472. scitex/plt/templates/research-master/scitex/vis/gallery/special/stx_raster.json +109 -0
  473. scitex/plt/templates/research-master/scitex/vis/gallery/special/stx_rectangle.json +108 -0
  474. scitex/plt/templates/research-master/scitex/vis/gallery/statistical/errorbar.json +93 -0
  475. scitex/plt/templates/research-master/scitex/vis/gallery/statistical/stx_errorbar.json +84 -0
  476. scitex/plt/templates/research-master/scitex/vis/gallery/statistical/stx_mean_ci.json +96 -0
  477. scitex/plt/templates/research-master/scitex/vis/gallery/statistical/stx_mean_std.json +96 -0
  478. scitex/plt/templates/research-master/scitex/vis/gallery/statistical/stx_median_iqr.json +96 -0
  479. scitex/plt/templates/research-master/scitex/vis/gallery/vector/quiver.json +99 -0
  480. scitex/plt/templates/research-master/scitex/vis/gallery/vector/streamplot.json +100 -0
  481. scitex/plt/utils/__init__.py +29 -2
  482. scitex/plt/utils/_close.py +8 -3
  483. scitex/plt/utils/_collect_figure_metadata.py +3031 -265
  484. scitex/plt/utils/_colorbar.py +15 -17
  485. scitex/plt/utils/_configure_mpl.py +22 -14
  486. scitex/plt/utils/_crop.py +60 -27
  487. scitex/plt/utils/_csv_column_naming.py +123 -72
  488. scitex/plt/utils/_dimension_viewer.py +7 -19
  489. scitex/plt/utils/_figure_from_axes_mm.py +70 -16
  490. scitex/plt/utils/_figure_mm.py +3 -2
  491. scitex/plt/utils/_get_actual_font.py +5 -4
  492. scitex/plt/utils/_histogram_utils.py +52 -48
  493. scitex/plt/utils/_is_valid_axis.py +19 -13
  494. scitex/plt/utils/_mk_colorbar.py +3 -3
  495. scitex/plt/utils/_scientific_captions.py +202 -139
  496. scitex/plt/utils/_scitex_config.py +98 -98
  497. scitex/plt/utils/_units.py +0 -0
  498. scitex/plt/utils/metadata/__init__.py +36 -0
  499. scitex/plt/utils/metadata/_artist_extraction.py +119 -0
  500. scitex/plt/utils/metadata/_axes_metadata.py +93 -0
  501. scitex/plt/utils/metadata/_collection_artists.py +292 -0
  502. scitex/plt/utils/metadata/_core.py +208 -0
  503. scitex/plt/utils/metadata/_csv_column_extraction.py +186 -0
  504. scitex/plt/utils/metadata/_csv_hash.py +115 -0
  505. scitex/plt/utils/metadata/_csv_verification.py +95 -0
  506. scitex/plt/utils/metadata/_data_linkage.py +263 -0
  507. scitex/plt/utils/metadata/_dimensions.py +239 -0
  508. scitex/plt/utils/metadata/_figure_metadata.py +58 -0
  509. scitex/plt/utils/metadata/_image_text_artists.py +168 -0
  510. scitex/plt/utils/metadata/_label_parsing.py +82 -0
  511. scitex/plt/utils/metadata/_legend_extraction.py +120 -0
  512. scitex/plt/utils/metadata/_line_artists.py +367 -0
  513. scitex/plt/utils/metadata/_line_semantic_handling.py +173 -0
  514. scitex/plt/utils/metadata/_patch_artists.py +211 -0
  515. scitex/plt/utils/metadata/_plot_content.py +26 -0
  516. scitex/plt/utils/metadata/_plot_type_detection.py +184 -0
  517. scitex/plt/utils/metadata/_precision.py +134 -0
  518. scitex/plt/utils/metadata/_precision_config.py +68 -0
  519. scitex/plt/utils/metadata/_precision_sections.py +211 -0
  520. scitex/plt/utils/metadata/_recipe_extraction.py +267 -0
  521. scitex/plt/utils/metadata/_style_parsing.py +174 -0
  522. scitex/repro/_RandomStateManager.py +33 -38
  523. scitex/repro/__init__.py +16 -7
  524. scitex/repro/_gen_ID.py +7 -9
  525. scitex/repro/_gen_timestamp.py +7 -6
  526. scitex/repro/_hash_array.py +8 -12
  527. scitex/reproduce/__init__.py +1 -1
  528. scitex/resource/_get_processor_usages.py +3 -1
  529. scitex/resource/_log_processor_usages.py +3 -1
  530. scitex/rng/__init__.py +1 -1
  531. scitex/schema/README.md +178 -0
  532. scitex/schema/__init__.py +144 -0
  533. scitex/schema/_canvas.py +444 -0
  534. scitex/schema/_stats.py +762 -0
  535. scitex/schema/_validation.py +590 -0
  536. scitex/scholar/.legacy/Scholar.py +5 -12
  537. scitex/scholar/.legacy/_Scholar.py +66 -99
  538. scitex/scholar/.legacy/_ScholarAPI.py +75 -66
  539. scitex/scholar/.legacy/_tmp/search_engine/_BaseSearchEngine.py +3 -3
  540. scitex/scholar/.legacy/_tmp/search_engine/_UnifiedSearcher.py +4 -9
  541. scitex/scholar/.legacy/_tmp/search_engine/__init__.py +14 -21
  542. scitex/scholar/.legacy/_tmp/search_engine/local/_LocalSearchEngine.py +40 -37
  543. scitex/scholar/.legacy/_tmp/search_engine/local/_VectorSearchEngine.py +31 -28
  544. scitex/scholar/.legacy/_tmp/search_engine/web/_ArxivSearchEngine.py +74 -65
  545. scitex/scholar/.legacy/_tmp/search_engine/web/_CrossRefSearchEngine.py +122 -116
  546. scitex/scholar/.legacy/_tmp/search_engine/web/_GoogleScholarSearchEngine.py +65 -59
  547. scitex/scholar/.legacy/_tmp/search_engine/web/_PubMedSearchEngine.py +121 -107
  548. scitex/scholar/.legacy/_tmp/search_engine/web/_SemanticScholarSearchEngine.py +5 -12
  549. scitex/scholar/.legacy/database/_DatabaseEntry.py +49 -45
  550. scitex/scholar/.legacy/database/_DatabaseIndex.py +131 -94
  551. scitex/scholar/.legacy/database/_LibraryManager.py +65 -63
  552. scitex/scholar/.legacy/database/_PaperDatabase.py +138 -124
  553. scitex/scholar/.legacy/database/_ScholarDatabaseIntegration.py +14 -36
  554. scitex/scholar/.legacy/database/_StorageIntegratedDB.py +192 -156
  555. scitex/scholar/.legacy/database/_ZoteroCompatibleDB.py +300 -237
  556. scitex/scholar/.legacy/database/__init__.py +2 -1
  557. scitex/scholar/.legacy/database/manage.py +92 -84
  558. scitex/scholar/.legacy/lookup/_LookupIndex.py +157 -101
  559. scitex/scholar/.legacy/lookup/__init__.py +2 -1
  560. scitex/scholar/.legacy/metadata/doi/batch/_MetadataHandlerForBatchDOIResolution.py +4 -9
  561. scitex/scholar/.legacy/metadata/doi/batch/_ProgressManagerForBatchDOIResolution.py +10 -23
  562. scitex/scholar/.legacy/metadata/doi/batch/_SourceStatsManagerForBatchDOIResolution.py +4 -9
  563. scitex/scholar/.legacy/metadata/doi/batch/__init__.py +3 -1
  564. scitex/scholar/.legacy/metadata/doi/resolvers/_BatchDOIResolver.py +10 -25
  565. scitex/scholar/.legacy/metadata/doi/resolvers/_BibTeXDOIResolver.py +19 -49
  566. scitex/scholar/.legacy/metadata/doi/resolvers/_DOIResolver.py +1 -0
  567. scitex/scholar/.legacy/metadata/doi/resolvers/_SingleDOIResolver.py +8 -20
  568. scitex/scholar/.legacy/metadata/doi/sources/.combined-SemanticScholarSource/_SemanticScholarSource.py +37 -35
  569. scitex/scholar/.legacy/metadata/doi/sources/.combined-SemanticScholarSource/_SemanticScholarSourceEnhanced.py +49 -37
  570. scitex/scholar/.legacy/metadata/doi/sources/_ArXivSource.py +11 -30
  571. scitex/scholar/.legacy/metadata/doi/sources/_BaseDOISource.py +19 -47
  572. scitex/scholar/.legacy/metadata/doi/sources/_CrossRefLocalSource.py +1 -0
  573. scitex/scholar/.legacy/metadata/doi/sources/_CrossRefSource.py +12 -33
  574. scitex/scholar/.legacy/metadata/doi/sources/_OpenAlexSource.py +8 -20
  575. scitex/scholar/.legacy/metadata/doi/sources/_PubMedSource.py +10 -27
  576. scitex/scholar/.legacy/metadata/doi/sources/_SemanticScholarSource.py +11 -29
  577. scitex/scholar/.legacy/metadata/doi/sources/_SourceManager.py +8 -21
  578. scitex/scholar/.legacy/metadata/doi/sources/_SourceResolutionStrategy.py +24 -55
  579. scitex/scholar/.legacy/metadata/doi/sources/_SourceRotationManager.py +8 -21
  580. scitex/scholar/.legacy/metadata/doi/sources/_URLDOISource.py +9 -16
  581. scitex/scholar/.legacy/metadata/doi/sources/_UnifiedSource.py +8 -22
  582. scitex/scholar/.legacy/metadata/doi/sources/__init__.py +1 -0
  583. scitex/scholar/.legacy/metadata/doi/utils/_PubMedConverter.py +4 -8
  584. scitex/scholar/.legacy/metadata/doi/utils/_RateLimitHandler.py +17 -43
  585. scitex/scholar/.legacy/metadata/doi/utils/_TextNormalizer.py +8 -18
  586. scitex/scholar/.legacy/metadata/doi/utils/_URLDOIExtractor.py +4 -8
  587. scitex/scholar/.legacy/metadata/doi/utils/__init__.py +1 -0
  588. scitex/scholar/.legacy/metadata/doi/utils/_to_complete_metadata_structure.py +1 -0
  589. scitex/scholar/.legacy/metadata/enrichment/_LibraryEnricher.py +2 -3
  590. scitex/scholar/.legacy/metadata/enrichment/enrichers/_ImpactFactorEnricher.py +6 -12
  591. scitex/scholar/.legacy/metadata/enrichment/enrichers/_SmartEnricher.py +5 -10
  592. scitex/scholar/.legacy/metadata/enrichment/sources/_UnifiedMetadataSource.py +4 -5
  593. scitex/scholar/.legacy/metadata/query_to_full_meta_json.py +8 -12
  594. scitex/scholar/.legacy/metadata/urls/_URLMetadataHandler.py +3 -3
  595. scitex/scholar/.legacy/metadata/urls/_ZoteroTranslatorRunner.py +15 -21
  596. scitex/scholar/.legacy/metadata/urls/__init__.py +3 -3
  597. scitex/scholar/.legacy/metadata/urls/_finder.py +4 -6
  598. scitex/scholar/.legacy/metadata/urls/_handler.py +7 -15
  599. scitex/scholar/.legacy/metadata/urls/_resolver.py +6 -12
  600. scitex/scholar/.legacy/search/_Embedder.py +74 -69
  601. scitex/scholar/.legacy/search/_SemanticSearch.py +91 -90
  602. scitex/scholar/.legacy/search/_SemanticSearchEngine.py +104 -109
  603. scitex/scholar/.legacy/search/_UnifiedSearcher.py +530 -471
  604. scitex/scholar/.legacy/search/_VectorDatabase.py +111 -92
  605. scitex/scholar/.legacy/search/__init__.py +1 -0
  606. scitex/scholar/.legacy/storage/_EnhancedStorageManager.py +182 -154
  607. scitex/scholar/.legacy/storage/__init__.py +2 -1
  608. scitex/scholar/__init__.py +0 -2
  609. scitex/scholar/__main__.py +1 -3
  610. scitex/scholar/auth/ScholarAuthManager.py +13 -36
  611. scitex/scholar/auth/core/AuthenticationGateway.py +15 -29
  612. scitex/scholar/auth/core/BrowserAuthenticator.py +22 -57
  613. scitex/scholar/auth/core/StrategyResolver.py +10 -27
  614. scitex/scholar/auth/core/__init__.py +5 -1
  615. scitex/scholar/auth/gateway/_OpenURLLinkFinder.py +11 -21
  616. scitex/scholar/auth/gateway/_OpenURLResolver.py +10 -18
  617. scitex/scholar/auth/gateway/_resolve_functions.py +3 -3
  618. scitex/scholar/auth/providers/BaseAuthenticator.py +1 -0
  619. scitex/scholar/auth/providers/EZProxyAuthenticator.py +7 -14
  620. scitex/scholar/auth/providers/OpenAthensAuthenticator.py +29 -57
  621. scitex/scholar/auth/providers/ShibbolethAuthenticator.py +87 -73
  622. scitex/scholar/auth/session/AuthCacheManager.py +12 -22
  623. scitex/scholar/auth/session/SessionManager.py +4 -6
  624. scitex/scholar/auth/sso/BaseSSOAutomator.py +13 -19
  625. scitex/scholar/auth/sso/OpenAthensSSOAutomator.py +16 -45
  626. scitex/scholar/auth/sso/SSOAutomator.py +8 -15
  627. scitex/scholar/auth/sso/UniversityOfMelbourneSSOAutomator.py +13 -23
  628. scitex/scholar/browser/ScholarBrowserManager.py +31 -56
  629. scitex/scholar/browser/__init__.py +1 -0
  630. scitex/scholar/browser/utils/click_and_wait.py +3 -4
  631. scitex/scholar/browser/utils/close_unwanted_pages.py +4 -7
  632. scitex/scholar/browser/utils/wait_redirects.py +15 -40
  633. scitex/scholar/citation_graph/__init__.py +0 -0
  634. scitex/scholar/citation_graph/builder.py +3 -7
  635. scitex/scholar/citation_graph/database.py +4 -11
  636. scitex/scholar/citation_graph/example.py +5 -10
  637. scitex/scholar/citation_graph/models.py +0 -0
  638. scitex/scholar/cli/_url_utils.py +1 -1
  639. scitex/scholar/cli/chrome.py +5 -3
  640. scitex/scholar/cli/download_pdf.py +13 -14
  641. scitex/scholar/cli/handlers/bibtex_handler.py +4 -12
  642. scitex/scholar/cli/handlers/doi_handler.py +1 -3
  643. scitex/scholar/cli/handlers/project_handler.py +6 -20
  644. scitex/scholar/cli/open_browser.py +41 -39
  645. scitex/scholar/cli/open_browser_auto.py +31 -39
  646. scitex/scholar/cli/open_browser_monitored.py +27 -24
  647. scitex/scholar/config/ScholarConfig.py +5 -8
  648. scitex/scholar/config/__init__.py +1 -0
  649. scitex/scholar/config/core/_CascadeConfig.py +3 -3
  650. scitex/scholar/config/core/_PathManager.py +16 -28
  651. scitex/scholar/core/Paper.py +79 -78
  652. scitex/scholar/core/Papers.py +16 -27
  653. scitex/scholar/core/Scholar.py +98 -229
  654. scitex/scholar/core/journal_normalizer.py +52 -49
  655. scitex/scholar/core/oa_cache.py +27 -23
  656. scitex/scholar/core/open_access.py +17 -8
  657. scitex/scholar/docs/template.py +4 -3
  658. scitex/scholar/docs/to_claude/examples/example-python-project-scitex/scripts/mnist/clf_svm.py +0 -0
  659. scitex/scholar/docs/to_claude/examples/example-python-project-scitex/scripts/mnist/download.py +0 -0
  660. scitex/scholar/docs/to_claude/examples/example-python-project-scitex/scripts/mnist/plot_conf_mat.py +0 -0
  661. scitex/scholar/docs/to_claude/examples/example-python-project-scitex/scripts/mnist/plot_digits.py +0 -0
  662. scitex/scholar/docs/to_claude/examples/example-python-project-scitex/scripts/mnist/plot_umap_space.py +0 -0
  663. scitex/scholar/examples/00_config.py +10 -9
  664. scitex/scholar/examples/01_auth.py +3 -0
  665. scitex/scholar/examples/02_browser.py +14 -10
  666. scitex/scholar/examples/03_01-engine.py +3 -0
  667. scitex/scholar/examples/03_02-engine-for-bibtex.py +4 -3
  668. scitex/scholar/examples/04_01-url.py +9 -9
  669. scitex/scholar/examples/04_02-url-for-bibtex.py +7 -3
  670. scitex/scholar/examples/04_02-url-for-dois.py +87 -97
  671. scitex/scholar/examples/05_download_pdf.py +10 -4
  672. scitex/scholar/examples/06_find_and_download.py +6 -6
  673. scitex/scholar/examples/06_parse_bibtex.py +17 -17
  674. scitex/scholar/examples/07_storage_integration.py +6 -9
  675. scitex/scholar/examples/99_fullpipeline-for-bibtex.py +14 -15
  676. scitex/scholar/examples/99_fullpipeline-for-one-entry.py +31 -23
  677. scitex/scholar/examples/99_maintenance.py +3 -0
  678. scitex/scholar/examples/dev.py +2 -3
  679. scitex/scholar/examples/zotero_integration.py +11 -18
  680. scitex/scholar/impact_factor/ImpactFactorEngine.py +7 -9
  681. scitex/scholar/impact_factor/estimation/__init__.py +4 -4
  682. scitex/scholar/impact_factor/estimation/core/__init__.py +3 -7
  683. scitex/scholar/impact_factor/estimation/core/cache_manager.py +223 -211
  684. scitex/scholar/impact_factor/estimation/core/calculator.py +165 -131
  685. scitex/scholar/impact_factor/estimation/core/journal_matcher.py +217 -172
  686. scitex/scholar/impact_factor/jcr/ImpactFactorJCREngine.py +6 -14
  687. scitex/scholar/impact_factor/jcr/build_database.py +4 -3
  688. scitex/scholar/integration/base.py +9 -17
  689. scitex/scholar/integration/mendeley/exporter.py +2 -4
  690. scitex/scholar/integration/mendeley/importer.py +3 -3
  691. scitex/scholar/integration/mendeley/linker.py +3 -3
  692. scitex/scholar/integration/mendeley/mapper.py +9 -6
  693. scitex/scholar/integration/zotero/__main__.py +26 -43
  694. scitex/scholar/integration/zotero/exporter.py +15 -11
  695. scitex/scholar/integration/zotero/importer.py +12 -10
  696. scitex/scholar/integration/zotero/linker.py +8 -12
  697. scitex/scholar/integration/zotero/mapper.py +17 -12
  698. scitex/scholar/metadata_engines/.combined-SemanticScholarSource/_SemanticScholarSource.py +37 -35
  699. scitex/scholar/metadata_engines/.combined-SemanticScholarSource/_SemanticScholarSourceEnhanced.py +47 -35
  700. scitex/scholar/metadata_engines/ScholarEngine.py +21 -43
  701. scitex/scholar/metadata_engines/__init__.py +1 -0
  702. scitex/scholar/metadata_engines/individual/ArXivEngine.py +15 -37
  703. scitex/scholar/metadata_engines/individual/CrossRefEngine.py +15 -42
  704. scitex/scholar/metadata_engines/individual/CrossRefLocalEngine.py +24 -45
  705. scitex/scholar/metadata_engines/individual/OpenAlexEngine.py +11 -21
  706. scitex/scholar/metadata_engines/individual/PubMedEngine.py +10 -27
  707. scitex/scholar/metadata_engines/individual/SemanticScholarEngine.py +28 -35
  708. scitex/scholar/metadata_engines/individual/URLDOIEngine.py +11 -22
  709. scitex/scholar/metadata_engines/individual/_BaseDOIEngine.py +20 -49
  710. scitex/scholar/metadata_engines/utils/_PubMedConverter.py +4 -8
  711. scitex/scholar/metadata_engines/utils/_URLDOIExtractor.py +5 -10
  712. scitex/scholar/metadata_engines/utils/__init__.py +2 -0
  713. scitex/scholar/metadata_engines/utils/_metadata2bibtex.py +3 -0
  714. scitex/scholar/metadata_engines/utils/_standardize_metadata.py +2 -3
  715. scitex/scholar/pdf_download/ScholarPDFDownloader.py +25 -37
  716. scitex/scholar/pdf_download/strategies/chrome_pdf_viewer.py +11 -19
  717. scitex/scholar/pdf_download/strategies/direct_download.py +5 -9
  718. scitex/scholar/pdf_download/strategies/manual_download_fallback.py +3 -3
  719. scitex/scholar/pdf_download/strategies/manual_download_utils.py +6 -13
  720. scitex/scholar/pdf_download/strategies/open_access_download.py +49 -31
  721. scitex/scholar/pdf_download/strategies/response_body.py +8 -19
  722. scitex/scholar/pipelines/ScholarPipelineBibTeX.py +9 -18
  723. scitex/scholar/pipelines/ScholarPipelineMetadataParallel.py +25 -26
  724. scitex/scholar/pipelines/ScholarPipelineMetadataSingle.py +62 -23
  725. scitex/scholar/pipelines/ScholarPipelineParallel.py +13 -30
  726. scitex/scholar/pipelines/ScholarPipelineSearchParallel.py +299 -220
  727. scitex/scholar/pipelines/ScholarPipelineSearchSingle.py +202 -165
  728. scitex/scholar/pipelines/ScholarPipelineSingle.py +25 -51
  729. scitex/scholar/pipelines/SearchQueryParser.py +55 -55
  730. scitex/scholar/search_engines/ScholarSearchEngine.py +31 -27
  731. scitex/scholar/search_engines/_BaseSearchEngine.py +20 -23
  732. scitex/scholar/search_engines/individual/ArXivSearchEngine.py +53 -35
  733. scitex/scholar/search_engines/individual/CrossRefSearchEngine.py +47 -40
  734. scitex/scholar/search_engines/individual/OpenAlexSearchEngine.py +55 -50
  735. scitex/scholar/search_engines/individual/PubMedSearchEngine.py +8 -10
  736. scitex/scholar/search_engines/individual/SemanticScholarSearchEngine.py +55 -49
  737. scitex/scholar/storage/BibTeXHandler.py +150 -95
  738. scitex/scholar/storage/PaperIO.py +3 -6
  739. scitex/scholar/storage/ScholarLibrary.py +70 -49
  740. scitex/scholar/storage/_DeduplicationManager.py +52 -25
  741. scitex/scholar/storage/_LibraryCacheManager.py +19 -46
  742. scitex/scholar/storage/_LibraryManager.py +65 -175
  743. scitex/scholar/url_finder/ScholarURLFinder.py +9 -25
  744. scitex/scholar/url_finder/strategies/find_pdf_urls_by_direct_links.py +1 -1
  745. scitex/scholar/url_finder/strategies/find_pdf_urls_by_href.py +6 -10
  746. scitex/scholar/url_finder/strategies/find_pdf_urls_by_navigation.py +4 -6
  747. scitex/scholar/url_finder/strategies/find_pdf_urls_by_publisher_patterns.py +8 -15
  748. scitex/scholar/url_finder/strategies/find_pdf_urls_by_zotero_translators.py +3 -3
  749. scitex/scholar/url_finder/strategies/find_supplementary_urls_by_href.py +3 -3
  750. scitex/scholar/url_finder/translators/core/patterns.py +6 -4
  751. scitex/scholar/url_finder/translators/core/registry.py +6 -9
  752. scitex/scholar/url_finder/translators/individual/BOFiP_Impots.py +60 -52
  753. scitex/scholar/url_finder/translators/individual/Baidu_Scholar.py +54 -62
  754. scitex/scholar/url_finder/translators/individual/Bangkok_Post.py +38 -44
  755. scitex/scholar/url_finder/translators/individual/Baruch_Foundation.py +43 -47
  756. scitex/scholar/url_finder/translators/individual/Beobachter.py +46 -50
  757. scitex/scholar/url_finder/translators/individual/Bezneng_Gajit.py +37 -41
  758. scitex/scholar/url_finder/translators/individual/BibLaTeX.py +59 -52
  759. scitex/scholar/url_finder/translators/individual/BibTeX.py +83 -79
  760. scitex/scholar/url_finder/translators/individual/Biblio_com.py +48 -51
  761. scitex/scholar/url_finder/translators/individual/Bibliontology_RDF.py +58 -56
  762. scitex/scholar/url_finder/translators/individual/Camara_Brasileira_do_Livro_ISBN.py +102 -99
  763. scitex/scholar/url_finder/translators/individual/CanLII.py +49 -43
  764. scitex/scholar/url_finder/translators/individual/Canada_com.py +36 -40
  765. scitex/scholar/url_finder/translators/individual/Canadian_Letters_and_Images.py +43 -43
  766. scitex/scholar/url_finder/translators/individual/Canadiana_ca.py +77 -66
  767. scitex/scholar/url_finder/translators/individual/Cascadilla_Proceedings_Project.py +68 -62
  768. scitex/scholar/url_finder/translators/individual/Central_and_Eastern_European_Online_Library_Journals.py +60 -60
  769. scitex/scholar/url_finder/translators/individual/Champlain_Society_Collection.py +63 -61
  770. scitex/scholar/url_finder/translators/individual/Chicago_Journal_of_Theoretical_Computer_Science.py +74 -58
  771. scitex/scholar/url_finder/translators/individual/Christian_Science_Monitor.py +32 -38
  772. scitex/scholar/url_finder/translators/individual/Columbia_University_Press.py +51 -47
  773. scitex/scholar/url_finder/translators/individual/Common_Place.py +66 -57
  774. scitex/scholar/url_finder/translators/individual/Cornell_LII.py +66 -62
  775. scitex/scholar/url_finder/translators/individual/Cornell_University_Press.py +38 -45
  776. scitex/scholar/url_finder/translators/individual/CourtListener.py +52 -56
  777. scitex/scholar/url_finder/translators/individual/DAI_Zenon.py +53 -54
  778. scitex/scholar/url_finder/translators/individual/access_medicine.py +27 -33
  779. scitex/scholar/url_finder/translators/individual/acm.py +1 -1
  780. scitex/scholar/url_finder/translators/individual/acm_digital_library.py +93 -63
  781. scitex/scholar/url_finder/translators/individual/airiti.py +3 -1
  782. scitex/scholar/url_finder/translators/individual/aosic.py +3 -1
  783. scitex/scholar/url_finder/translators/individual/archive_ouverte_aosic.py +3 -1
  784. scitex/scholar/url_finder/translators/individual/archive_ouverte_en_sciences_de_l_information_et_de_la_communication___aosic_.py +6 -2
  785. scitex/scholar/url_finder/translators/individual/artforum.py +35 -27
  786. scitex/scholar/url_finder/translators/individual/arxiv.py +1 -1
  787. scitex/scholar/url_finder/translators/individual/arxiv_org.py +8 -4
  788. scitex/scholar/url_finder/translators/individual/atlanta_journal_constitution.py +22 -18
  789. scitex/scholar/url_finder/translators/individual/atypon_journals.py +19 -11
  790. scitex/scholar/url_finder/translators/individual/austlii_and_nzlii.py +48 -44
  791. scitex/scholar/url_finder/translators/individual/australian_dictionary_of_biography.py +21 -17
  792. scitex/scholar/url_finder/translators/individual/bailii.py +22 -19
  793. scitex/scholar/url_finder/translators/individual/bbc.py +46 -42
  794. scitex/scholar/url_finder/translators/individual/bbc_genome.py +37 -25
  795. scitex/scholar/url_finder/translators/individual/biblioteca_nacional_de_maestros.py +24 -20
  796. scitex/scholar/url_finder/translators/individual/bibliotheque_archives_nationale_quebec_pistard.py +42 -43
  797. scitex/scholar/url_finder/translators/individual/bibliotheque_archives_nationales_quebec.py +87 -81
  798. scitex/scholar/url_finder/translators/individual/bibliotheque_nationale_france.py +39 -37
  799. scitex/scholar/url_finder/translators/individual/bibsys.py +32 -28
  800. scitex/scholar/url_finder/translators/individual/bioconductor.py +58 -52
  801. scitex/scholar/url_finder/translators/individual/biomed_central.py +23 -15
  802. scitex/scholar/url_finder/translators/individual/biorxiv.py +26 -13
  803. scitex/scholar/url_finder/translators/individual/blogger.py +39 -43
  804. scitex/scholar/url_finder/translators/individual/bloomberg.py +48 -52
  805. scitex/scholar/url_finder/translators/individual/bloomsbury_food_library.py +37 -37
  806. scitex/scholar/url_finder/translators/individual/bluesky.py +30 -28
  807. scitex/scholar/url_finder/translators/individual/bnf_isbn.py +1 -1
  808. scitex/scholar/url_finder/translators/individual/bocc.py +66 -60
  809. scitex/scholar/url_finder/translators/individual/boe.py +52 -52
  810. scitex/scholar/url_finder/translators/individual/brill.py +3 -1
  811. scitex/scholar/url_finder/translators/individual/business_standard.py +36 -38
  812. scitex/scholar/url_finder/translators/individual/cabi_cab_abstracts.py +39 -41
  813. scitex/scholar/url_finder/translators/individual/cambridge.py +3 -1
  814. scitex/scholar/url_finder/translators/individual/cambridge_core.py +30 -24
  815. scitex/scholar/url_finder/translators/individual/caod.py +50 -46
  816. scitex/scholar/url_finder/translators/individual/cbc.py +91 -67
  817. scitex/scholar/url_finder/translators/individual/ccfr_bnf.py +49 -53
  818. scitex/scholar/url_finder/translators/individual/cia_world_factbook.py +43 -33
  819. scitex/scholar/url_finder/translators/individual/crossref_rest.py +208 -174
  820. scitex/scholar/url_finder/translators/individual/current_affairs.py +29 -35
  821. scitex/scholar/url_finder/translators/individual/dabi.py +70 -66
  822. scitex/scholar/url_finder/translators/individual/dagens_nyheter.py +3 -1
  823. scitex/scholar/url_finder/translators/individual/dagstuhl.py +10 -15
  824. scitex/scholar/url_finder/translators/individual/dar_almandumah.py +13 -9
  825. scitex/scholar/url_finder/translators/individual/dart_europe.py +19 -22
  826. scitex/scholar/url_finder/translators/individual/data_gov.py +2 -2
  827. scitex/scholar/url_finder/translators/individual/databrary.py +27 -28
  828. scitex/scholar/url_finder/translators/individual/datacite_json.py +152 -137
  829. scitex/scholar/url_finder/translators/individual/dataverse.py +68 -64
  830. scitex/scholar/url_finder/translators/individual/daum_news.py +38 -38
  831. scitex/scholar/url_finder/translators/individual/dblp.py +4 -8
  832. scitex/scholar/url_finder/translators/individual/dblp_computer_science_bibliography.py +8 -3
  833. scitex/scholar/url_finder/translators/individual/dbpia.py +5 -3
  834. scitex/scholar/url_finder/translators/individual/defense_technical_information_center.py +30 -28
  835. scitex/scholar/url_finder/translators/individual/delpher.py +102 -79
  836. scitex/scholar/url_finder/translators/individual/demographic_research.py +35 -31
  837. scitex/scholar/url_finder/translators/individual/denik_cz.py +58 -54
  838. scitex/scholar/url_finder/translators/individual/depatisnet.py +7 -10
  839. scitex/scholar/url_finder/translators/individual/der_freitag.py +81 -66
  840. scitex/scholar/url_finder/translators/individual/der_spiegel.py +56 -54
  841. scitex/scholar/url_finder/translators/individual/digibib_net.py +3 -1
  842. scitex/scholar/url_finder/translators/individual/digizeitschriften.py +3 -1
  843. scitex/scholar/url_finder/translators/individual/dpla.py +13 -14
  844. scitex/scholar/url_finder/translators/individual/dspace.py +2 -2
  845. scitex/scholar/url_finder/translators/individual/ebrary.py +3 -1
  846. scitex/scholar/url_finder/translators/individual/ebscohost.py +3 -1
  847. scitex/scholar/url_finder/translators/individual/electronic_colloquium_on_computational_complexity.py +3 -1
  848. scitex/scholar/url_finder/translators/individual/elife.py +3 -1
  849. scitex/scholar/url_finder/translators/individual/elsevier_health_journals.py +3 -1
  850. scitex/scholar/url_finder/translators/individual/emerald.py +3 -1
  851. scitex/scholar/url_finder/translators/individual/emerald_insight.py +3 -1
  852. scitex/scholar/url_finder/translators/individual/epicurious.py +3 -1
  853. scitex/scholar/url_finder/translators/individual/eurogamerusgamer.py +3 -1
  854. scitex/scholar/url_finder/translators/individual/fachportal_padagogik.py +3 -1
  855. scitex/scholar/url_finder/translators/individual/frontiers.py +1 -1
  856. scitex/scholar/url_finder/translators/individual/gale_databases.py +3 -1
  857. scitex/scholar/url_finder/translators/individual/gms_german_medical_science.py +6 -2
  858. scitex/scholar/url_finder/translators/individual/ieee_computer_society.py +6 -2
  859. scitex/scholar/url_finder/translators/individual/ieee_xplore.py +41 -35
  860. scitex/scholar/url_finder/translators/individual/inter_research_science_center.py +6 -2
  861. scitex/scholar/url_finder/translators/individual/jisc_historical_texts.py +3 -1
  862. scitex/scholar/url_finder/translators/individual/jstor.py +14 -12
  863. scitex/scholar/url_finder/translators/individual/korean_national_library.py +3 -1
  864. scitex/scholar/url_finder/translators/individual/la_times.py +3 -1
  865. scitex/scholar/url_finder/translators/individual/landesbibliographie_baden_wurttemberg.py +3 -1
  866. scitex/scholar/url_finder/translators/individual/legislative_insight.py +3 -1
  867. scitex/scholar/url_finder/translators/individual/libraries_tasmania.py +3 -1
  868. scitex/scholar/url_finder/translators/individual/library_catalog__koha_.py +3 -1
  869. scitex/scholar/url_finder/translators/individual/lingbuzz.py +2 -2
  870. scitex/scholar/url_finder/translators/individual/max_planck_institute_for_the_history_of_science_virtual_laboratory_library.py +3 -1
  871. scitex/scholar/url_finder/translators/individual/mdpi.py +12 -6
  872. scitex/scholar/url_finder/translators/individual/microbiology_society_journals.py +3 -1
  873. scitex/scholar/url_finder/translators/individual/midas_journals.py +3 -1
  874. scitex/scholar/url_finder/translators/individual/nagoya_university_opac.py +3 -1
  875. scitex/scholar/url_finder/translators/individual/nature_publishing_group.py +32 -19
  876. scitex/scholar/url_finder/translators/individual/ntsb_accident_reports.py +3 -1
  877. scitex/scholar/url_finder/translators/individual/openedition_journals.py +8 -4
  878. scitex/scholar/url_finder/translators/individual/orcid.py +16 -15
  879. scitex/scholar/url_finder/translators/individual/oxford.py +25 -19
  880. scitex/scholar/url_finder/translators/individual/oxford_dictionaries_premium.py +3 -1
  881. scitex/scholar/url_finder/translators/individual/ozon_ru.py +3 -1
  882. scitex/scholar/url_finder/translators/individual/plos.py +9 -12
  883. scitex/scholar/url_finder/translators/individual/polygon.py +3 -1
  884. scitex/scholar/url_finder/translators/individual/primo.py +3 -1
  885. scitex/scholar/url_finder/translators/individual/project_muse.py +3 -1
  886. scitex/scholar/url_finder/translators/individual/pubfactory_journals.py +3 -1
  887. scitex/scholar/url_finder/translators/individual/pubmed.py +71 -65
  888. scitex/scholar/url_finder/translators/individual/pubmed_central.py +8 -6
  889. scitex/scholar/url_finder/translators/individual/rechtspraak_nl.py +3 -1
  890. scitex/scholar/url_finder/translators/individual/sage_journals.py +25 -17
  891. scitex/scholar/url_finder/translators/individual/sciencedirect.py +36 -17
  892. scitex/scholar/url_finder/translators/individual/semantics_visual_library.py +3 -1
  893. scitex/scholar/url_finder/translators/individual/silverchair.py +70 -52
  894. scitex/scholar/url_finder/translators/individual/sora.py +3 -1
  895. scitex/scholar/url_finder/translators/individual/springer.py +15 -11
  896. scitex/scholar/url_finder/translators/individual/ssrn.py +3 -3
  897. scitex/scholar/url_finder/translators/individual/stanford_encyclopedia_of_philosophy.py +3 -1
  898. scitex/scholar/url_finder/translators/individual/superlib.py +3 -1
  899. scitex/scholar/url_finder/translators/individual/treesearch.py +3 -1
  900. scitex/scholar/url_finder/translators/individual/university_of_chicago_press_books.py +3 -1
  901. scitex/scholar/url_finder/translators/individual/vlex.py +3 -1
  902. scitex/scholar/url_finder/translators/individual/web_of_science.py +3 -1
  903. scitex/scholar/url_finder/translators/individual/web_of_science_nextgen.py +3 -1
  904. scitex/scholar/url_finder/translators/individual/wiley.py +31 -25
  905. scitex/scholar/url_finder/translators/individual/wilson_center_digital_archive.py +3 -1
  906. scitex/scholar/utils/bibtex/_parse_bibtex.py +3 -3
  907. scitex/scholar/utils/cleanup/_cleanup_scholar_processes.py +5 -9
  908. scitex/scholar/utils/text/_TextNormalizer.py +249 -176
  909. scitex/scholar/utils/validation/DOIValidator.py +31 -28
  910. scitex/scholar/utils/validation/__init__.py +0 -0
  911. scitex/scholar/utils/validation/validate_library_dois.py +61 -57
  912. scitex/scholar/zotero/__init__.py +1 -1
  913. scitex/security/cli.py +7 -20
  914. scitex/security/github.py +45 -32
  915. scitex/session/__init__.py +8 -9
  916. scitex/session/_decorator.py +49 -42
  917. scitex/session/_lifecycle.py +39 -39
  918. scitex/session/_manager.py +24 -20
  919. scitex/sh/__init__.py +4 -3
  920. scitex/sh/_execute.py +10 -7
  921. scitex/sh/_security.py +3 -3
  922. scitex/sh/_types.py +2 -3
  923. scitex/stats/__init__.py +57 -6
  924. scitex/stats/_schema.py +42 -569
  925. scitex/stats/auto/__init__.py +188 -0
  926. scitex/stats/auto/_context.py +331 -0
  927. scitex/stats/auto/_formatting.py +679 -0
  928. scitex/stats/auto/_rules.py +901 -0
  929. scitex/stats/auto/_selector.py +554 -0
  930. scitex/stats/auto/_styles.py +721 -0
  931. scitex/stats/correct/__init__.py +4 -4
  932. scitex/stats/correct/_correct_bonferroni.py +43 -34
  933. scitex/stats/correct/_correct_fdr.py +14 -40
  934. scitex/stats/correct/_correct_fdr_.py +39 -46
  935. scitex/stats/correct/_correct_holm.py +14 -32
  936. scitex/stats/correct/_correct_sidak.py +36 -21
  937. scitex/stats/descriptive/_circular.py +20 -21
  938. scitex/stats/descriptive/_describe.py +19 -5
  939. scitex/stats/descriptive/_nan.py +5 -7
  940. scitex/stats/descriptive/_real.py +4 -3
  941. scitex/stats/effect_sizes/__init__.py +10 -11
  942. scitex/stats/effect_sizes/_cliffs_delta.py +35 -32
  943. scitex/stats/effect_sizes/_cohens_d.py +30 -31
  944. scitex/stats/effect_sizes/_epsilon_squared.py +19 -22
  945. scitex/stats/effect_sizes/_eta_squared.py +23 -27
  946. scitex/stats/effect_sizes/_prob_superiority.py +18 -21
  947. scitex/stats/posthoc/__init__.py +3 -3
  948. scitex/stats/posthoc/_dunnett.py +75 -55
  949. scitex/stats/posthoc/_games_howell.py +61 -43
  950. scitex/stats/posthoc/_tukey_hsd.py +42 -34
  951. scitex/stats/power/__init__.py +2 -2
  952. scitex/stats/power/_power.py +56 -56
  953. scitex/stats/tests/__init__.py +1 -1
  954. scitex/stats/tests/correlation/__init__.py +1 -1
  955. scitex/stats/tests/correlation/_test_pearson.py +28 -38
  956. scitex/stats/utils/__init__.py +14 -17
  957. scitex/stats/utils/_effect_size.py +85 -78
  958. scitex/stats/utils/_formatters.py +49 -43
  959. scitex/stats/utils/_normalizers.py +7 -14
  960. scitex/stats/utils/_power.py +56 -56
  961. scitex/str/__init__.py +1 -0
  962. scitex/str/_clean_path.py +3 -3
  963. scitex/str/_factor_out_digits.py +86 -58
  964. scitex/str/_format_plot_text.py +180 -111
  965. scitex/str/_latex.py +19 -19
  966. scitex/str/_latex_fallback.py +9 -10
  967. scitex/str/_parse.py +3 -6
  968. scitex/str/_print_debug.py +13 -13
  969. scitex/str/_printc.py +2 -0
  970. scitex/str/_search.py +3 -3
  971. scitex/template/.legacy/_clone_project.py +9 -13
  972. scitex/template/__init__.py +10 -2
  973. scitex/template/_clone_project.py +7 -2
  974. scitex/template/_copy.py +1 -0
  975. scitex/template/_customize.py +3 -6
  976. scitex/template/_git_strategy.py +2 -3
  977. scitex/template/_rename.py +1 -0
  978. scitex/template/clone_pip_project.py +6 -7
  979. scitex/template/clone_research.py +7 -10
  980. scitex/template/clone_singularity.py +6 -7
  981. scitex/template/clone_writer_directory.py +6 -7
  982. scitex/tex/_preview.py +26 -11
  983. scitex/tex/_to_vec.py +10 -7
  984. scitex/torch/__init__.py +11 -1
  985. scitex/types/_ArrayLike.py +2 -0
  986. scitex/types/_is_listed_X.py +3 -3
  987. scitex/units.py +110 -77
  988. scitex/utils/_compress_hdf5.py +3 -3
  989. scitex/utils/_email.py +8 -4
  990. scitex/utils/_notify.py +14 -8
  991. scitex/utils/_search.py +6 -6
  992. scitex/utils/_verify_scitex_format.py +17 -42
  993. scitex/utils/_verify_scitex_format_v01.py +12 -34
  994. scitex/utils/template.py +4 -3
  995. scitex/vis/__init__.py +0 -0
  996. scitex/vis/backend/__init__.py +3 -3
  997. scitex/vis/backend/{export.py → _export.py} +1 -1
  998. scitex/vis/backend/{parser.py → _parser.py} +1 -3
  999. scitex/vis/backend/{render.py → _render.py} +1 -1
  1000. scitex/vis/canvas.py +15 -3
  1001. scitex/vis/editor/__init__.py +0 -0
  1002. scitex/vis/editor/_dearpygui_editor.py +450 -304
  1003. scitex/vis/editor/_defaults.py +114 -123
  1004. scitex/vis/editor/_edit.py +38 -26
  1005. scitex/vis/editor/_flask_editor.py +8 -8
  1006. scitex/vis/editor/_mpl_editor.py +63 -48
  1007. scitex/vis/editor/_qt_editor.py +210 -159
  1008. scitex/vis/editor/_tkinter_editor.py +146 -89
  1009. scitex/vis/editor/flask_editor/__init__.py +10 -10
  1010. scitex/vis/editor/flask_editor/_bbox.py +529 -0
  1011. scitex/vis/editor/flask_editor/{core.py → _core.py} +45 -29
  1012. scitex/vis/editor/flask_editor/_plotter.py +567 -0
  1013. scitex/vis/editor/flask_editor/_renderer.py +393 -0
  1014. scitex/vis/editor/flask_editor/{utils.py → _utils.py} +13 -14
  1015. scitex/vis/editor/flask_editor/templates/__init__.py +5 -5
  1016. scitex/vis/editor/flask_editor/templates/{html.py → _html.py} +234 -16
  1017. scitex/vis/editor/flask_editor/templates/_scripts.py +1261 -0
  1018. scitex/vis/editor/flask_editor/templates/{styles.py → _styles.py} +192 -2
  1019. scitex/vis/io/__init__.py +5 -5
  1020. scitex/vis/io/{canvas.py → _canvas.py} +8 -4
  1021. scitex/vis/io/{data.py → _data.py} +13 -9
  1022. scitex/vis/io/{directory.py → _directory.py} +7 -4
  1023. scitex/vis/io/{export.py → _export.py} +15 -12
  1024. scitex/vis/io/{load.py → _load.py} +1 -1
  1025. scitex/vis/io/{panel.py → _panel.py} +21 -13
  1026. scitex/vis/io/{save.py → _save.py} +0 -0
  1027. scitex/vis/model/__init__.py +7 -7
  1028. scitex/vis/model/{annotations.py → _annotations.py} +2 -4
  1029. scitex/vis/model/{axes.py → _axes.py} +1 -1
  1030. scitex/vis/model/{figure.py → _figure.py} +0 -0
  1031. scitex/vis/model/{guides.py → _guides.py} +1 -1
  1032. scitex/vis/model/{plot.py → _plot.py} +2 -4
  1033. scitex/vis/model/{plot_types.py → _plot_types.py} +0 -0
  1034. scitex/vis/model/{styles.py → _styles.py} +0 -0
  1035. scitex/vis/utils/__init__.py +2 -2
  1036. scitex/vis/utils/{defaults.py → _defaults.py} +1 -2
  1037. scitex/vis/utils/{validate.py → _validate.py} +3 -9
  1038. scitex/web/__init__.py +7 -1
  1039. scitex/web/_scraping.py +54 -38
  1040. scitex/web/_search_pubmed.py +30 -14
  1041. scitex/writer/.legacy/Writer_v01-refactored.py +4 -4
  1042. scitex/writer/.legacy/_compile.py +18 -28
  1043. scitex/writer/Writer.py +8 -21
  1044. scitex/writer/__init__.py +11 -11
  1045. scitex/writer/_clone_writer_project.py +2 -6
  1046. scitex/writer/_compile/__init__.py +1 -0
  1047. scitex/writer/_compile/_parser.py +1 -0
  1048. scitex/writer/_compile/_runner.py +35 -38
  1049. scitex/writer/_compile/_validator.py +1 -0
  1050. scitex/writer/_compile/manuscript.py +1 -0
  1051. scitex/writer/_compile/revision.py +1 -0
  1052. scitex/writer/_compile/supplementary.py +1 -0
  1053. scitex/writer/_compile_async.py +5 -12
  1054. scitex/writer/_project/__init__.py +1 -0
  1055. scitex/writer/_project/_create.py +10 -25
  1056. scitex/writer/_project/_trees.py +4 -9
  1057. scitex/writer/_project/_validate.py +2 -3
  1058. scitex/writer/_validate_tree_structures.py +7 -18
  1059. scitex/writer/dataclasses/__init__.py +8 -10
  1060. scitex/writer/dataclasses/config/_CONSTANTS.py +2 -3
  1061. scitex/writer/dataclasses/config/_WriterConfig.py +4 -9
  1062. scitex/writer/dataclasses/contents/_ManuscriptContents.py +14 -25
  1063. scitex/writer/dataclasses/contents/_RevisionContents.py +21 -16
  1064. scitex/writer/dataclasses/contents/_SupplementaryContents.py +21 -24
  1065. scitex/writer/dataclasses/core/_Document.py +2 -3
  1066. scitex/writer/dataclasses/core/_DocumentSection.py +8 -23
  1067. scitex/writer/dataclasses/results/_CompilationResult.py +2 -3
  1068. scitex/writer/dataclasses/results/_LaTeXIssue.py +3 -6
  1069. scitex/writer/dataclasses/results/_SaveSectionsResponse.py +20 -9
  1070. scitex/writer/dataclasses/results/_SectionReadResponse.py +24 -10
  1071. scitex/writer/dataclasses/tree/_ConfigTree.py +7 -4
  1072. scitex/writer/dataclasses/tree/_ManuscriptTree.py +10 -13
  1073. scitex/writer/dataclasses/tree/_RevisionTree.py +16 -17
  1074. scitex/writer/dataclasses/tree/_ScriptsTree.py +10 -5
  1075. scitex/writer/dataclasses/tree/_SharedTree.py +10 -13
  1076. scitex/writer/dataclasses/tree/_SupplementaryTree.py +15 -14
  1077. scitex/writer/utils/.legacy_git_retry.py +3 -8
  1078. scitex/writer/utils/_parse_latex_logs.py +2 -3
  1079. scitex/writer/utils/_parse_script_args.py +20 -23
  1080. scitex/writer/utils/_watch.py +5 -5
  1081. {scitex-2.5.0.dist-info → scitex-2.7.0.dist-info}/METADATA +4 -10
  1082. {scitex-2.5.0.dist-info → scitex-2.7.0.dist-info}/RECORD +1071 -975
  1083. scitex/db/_sqlite3/_SQLite3Mixins/_ColumnMixin_v01-indentation-issues.py +0 -583
  1084. scitex/plt/_subplots/_export_as_csv_formatters.py +0 -112
  1085. scitex/vis/editor/flask_editor/bbox.py +0 -216
  1086. scitex/vis/editor/flask_editor/plotter.py +0 -130
  1087. scitex/vis/editor/flask_editor/renderer.py +0 -184
  1088. scitex/vis/editor/flask_editor/templates/scripts.py +0 -614
  1089. {scitex-2.5.0.dist-info → scitex-2.7.0.dist-info}/WHEEL +0 -0
  1090. {scitex-2.5.0.dist-info → scitex-2.7.0.dist-info}/entry_points.txt +0 -0
  1091. {scitex-2.5.0.dist-info → scitex-2.7.0.dist-info}/licenses/LICENSE +0 -0
@@ -5,6 +5,7 @@
5
5
  # ----------------------------------------
6
6
  from __future__ import annotations
7
7
  import os
8
+
8
9
  __FILE__ = __file__
9
10
  __DIR__ = os.path.dirname(__FILE__)
10
11
  # ----------------------------------------
@@ -36,7 +37,7 @@ def generate_org_report(
36
37
  ) -> Path:
37
38
  """
38
39
  Generate org-mode report with inline images and optional pandoc conversions.
39
-
40
+
40
41
  Parameters
41
42
  ----------
42
43
  results : Dict[str, Any]
@@ -49,7 +50,7 @@ def generate_org_report(
49
50
  Whether to print progress messages
50
51
  convert_formats : bool, default True
51
52
  Whether to use pandoc to generate other formats
52
-
53
+
53
54
  Returns
54
55
  -------
55
56
  Path
@@ -57,24 +58,37 @@ def generate_org_report(
57
58
  """
58
59
  output_path = Path(output_path)
59
60
  output_path.parent.mkdir(parents=True, exist_ok=True)
60
-
61
+
61
62
  # Try to get CONFIG from results first (passed from memory)
62
63
  config_data = None
63
64
  # Debug logging
64
65
  from scitex.logging import getLogger
66
+
65
67
  logger = getLogger(__name__)
66
-
68
+
67
69
  # First check if session_config was passed in results
68
- if 'session_config' in results and results['session_config'] is not None:
70
+ if "session_config" in results and results["session_config"] is not None:
69
71
  # Convert CONFIG object to dict, only keeping useful fields
70
- session_config = results['session_config']
72
+ session_config = results["session_config"]
71
73
  config_data = {}
72
-
74
+
73
75
  # List of keys to include (uppercase attributes that are not methods)
74
- useful_keys = ['ID', 'PID', 'START_TIME', 'END_TIME', 'RUN_TIME',
75
- 'FILE', 'FILE_PATH', 'SDIR', 'SDIR_PATH',
76
- 'REL_SDIR', 'REL_SDIR_PATH', 'ARGS', 'EXIT_STATUS']
77
-
76
+ useful_keys = [
77
+ "ID",
78
+ "PID",
79
+ "START_TIME",
80
+ "END_TIME",
81
+ "RUN_TIME",
82
+ "FILE",
83
+ "FILE_PATH",
84
+ "SDIR",
85
+ "SDIR_PATH",
86
+ "REL_SDIR",
87
+ "REL_SDIR_PATH",
88
+ "ARGS",
89
+ "EXIT_STATUS",
90
+ ]
91
+
78
92
  for key in useful_keys:
79
93
  if hasattr(session_config, key):
80
94
  value = getattr(session_config, key)
@@ -85,31 +99,35 @@ def generate_org_report(
85
99
  else:
86
100
  # Use str() for cleaner output
87
101
  config_data[key] = str(value)
88
-
102
+
89
103
  logger.info(f"Using session CONFIG from memory with {len(config_data)} keys")
90
-
104
+
91
105
  # Fallback to loading from file if not provided
92
106
  if config_data is None:
93
107
  try:
94
108
  # Try different possible locations for CONFIG.yaml
95
109
  # The report is generated in classification_results/reports/
96
110
  # CONFIG.yaml is in the session directory under CONFIGS/
97
-
111
+
98
112
  # Get the session directory from the output path
99
113
  # output_path is like: /path/to/RUNNING/ID/classification_results/reports/report.org
100
114
  # We need: /path/to/RUNNING/ID/CONFIGS/CONFIG.yaml
101
-
115
+
102
116
  # output_path.parent = /path/to/RUNNING/ID/classification_results/reports/
103
117
  # output_path.parent.parent = /path/to/RUNNING/ID/classification_results/
104
118
  # output_path.parent.parent.parent = /path/to/RUNNING/ID/ <- Session root!
105
-
119
+
106
120
  session_dir = output_path.parent.parent.parent
107
121
  possible_paths = [
108
- session_dir / "CONFIGS" / "CONFIG.yaml", # This should be the correct path
122
+ session_dir
123
+ / "CONFIGS"
124
+ / "CONFIG.yaml", # This should be the correct path
109
125
  ]
110
-
126
+
111
127
  logger.info(f"Looking for CONFIG.yaml from: {output_path}")
112
- logger.info(f"Output path parent dirs: {output_path.parent}, {output_path.parent.parent}, {output_path.parent.parent.parent}")
128
+ logger.info(
129
+ f"Output path parent dirs: {output_path.parent}, {output_path.parent.parent}, {output_path.parent.parent.parent}"
130
+ )
113
131
  config_path = None
114
132
  for path in possible_paths:
115
133
  logger.info(f"Checking path: {path}, exists: {path.exists()}")
@@ -117,22 +135,28 @@ def generate_org_report(
117
135
  config_path = path
118
136
  logger.info(f"Found CONFIG at path: {config_path}")
119
137
  break
120
-
138
+
121
139
  # Try to get from session output directory if not found
122
- if not config_path and 'config' in results and 'output_dir' in results['config']:
140
+ if (
141
+ not config_path
142
+ and "config" in results
143
+ and "output_dir" in results["config"]
144
+ ):
123
145
  # The output_dir is like /path/to/RUNNING/ID/classification_results
124
146
  # We need to go to /path/to/RUNNING/ID/CONFIGS/CONFIG.yaml
125
- output_dir = Path(results['config']['output_dir'])
147
+ output_dir = Path(results["config"]["output_dir"])
126
148
  # Go up to session dir (from classification_results to session root)
127
- session_dir = output_dir.parent
149
+ session_dir = output_dir.parent
128
150
  config_path = session_dir / "CONFIGS" / "CONFIG.yaml"
129
- logger.info(f"Trying session path: {config_path}, exists: {config_path.exists()}")
151
+ logger.info(
152
+ f"Trying session path: {config_path}, exists: {config_path.exists()}"
153
+ )
130
154
  if not config_path.exists():
131
155
  config_path = None
132
-
156
+
133
157
  if config_path and config_path.exists():
134
158
  logger.info(f"Found CONFIG at: {config_path}")
135
- with open(config_path, 'r') as config_file:
159
+ with open(config_path, "r") as config_file:
136
160
  config_data = yaml.safe_load(config_file)
137
161
  logger.info(f"Successfully loaded CONFIG with {len(config_data)} keys")
138
162
  else:
@@ -141,25 +165,28 @@ def generate_org_report(
141
165
  # Always log the error for debugging
142
166
  logger.warning(f"Could not load CONFIG.yaml: {e}")
143
167
  import traceback
168
+
144
169
  logger.warning(f"Traceback: {traceback.format_exc()}")
145
-
146
- with open(output_path, 'w') as f:
170
+
171
+ with open(output_path, "w") as f:
147
172
  # Header
148
173
  f.write("#+TITLE: Classification Results Report\n")
149
174
  f.write(f"#+AUTHOR: SciTeX Classification Reporter\n")
150
175
  f.write(f"#+DATE: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
151
176
  f.write("#+OPTIONS: toc:2 num:t\n")
152
177
  f.write("#+STARTUP: overview inlineimages\n")
153
- f.write("#+HTML_HEAD: <style>img { cursor: zoom-in; } img:active { transform: scale(2.5); z-index: 999; position: relative; }</style>\n")
178
+ f.write(
179
+ "#+HTML_HEAD: <style>img { cursor: zoom-in; } img:active { transform: scale(2.5); z-index: 999; position: relative; }</style>\n"
180
+ )
154
181
  f.write("#+ATTR_ORG: :width 400\n\n") # Default width for all images
155
-
182
+
156
183
  # Get configuration and fold info
157
- config = results.get('config', {})
158
- n_folds = config.get('n_folds', len(results.get('folds', [])))
159
-
184
+ config = results.get("config", {})
185
+ n_folds = config.get("n_folds", len(results.get("folds", [])))
186
+
160
187
  # Dataset information (sample sizes) - extract from folds
161
188
  f.write("* Dataset Information\n\n")
162
- if 'folds' in results and results['folds']:
189
+ if "folds" in results and results["folds"]:
163
190
  # Create table header
164
191
  sample_header = "| Fold | Train Total | Train Seizure | Train Interictal | Test Total | Test Seizure | Test Interictal |"
165
192
  sample_separator = "|------|-------------|---------------|------------------|------------|--------------|-----------------|"
@@ -168,15 +195,15 @@ def generate_org_report(
168
195
  f.write(sample_separator + "\n")
169
196
 
170
197
  # Add sample size info for each fold if available
171
- for fold_data in results['folds']:
172
- fold_id = fold_data.get('fold_id', '?')
198
+ for fold_data in results["folds"]:
199
+ fold_id = fold_data.get("fold_id", "?")
173
200
  # Sample sizes might be in fold_data directly or we need to compute
174
- n_train = fold_data.get('n_train', '-')
175
- n_test = fold_data.get('n_test', '-')
176
- n_train_seizure = fold_data.get('n_train_seizure', '-')
177
- n_train_interictal = fold_data.get('n_train_interictal', '-')
178
- n_test_seizure = fold_data.get('n_test_seizure', '-')
179
- n_test_interictal = fold_data.get('n_test_interictal', '-')
201
+ n_train = fold_data.get("n_train", "-")
202
+ n_test = fold_data.get("n_test", "-")
203
+ n_train_seizure = fold_data.get("n_train_seizure", "-")
204
+ n_train_interictal = fold_data.get("n_train_interictal", "-")
205
+ n_test_seizure = fold_data.get("n_test_seizure", "-")
206
+ n_test_interictal = fold_data.get("n_test_interictal", "-")
180
207
 
181
208
  row = f"| {fold_id:02d} | {n_train} | {n_train_seizure} | {n_train_interictal} | {n_test} | {n_test_seizure} | {n_test_interictal} |"
182
209
  f.write(row + "\n")
@@ -185,7 +212,7 @@ def generate_org_report(
185
212
  f.write("* Summary Performance\n\n")
186
213
 
187
214
  # Create comprehensive metrics table including per-fold values
188
- if 'summary' in results and results['summary']:
215
+ if "summary" in results and results["summary"]:
189
216
  # Build header with fold columns
190
217
  header = "| Metric |"
191
218
  separator = "|--------|"
@@ -199,23 +226,25 @@ def generate_org_report(
199
226
  f.write(separator + "\n")
200
227
 
201
228
  # Display metrics in specific order
202
- metric_order = ['balanced_accuracy', 'mcc', 'roc_auc', 'pr_auc']
229
+ metric_order = ["balanced_accuracy", "mcc", "roc_auc", "pr_auc"]
203
230
  metric_display_names = {
204
- 'balanced_accuracy': 'Balanced Accuracy',
205
- 'mcc': 'MCC',
206
- 'roc_auc': 'ROC AUC',
207
- 'pr_auc': 'PR AUC'
231
+ "balanced_accuracy": "Balanced Accuracy",
232
+ "mcc": "MCC",
233
+ "roc_auc": "ROC AUC",
234
+ "pr_auc": "PR AUC",
208
235
  }
209
236
 
210
237
  # Collect fold values
211
238
  for metric_name in metric_order:
212
- if metric_name in results['summary']:
213
- stats = results['summary'][metric_name]
214
- if isinstance(stats, dict) and 'mean' in stats:
215
- row = f"| {metric_display_names.get(metric_name, metric_name)} |"
239
+ if metric_name in results["summary"]:
240
+ stats = results["summary"][metric_name]
241
+ if isinstance(stats, dict) and "mean" in stats:
242
+ row = (
243
+ f"| {metric_display_names.get(metric_name, metric_name)} |"
244
+ )
216
245
 
217
246
  # Add individual fold values (rounded to 3 digits)
218
- fold_values = stats.get('values', [])
247
+ fold_values = stats.get("values", [])
219
248
  for i in range(n_folds):
220
249
  if i < len(fold_values):
221
250
  row += f" {fold_values[i]:.3f} |"
@@ -223,61 +252,76 @@ def generate_org_report(
223
252
  row += " - |"
224
253
 
225
254
  # Add mean ± std (rounded to 3 digits)
226
- mean = stats.get('mean', 0)
227
- std = stats.get('std', 0)
255
+ mean = stats.get("mean", 0)
256
+ std = stats.get("std", 0)
228
257
  row += f" {mean:.3f} ± {std:.3f} |"
229
258
  f.write(row + "\n")
230
259
  f.write("\n")
231
260
 
232
261
  # Feature Importance section
233
- if 'summary' in results and 'feature_importance' in results['summary']:
262
+ if "summary" in results and "feature_importance" in results["summary"]:
234
263
  f.write("* Feature Importance\n\n")
235
- feature_imp = results['summary']['feature_importance']
264
+ feature_imp = results["summary"]["feature_importance"]
236
265
 
237
- if 'mean' in feature_imp:
266
+ if "mean" in feature_imp:
238
267
  # Create feature importance table
239
268
  f.write("| Feature | Mean | Std | Min | Max | CV |\n")
240
269
  f.write("|---------|------|-----|-----|-----|----|\n")
241
270
 
242
271
  # Sort by mean importance (descending)
243
272
  features_sorted = sorted(
244
- feature_imp['mean'].items(),
245
- key=lambda x: x[1],
246
- reverse=True
273
+ feature_imp["mean"].items(), key=lambda x: x[1], reverse=True
247
274
  )
248
275
 
249
276
  for feature_name, mean_imp in features_sorted:
250
- std_imp = feature_imp['std'].get(feature_name, 0)
251
- min_imp = feature_imp['min'].get(feature_name, 0)
252
- max_imp = feature_imp['max'].get(feature_name, 0)
253
- cv_imp = feature_imp['cv'].get(feature_name, 0)
277
+ std_imp = feature_imp["std"].get(feature_name, 0)
278
+ min_imp = feature_imp["min"].get(feature_name, 0)
279
+ max_imp = feature_imp["max"].get(feature_name, 0)
280
+ cv_imp = feature_imp["cv"].get(feature_name, 0)
254
281
 
255
- f.write(f"| {feature_name} | {mean_imp:.3f} | {std_imp:.3f} | "
256
- f"{min_imp:.3f} | {max_imp:.3f} | {cv_imp:.3f} |\n")
282
+ f.write(
283
+ f"| {feature_name} | {mean_imp:.3f} | {std_imp:.3f} | "
284
+ f"{min_imp:.3f} | {max_imp:.3f} | {cv_imp:.3f} |\n"
285
+ )
257
286
  f.write("\n")
258
287
 
259
- f.write("*Note:* CV = Coefficient of Variation (std/mean), "
260
- "indicating stability across folds.\n\n")
288
+ f.write(
289
+ "*Note:* CV = Coefficient of Variation (std/mean), "
290
+ "indicating stability across folds.\n\n"
291
+ )
261
292
 
262
293
  # Visualizations section
263
- if include_plots and 'plots' in results:
294
+ if include_plots and "plots" in results:
264
295
  f.write("* Visualizations\n\n")
265
-
296
+
266
297
  # Confusion Matrices - all in one section
267
298
  f.write("** Confusion Matrices\n\n")
268
299
  f.write("#+BEGIN_EXPORT html\n")
269
- f.write("<div style='display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; max-width: 100%;'>\n")
300
+ f.write(
301
+ "<div style='display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; max-width: 100%;'>\n"
302
+ )
270
303
  f.write("#+END_EXPORT\n\n")
271
-
304
+
272
305
  # CV Summary confusion matrix first
273
- cv_summary_plots = {k: v for k, v in results['plots'].items() if 'cv_summary' in k or 'cv-summary' in k}
306
+ cv_summary_plots = {
307
+ k: v
308
+ for k, v in results["plots"].items()
309
+ if "cv_summary" in k or "cv-summary" in k
310
+ }
274
311
  # Support both old (confusion_matrix) and new (confusion-matrix) naming
275
- cm_plots = [v for k, v in cv_summary_plots.items() if ('confusion_matrix' in k or 'confusion-matrix' in k)]
276
-
312
+ cm_plots = [
313
+ v
314
+ for k, v in cv_summary_plots.items()
315
+ if ("confusion_matrix" in k or "confusion-matrix" in k)
316
+ ]
317
+
277
318
  if cm_plots:
278
319
  for plot_path in cm_plots:
279
- rel_path = _make_relative_path(output_path.parent,
280
- Path(results.get('config', {}).get('output_dir', '.')) / plot_path)
320
+ rel_path = _make_relative_path(
321
+ output_path.parent,
322
+ Path(results.get("config", {}).get("output_dir", "."))
323
+ / plot_path,
324
+ )
281
325
  f.write("#+BEGIN_EXPORT html\n")
282
326
  f.write("<div style='text-align: center;'>\n")
283
327
  f.write("#+END_EXPORT\n")
@@ -288,47 +332,65 @@ def generate_org_report(
288
332
  f.write("#+BEGIN_EXPORT html\n")
289
333
  f.write("</div>\n")
290
334
  f.write("#+END_EXPORT\n\n")
291
-
335
+
292
336
  # Individual fold confusion matrices
293
337
  for fold in range(n_folds):
294
338
  # Look for plots with exact fold matching
295
- fold_key = f'fold_{fold:02d}'
296
- fold_plots = {k: v for k, v in results['plots'].items() if fold_key in k}
339
+ fold_key = f"fold_{fold:02d}"
340
+ fold_plots = {
341
+ k: v for k, v in results["plots"].items() if fold_key in k
342
+ }
297
343
  # Support both old (confusion_matrix) and new (confusion-matrix) naming
298
- fold_cm = [v for k, v in fold_plots.items() if ('confusion_matrix' in k or 'confusion-matrix' in k)]
299
-
344
+ fold_cm = [
345
+ v
346
+ for k, v in fold_plots.items()
347
+ if ("confusion_matrix" in k or "confusion-matrix" in k)
348
+ ]
349
+
300
350
  if fold_cm and len(fold_cm) > 0:
301
351
  # Take only the first matching confusion matrix for this fold
302
352
  plot_path = fold_cm[0]
303
- rel_path = _make_relative_path(output_path.parent,
304
- Path(results.get('config', {}).get('output_dir', '.')) / plot_path)
353
+ rel_path = _make_relative_path(
354
+ output_path.parent,
355
+ Path(results.get("config", {}).get("output_dir", "."))
356
+ / plot_path,
357
+ )
305
358
  f.write("#+BEGIN_EXPORT html\n")
306
359
  f.write("<div style='text-align: center;'>\n")
307
360
  f.write("#+END_EXPORT\n")
308
361
  f.write("#+ATTR_ORG: :width 250\n")
309
- f.write("#+ATTR_HTML: :width 100% :style max-width:250px\n")
362
+ f.write("#+ATTR_HTML: :width 100% :style max-width:250px\n")
310
363
  f.write(f"#+CAPTION: Fold {fold:02d}\n")
311
364
  f.write(f"[[file:{rel_path}]]\n")
312
365
  f.write("#+BEGIN_EXPORT html\n")
313
366
  f.write("</div>\n")
314
367
  f.write("#+END_EXPORT\n\n")
315
-
368
+
316
369
  f.write("#+BEGIN_EXPORT html\n")
317
370
  f.write("</div>\n")
318
371
  f.write("#+END_EXPORT\n\n")
319
-
372
+
320
373
  # ROC Curves - all in one section
321
374
  f.write("** ROC Curves\n\n")
322
375
  f.write("#+BEGIN_EXPORT html\n")
323
- f.write("<div style='display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; max-width: 100%;'>\n")
376
+ f.write(
377
+ "<div style='display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; max-width: 100%;'>\n"
378
+ )
324
379
  f.write("#+END_EXPORT\n\n")
325
-
380
+
326
381
  # CV Summary ROC curve (support both old and new naming)
327
- roc_plots = [v for k, v in cv_summary_plots.items() if ('roc_curve' in k or 'roc-curve' in k)]
382
+ roc_plots = [
383
+ v
384
+ for k, v in cv_summary_plots.items()
385
+ if ("roc_curve" in k or "roc-curve" in k)
386
+ ]
328
387
  if roc_plots:
329
388
  for plot_path in roc_plots:
330
- rel_path = _make_relative_path(output_path.parent,
331
- Path(results.get('config', {}).get('output_dir', '.')) / plot_path)
389
+ rel_path = _make_relative_path(
390
+ output_path.parent,
391
+ Path(results.get("config", {}).get("output_dir", "."))
392
+ / plot_path,
393
+ )
332
394
  f.write("#+BEGIN_EXPORT html\n")
333
395
  f.write("<div style='text-align: center;'>\n")
334
396
  f.write("#+END_EXPORT\n")
@@ -339,20 +401,29 @@ def generate_org_report(
339
401
  f.write("#+BEGIN_EXPORT html\n")
340
402
  f.write("</div>\n")
341
403
  f.write("#+END_EXPORT\n\n")
342
-
404
+
343
405
  # Individual fold ROC curves
344
406
  for fold in range(n_folds):
345
407
  # Look for plots with exact fold matching
346
- fold_key = f'fold_{fold:02d}'
347
- fold_plots = {k: v for k, v in results['plots'].items() if fold_key in k}
408
+ fold_key = f"fold_{fold:02d}"
409
+ fold_plots = {
410
+ k: v for k, v in results["plots"].items() if fold_key in k
411
+ }
348
412
  # Support both old (roc_curve) and new (roc-curve) naming
349
- fold_roc = [v for k, v in fold_plots.items() if ('roc_curve' in k or 'roc-curve' in k)]
350
-
413
+ fold_roc = [
414
+ v
415
+ for k, v in fold_plots.items()
416
+ if ("roc_curve" in k or "roc-curve" in k)
417
+ ]
418
+
351
419
  if fold_roc and len(fold_roc) > 0:
352
420
  # Take only the first matching ROC curve for this fold
353
421
  plot_path = fold_roc[0]
354
- rel_path = _make_relative_path(output_path.parent,
355
- Path(results.get('config', {}).get('output_dir', '.')) / plot_path)
422
+ rel_path = _make_relative_path(
423
+ output_path.parent,
424
+ Path(results.get("config", {}).get("output_dir", "."))
425
+ / plot_path,
426
+ )
356
427
  f.write("#+BEGIN_EXPORT html\n")
357
428
  f.write("<div style='text-align: center;'>\n")
358
429
  f.write("#+END_EXPORT\n")
@@ -363,23 +434,32 @@ def generate_org_report(
363
434
  f.write("#+BEGIN_EXPORT html\n")
364
435
  f.write("</div>\n")
365
436
  f.write("#+END_EXPORT\n\n")
366
-
437
+
367
438
  f.write("#+BEGIN_EXPORT html\n")
368
439
  f.write("</div>\n")
369
440
  f.write("#+END_EXPORT\n\n")
370
-
371
- # PR Curves - all in one section
441
+
442
+ # PR Curves - all in one section
372
443
  f.write("** Precision-Recall Curves\n\n")
373
444
  f.write("#+BEGIN_EXPORT html\n")
374
- f.write("<div style='display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; max-width: 100%;'>\n")
445
+ f.write(
446
+ "<div style='display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; max-width: 100%;'>\n"
447
+ )
375
448
  f.write("#+END_EXPORT\n\n")
376
-
449
+
377
450
  # CV Summary PR curve (support both old and new naming)
378
- pr_plots = [v for k, v in cv_summary_plots.items() if ('pr_curve' in k or 'pr-curve' in k)]
451
+ pr_plots = [
452
+ v
453
+ for k, v in cv_summary_plots.items()
454
+ if ("pr_curve" in k or "pr-curve" in k)
455
+ ]
379
456
  if pr_plots:
380
457
  for plot_path in pr_plots:
381
- rel_path = _make_relative_path(output_path.parent,
382
- Path(results.get('config', {}).get('output_dir', '.')) / plot_path)
458
+ rel_path = _make_relative_path(
459
+ output_path.parent,
460
+ Path(results.get("config", {}).get("output_dir", "."))
461
+ / plot_path,
462
+ )
383
463
  f.write("#+BEGIN_EXPORT html\n")
384
464
  f.write("<div style='text-align: center;'>\n")
385
465
  f.write("#+END_EXPORT\n")
@@ -390,20 +470,29 @@ def generate_org_report(
390
470
  f.write("#+BEGIN_EXPORT html\n")
391
471
  f.write("</div>\n")
392
472
  f.write("#+END_EXPORT\n\n")
393
-
473
+
394
474
  # Individual fold PR curves
395
475
  for fold in range(n_folds):
396
476
  # Look for plots with exact fold matching
397
- fold_key = f'fold_{fold:02d}'
398
- fold_plots = {k: v for k, v in results['plots'].items() if fold_key in k}
477
+ fold_key = f"fold_{fold:02d}"
478
+ fold_plots = {
479
+ k: v for k, v in results["plots"].items() if fold_key in k
480
+ }
399
481
  # Support both old (pr_curve) and new (pr-curve) naming
400
- fold_pr = [v for k, v in fold_plots.items() if ('pr_curve' in k or 'pr-curve' in k)]
401
-
482
+ fold_pr = [
483
+ v
484
+ for k, v in fold_plots.items()
485
+ if ("pr_curve" in k or "pr-curve" in k)
486
+ ]
487
+
402
488
  if fold_pr and len(fold_pr) > 0:
403
489
  # Take only the first matching PR curve for this fold
404
490
  plot_path = fold_pr[0]
405
- rel_path = _make_relative_path(output_path.parent,
406
- Path(results.get('config', {}).get('output_dir', '.')) / plot_path)
491
+ rel_path = _make_relative_path(
492
+ output_path.parent,
493
+ Path(results.get("config", {}).get("output_dir", "."))
494
+ / plot_path,
495
+ )
407
496
  f.write("#+BEGIN_EXPORT html\n")
408
497
  f.write("<div style='text-align: center;'>\n")
409
498
  f.write("#+END_EXPORT\n")
@@ -414,105 +503,128 @@ def generate_org_report(
414
503
  f.write("#+BEGIN_EXPORT html\n")
415
504
  f.write("</div>\n")
416
505
  f.write("#+END_EXPORT\n\n")
417
-
506
+
418
507
  f.write("#+BEGIN_EXPORT html\n")
419
508
  f.write("</div>\n")
420
509
  f.write("#+END_EXPORT\n\n")
421
-
510
+
422
511
  # Experiment Configuration section at the end (less prominent)
423
- logger.info(f"config_data is: {config_data is not None}, type: {type(config_data)}")
512
+ logger.info(
513
+ f"config_data is: {config_data is not None}, type: {type(config_data)}"
514
+ )
424
515
  if config_data:
425
516
  logger.info(f"Writing CONFIG section with {len(config_data)} keys")
426
517
  f.write("* Experiment Configuration\n\n")
427
518
  f.write("| Parameter | Value |\n")
428
519
  f.write("|-----------|-------|\n")
429
-
520
+
430
521
  # Display configuration in a clean format
431
522
  for key, value in sorted(config_data.items()):
432
523
  # Format the key nicely
433
- display_key = key.replace('_', ' ').title()
524
+ display_key = key.replace("_", " ").title()
434
525
  # Format value for org-mode table
435
526
  display_value = str(value)
436
527
  # Escape pipe characters in values for org table
437
- display_value = display_value.replace('|', '\\vert{}')
528
+ display_value = display_value.replace("|", "\\vert{}")
438
529
  # For paths, show just the relative part if too long
439
- if 'SDIR' in key.upper() and len(display_value) > 80:
530
+ if "SDIR" in key.upper() and len(display_value) > 80:
440
531
  # Try to show the end part which is more informative
441
- if '/' in display_value:
442
- parts = display_value.split('/')
532
+ if "/" in display_value:
533
+ parts = display_value.split("/")
443
534
  # Keep last few parts
444
535
  if len(parts) > 4:
445
- display_value = '.../'+'/'.join(parts[-4:])
536
+ display_value = ".../" + "/".join(parts[-4:])
446
537
  # Wrap in verbatim for paths to avoid formatting issues
447
- if '/' in display_value or '_' in display_value:
538
+ if "/" in display_value or "_" in display_value:
448
539
  display_value = f"~{display_value}~"
449
540
  f.write(f"| {display_key} | {display_value} |\n")
450
541
  f.write("\n")
451
-
542
+
452
543
  # Footer
453
544
  f.write("\n* Report Generation\n\n")
454
545
  f.write(f"- Generated at: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
455
546
  f.write("- Generated by: SciTeX Classification Reporter\n")
456
547
  f.write("- Format: Org-mode\n")
457
-
548
+
458
549
  if verbose:
459
550
  from scitex.logging import getLogger
551
+
460
552
  logger = getLogger(__name__)
461
553
  logger.info(f"Generated org-mode report: {output_path}")
462
-
554
+
463
555
  # Optional pandoc conversions
464
556
  if convert_formats:
465
557
  import subprocess
466
558
  import shutil
467
559
  import os
468
-
560
+
469
561
  # Check for pandoc in regular PATH or module system
470
- pandoc_cmd = shutil.which('pandoc')
562
+ pandoc_cmd = shutil.which("pandoc")
471
563
  if not pandoc_cmd:
472
564
  # Try module system path
473
- module_pandoc = '/apps/easybuild-2022/easybuild/software/Core/Pandoc/3.1.2/bin/pandoc'
565
+ module_pandoc = (
566
+ "/apps/easybuild-2022/easybuild/software/Core/Pandoc/3.1.2/bin/pandoc"
567
+ )
474
568
  if os.path.exists(module_pandoc):
475
569
  pandoc_cmd = module_pandoc
476
-
570
+
477
571
  if pandoc_cmd:
478
572
  conversions = [
479
573
  # (output_filename, extra_args, description)
480
- (output_path.with_suffix('.md'), [], "markdown"),
481
- (output_path.with_suffix('.html'), ['--standalone', '--embed-resources'], "HTML"),
482
- (output_path.with_suffix('.tex'), [], "LaTeX"),
483
- (output_path.with_suffix('.docx'), ['--resource-path=' + str(output_path.parent.parent)], "DOCX"),
574
+ (output_path.with_suffix(".md"), [], "markdown"),
575
+ (
576
+ output_path.with_suffix(".html"),
577
+ ["--standalone", "--embed-resources"],
578
+ "HTML",
579
+ ),
580
+ (output_path.with_suffix(".tex"), [], "LaTeX"),
581
+ (
582
+ output_path.with_suffix(".docx"),
583
+ ["--resource-path=" + str(output_path.parent.parent)],
584
+ "DOCX",
585
+ ),
484
586
  ]
485
-
587
+
486
588
  for output_file, extra_args, format_name in conversions:
487
589
  try:
488
- cmd = [pandoc_cmd, str(output_path), '-o', str(output_file)] + extra_args
590
+ cmd = [
591
+ pandoc_cmd,
592
+ str(output_path),
593
+ "-o",
594
+ str(output_file),
595
+ ] + extra_args
489
596
  result = subprocess.run(
490
- cmd,
491
- capture_output=True,
492
- text=True,
493
- timeout=30
597
+ cmd, capture_output=True, text=True, timeout=30
494
598
  )
495
599
  if result.returncode == 0 and verbose:
496
600
  logger.info(f"Generated {format_name} report: {output_file}")
497
601
  elif verbose and result.returncode != 0:
498
- logger.warning(f"{format_name} generation failed: {result.stderr}")
602
+ logger.warning(
603
+ f"{format_name} generation failed: {result.stderr}"
604
+ )
499
605
  except subprocess.TimeoutExpired:
500
606
  if verbose:
501
607
  logger.warning(f"{format_name} conversion timed out")
502
608
  except Exception as e:
503
609
  if verbose:
504
610
  logger.warning(f"{format_name} conversion failed: {e}")
505
-
611
+
506
612
  # Convert to PDF (requires LaTeX)
507
- if shutil.which('xelatex') or shutil.which('pdflatex'):
613
+ if shutil.which("xelatex") or shutil.which("pdflatex"):
508
614
  try:
509
- pdf_path = output_path.with_suffix('.pdf')
510
- pdf_engine = 'xelatex' if shutil.which('xelatex') else 'pdflatex'
615
+ pdf_path = output_path.with_suffix(".pdf")
616
+ pdf_engine = "xelatex" if shutil.which("xelatex") else "pdflatex"
511
617
  result = subprocess.run(
512
- [pandoc_cmd, str(output_path), f'--pdf-engine={pdf_engine}', '-o', str(pdf_path)],
618
+ [
619
+ pandoc_cmd,
620
+ str(output_path),
621
+ f"--pdf-engine={pdf_engine}",
622
+ "-o",
623
+ str(pdf_path),
624
+ ],
513
625
  capture_output=True,
514
626
  text=True,
515
- timeout=60
627
+ timeout=60,
516
628
  )
517
629
  if result.returncode == 0 and verbose:
518
630
  logger.info(f"Generated PDF report: {pdf_path}")
@@ -525,8 +637,10 @@ def generate_org_report(
525
637
  if verbose:
526
638
  logger.warning(f"PDF conversion failed: {e}")
527
639
  elif verbose:
528
- logger.info("Pandoc not found. Skipping format conversions. Try 'module load Pandoc/3.1.2'")
529
-
640
+ logger.info(
641
+ "Pandoc not found. Skipping format conversions. Try 'module load Pandoc/3.1.2'"
642
+ )
643
+
530
644
  return output_path
531
645
 
532
646
 
@@ -538,7 +652,7 @@ def generate_markdown_report(
538
652
  ) -> Path:
539
653
  """
540
654
  Generate comprehensive markdown report.
541
-
655
+
542
656
  Parameters
543
657
  ----------
544
658
  results : Dict[str, Any]
@@ -549,7 +663,7 @@ def generate_markdown_report(
549
663
  Whether to include plot images
550
664
  verbose : bool, default True
551
665
  Whether to print progress messages
552
-
666
+
553
667
  Returns
554
668
  -------
555
669
  Path
@@ -557,105 +671,129 @@ def generate_markdown_report(
557
671
  """
558
672
  output_path = Path(output_path)
559
673
  output_path.parent.mkdir(parents=True, exist_ok=True)
560
-
561
- with open(output_path, 'w') as f:
674
+
675
+ with open(output_path, "w") as f:
562
676
  # Header
563
677
  f.write("# Classification Report\n\n")
564
678
  f.write(f"**Generated:** {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n")
565
-
679
+
566
680
  # Experiment Info
567
- config = results.get('config', {})
681
+ config = results.get("config", {})
568
682
  f.write("## Experiment Information\n\n")
569
683
  f.write(f"- **Experiment Name:** {config.get('name', 'Unknown')}\n")
570
684
  f.write(f"- **Number of Folds:** {config.get('n_folds', 'N/A')}\n")
571
685
  f.write(f"- **Output Directory:** `{config.get('output_dir', 'N/A')}`\n\n")
572
-
686
+
573
687
  # Summary Statistics
574
- if 'summary' in results and results['summary']:
688
+ if "summary" in results and results["summary"]:
575
689
  f.write("## Summary Statistics\n\n")
576
690
  f.write("| Metric | Mean ± Std | Min | Max |\n")
577
691
  f.write("|--------|------------|-----|-----|\n")
578
-
579
- for metric_name, stats in results['summary'].items():
580
- if isinstance(stats, dict) and 'mean' in stats:
581
- mean = stats.get('mean', 0)
582
- std = stats.get('std', 0)
583
- min_val = stats.get('min', 0)
584
- max_val = stats.get('max', 0)
585
- metric_display = metric_name.replace('_', ' ').title()
586
- f.write(f"| {metric_display} | {mean:.3f} ± {std:.3f} | "
587
- f"{min_val:.3f} | {max_val:.3f} |\n")
692
+
693
+ for metric_name, stats in results["summary"].items():
694
+ if isinstance(stats, dict) and "mean" in stats:
695
+ mean = stats.get("mean", 0)
696
+ std = stats.get("std", 0)
697
+ min_val = stats.get("min", 0)
698
+ max_val = stats.get("max", 0)
699
+ metric_display = metric_name.replace("_", " ").title()
700
+ f.write(
701
+ f"| {metric_display} | {mean:.3f} ± {std:.3f} | "
702
+ f"{min_val:.3f} | {max_val:.3f} |\n"
703
+ )
588
704
  f.write("\n")
589
-
705
+
590
706
  # CV Summary Results with Plots
591
- if include_plots and 'plots' in results:
707
+ if include_plots and "plots" in results:
592
708
  f.write("## CV Summary Results\n\n")
593
-
709
+
594
710
  # Find cv_summary plots
595
- cv_summary_plots = {k: v for k, v in results['plots'].items()
596
- if 'cv_summary' in k or 'cv-summary' in k}
711
+ cv_summary_plots = {
712
+ k: v
713
+ for k, v in results["plots"].items()
714
+ if "cv_summary" in k or "cv-summary" in k
715
+ }
597
716
 
598
717
  if cv_summary_plots:
599
718
  # Confusion Matrix (support both old and new naming)
600
- cm_plots = [v for k, v in cv_summary_plots.items()
601
- if ('confusion_matrix' in k or 'confusion-matrix' in k)]
719
+ cm_plots = [
720
+ v
721
+ for k, v in cv_summary_plots.items()
722
+ if ("confusion_matrix" in k or "confusion-matrix" in k)
723
+ ]
602
724
  if cm_plots:
603
725
  f.write("### CV Summary Confusion Matrix\n\n")
604
726
  for plot_path in cm_plots:
605
- rel_path = _make_relative_path(output_path.parent,
606
- Path(results.get('config', {}).get('output_dir', '.')) / plot_path)
727
+ rel_path = _make_relative_path(
728
+ output_path.parent,
729
+ Path(results.get("config", {}).get("output_dir", "."))
730
+ / plot_path,
731
+ )
607
732
  f.write(f"![Confusion Matrix]({rel_path})\n\n")
608
-
733
+
609
734
  # ROC Curve (support both old and new naming)
610
- roc_plots = [v for k, v in cv_summary_plots.items()
611
- if ('roc_curve' in k or 'roc-curve' in k)]
735
+ roc_plots = [
736
+ v
737
+ for k, v in cv_summary_plots.items()
738
+ if ("roc_curve" in k or "roc-curve" in k)
739
+ ]
612
740
  if roc_plots:
613
741
  f.write("### CV Summary ROC Curve\n\n")
614
742
  for plot_path in roc_plots:
615
- rel_path = _make_relative_path(output_path.parent,
616
- Path(results.get('config', {}).get('output_dir', '.')) / plot_path)
743
+ rel_path = _make_relative_path(
744
+ output_path.parent,
745
+ Path(results.get("config", {}).get("output_dir", "."))
746
+ / plot_path,
747
+ )
617
748
  f.write(f"![ROC Curve]({rel_path})\n\n")
618
-
749
+
619
750
  # PR Curve (support both old and new naming)
620
- pr_plots = [v for k, v in cv_summary_plots.items()
621
- if ('pr_curve' in k or 'pr-curve' in k)]
751
+ pr_plots = [
752
+ v
753
+ for k, v in cv_summary_plots.items()
754
+ if ("pr_curve" in k or "pr-curve" in k)
755
+ ]
622
756
  if pr_plots:
623
757
  f.write("### CV Summary Precision-Recall Curve\n\n")
624
758
  for plot_path in pr_plots:
625
- rel_path = _make_relative_path(output_path.parent,
626
- Path(results.get('config', {}).get('output_dir', '.')) / plot_path)
759
+ rel_path = _make_relative_path(
760
+ output_path.parent,
761
+ Path(results.get("config", {}).get("output_dir", "."))
762
+ / plot_path,
763
+ )
627
764
  f.write(f"![PR Curve]({rel_path})\n\n")
628
-
765
+
629
766
  # Per-Fold Results (abbreviated for brevity)
630
- if 'folds' in results and results['folds']:
767
+ if "folds" in results and results["folds"]:
631
768
  f.write("## Per-Fold Results\n\n")
632
-
769
+
633
770
  # Create summary table
634
771
  f.write("| Fold | Balanced Accuracy | ROC AUC | PR AUC | MCC |\n")
635
772
  f.write("|------|-------------------|---------|--------|-----|\n")
636
-
637
- for fold_data in results['folds']:
638
- fold_id = fold_data.get('fold_id', 0)
639
-
773
+
774
+ for fold_data in results["folds"]:
775
+ fold_id = fold_data.get("fold_id", 0)
776
+
640
777
  # Extract metrics
641
- bacc = _extract_metric_value(fold_data.get('balanced_accuracy'))
642
- roc = _extract_metric_value(fold_data.get('roc_auc'))
643
- pr = _extract_metric_value(fold_data.get('pr_auc'))
644
- mcc = _extract_metric_value(fold_data.get('mcc'))
645
-
778
+ bacc = _extract_metric_value(fold_data.get("balanced_accuracy"))
779
+ roc = _extract_metric_value(fold_data.get("roc_auc"))
780
+ pr = _extract_metric_value(fold_data.get("pr_auc"))
781
+ mcc = _extract_metric_value(fold_data.get("mcc"))
782
+
646
783
  f.write(f"| {fold_id:02d} | ")
647
784
  f.write(f"{bacc:.3f} | " if bacc is not None else "N/A | ")
648
785
  f.write(f"{roc:.3f} | " if roc is not None else "N/A | ")
649
786
  f.write(f"{pr:.3f} | " if pr is not None else "N/A | ")
650
787
  f.write(f"{mcc:.3f} |\n" if mcc is not None else "N/A |\n")
651
-
788
+
652
789
  f.write("\n")
653
-
790
+
654
791
  if verbose:
655
792
  from scitex.logging import getLogger
793
+
656
794
  logger = getLogger(__name__)
657
795
  logger.info(f"Generated markdown report: {output_path}")
658
-
796
+
659
797
  return output_path
660
798
 
661
799
 
@@ -666,7 +804,7 @@ def generate_latex_report(
666
804
  ) -> Path:
667
805
  """
668
806
  Generate LaTeX report for academic papers.
669
-
807
+
670
808
  Parameters
671
809
  ----------
672
810
  results : Dict[str, Any]
@@ -675,7 +813,7 @@ def generate_latex_report(
675
813
  Output file path
676
814
  verbose : bool, default True
677
815
  Whether to print progress messages
678
-
816
+
679
817
  Returns
680
818
  -------
681
819
  Path
@@ -683,8 +821,8 @@ def generate_latex_report(
683
821
  """
684
822
  output_path = Path(output_path)
685
823
  output_path.parent.mkdir(parents=True, exist_ok=True)
686
-
687
- with open(output_path, 'w') as f:
824
+
825
+ with open(output_path, "w") as f:
688
826
  # Document setup
689
827
  f.write("\\documentclass[11pt]{article}\n")
690
828
  f.write("\\usepackage{booktabs}\n")
@@ -692,24 +830,28 @@ def generate_latex_report(
692
830
  f.write("\\usepackage{float}\n")
693
831
  f.write("\\usepackage{amsmath}\n")
694
832
  f.write("\\usepackage{hyperref}\n\n")
695
-
833
+
696
834
  f.write("\\title{Classification Report}\n")
697
835
  f.write(f"\\date{{\\today}}\n\n")
698
-
836
+
699
837
  f.write("\\begin{document}\n")
700
838
  f.write("\\maketitle\n\n")
701
-
839
+
702
840
  # Experiment Information
703
- config = results.get('config', {})
841
+ config = results.get("config", {})
704
842
  f.write("\\section{Experiment Information}\n\n")
705
843
  f.write("\\begin{itemize}\n")
706
- f.write(f"\\item \\textbf{{Experiment Name:}} {_latex_escape(config.get('name', 'Unknown'))}\n")
844
+ f.write(
845
+ f"\\item \\textbf{{Experiment Name:}} {_latex_escape(config.get('name', 'Unknown'))}\n"
846
+ )
707
847
  f.write(f"\\item \\textbf{{Number of Folds:}} {config.get('n_folds', 'N/A')}\n")
708
- f.write(f"\\item \\textbf{{Output Directory:}} \\texttt{{{_latex_escape(str(config.get('output_dir', 'N/A')))}}}\n")
848
+ f.write(
849
+ f"\\item \\textbf{{Output Directory:}} \\texttt{{{_latex_escape(str(config.get('output_dir', 'N/A')))}}}\n"
850
+ )
709
851
  f.write("\\end{itemize}\n\n")
710
-
852
+
711
853
  # Summary Statistics
712
- if 'summary' in results and results['summary']:
854
+ if "summary" in results and results["summary"]:
713
855
  f.write("\\section{Summary Statistics}\n\n")
714
856
  f.write("\\begin{table}[H]\n")
715
857
  f.write("\\centering\n")
@@ -717,105 +859,128 @@ def generate_latex_report(
717
859
  f.write("\\toprule\n")
718
860
  f.write("Metric & Mean $\\pm$ Std & Min & Max \\\\\n")
719
861
  f.write("\\midrule\n")
720
-
721
- for metric_name, stats in results['summary'].items():
722
- if isinstance(stats, dict) and 'mean' in stats:
723
- mean = stats.get('mean', 0)
724
- std = stats.get('std', 0)
725
- min_val = stats.get('min', 0)
726
- max_val = stats.get('max', 0)
727
- metric_display = metric_name.replace('_', ' ').title()
728
- f.write(f"{_latex_escape(metric_display)} & "
729
- f"${mean:.3f} \\pm {std:.3f}$ & "
730
- f"{min_val:.3f} & {max_val:.3f} \\\\\n")
731
-
862
+
863
+ for metric_name, stats in results["summary"].items():
864
+ if isinstance(stats, dict) and "mean" in stats:
865
+ mean = stats.get("mean", 0)
866
+ std = stats.get("std", 0)
867
+ min_val = stats.get("min", 0)
868
+ max_val = stats.get("max", 0)
869
+ metric_display = metric_name.replace("_", " ").title()
870
+ f.write(
871
+ f"{_latex_escape(metric_display)} & "
872
+ f"${mean:.3f} \\pm {std:.3f}$ & "
873
+ f"{min_val:.3f} & {max_val:.3f} \\\\\n"
874
+ )
875
+
732
876
  f.write("\\bottomrule\n")
733
877
  f.write("\\end{tabular}\n")
734
878
  f.write("\\caption{Cross-validation performance metrics}\n")
735
879
  f.write("\\label{tab:cv_metrics}\n")
736
880
  f.write("\\end{table}\n\n")
737
-
881
+
738
882
  # Plots (if available)
739
- if 'plots' in results:
883
+ if "plots" in results:
740
884
  f.write("\\section{Visualizations}\n\n")
741
-
885
+
742
886
  # CV Summary plots (support both old and new naming)
743
- cv_summary_plots = {k: v for k, v in results['plots'].items()
744
- if 'cv_summary' in k or 'cv-summary' in k}
887
+ cv_summary_plots = {
888
+ k: v
889
+ for k, v in results["plots"].items()
890
+ if "cv_summary" in k or "cv-summary" in k
891
+ }
745
892
 
746
893
  if cv_summary_plots:
747
894
  # Find specific plot types (support both old underscore and new hyphen naming)
748
895
  for plot_type, plot_type_alt, title in [
749
- ('confusion_matrix', 'confusion-matrix', 'CV Summary Confusion Matrix'),
750
- ('roc_curve', 'roc-curve', 'CV Summary ROC Curve'),
751
- ('pr_curve', 'pr-curve', 'CV Summary Precision-Recall Curve')
896
+ (
897
+ "confusion_matrix",
898
+ "confusion-matrix",
899
+ "CV Summary Confusion Matrix",
900
+ ),
901
+ ("roc_curve", "roc-curve", "CV Summary ROC Curve"),
902
+ ("pr_curve", "pr-curve", "CV Summary Precision-Recall Curve"),
752
903
  ]:
753
- type_plots = [v for k, v in cv_summary_plots.items()
754
- if (plot_type in k or plot_type_alt in k)]
904
+ type_plots = [
905
+ v
906
+ for k, v in cv_summary_plots.items()
907
+ if (plot_type in k or plot_type_alt in k)
908
+ ]
755
909
  if type_plots:
756
910
  f.write(f"\\subsection{{{title}}}\n\n")
757
911
  for plot_path in type_plots:
758
- rel_path = _make_relative_path(output_path.parent,
759
- Path(results.get('config', {}).get('output_dir', '.')) / plot_path)
912
+ rel_path = _make_relative_path(
913
+ output_path.parent,
914
+ Path(results.get("config", {}).get("output_dir", "."))
915
+ / plot_path,
916
+ )
760
917
  f.write("\\begin{figure}[H]\n")
761
918
  f.write("\\centering\n")
762
- f.write(f"\\includegraphics[width=0.8\\textwidth]{{{rel_path}}}\n")
919
+ f.write(
920
+ f"\\includegraphics[width=0.8\\textwidth]{{{rel_path}}}\n"
921
+ )
763
922
  f.write(f"\\caption{{{title}}}\n")
764
923
  f.write(f"\\label{{fig:{plot_type}_cv_summary}}\n")
765
924
  f.write("\\end{figure}\n\n")
766
-
925
+
767
926
  f.write("\\end{document}\n")
768
-
927
+
769
928
  if verbose:
770
929
  from scitex.logging import getLogger
930
+
771
931
  logger = getLogger(__name__)
772
932
  logger.info(f"Generated LaTeX report: {output_path}")
773
-
933
+
774
934
  return output_path
775
935
 
776
936
 
777
937
  def create_summary_statistics(results: Dict[str, Any]) -> Dict[str, Any]:
778
938
  """
779
939
  Create comprehensive summary statistics from results.
780
-
940
+
781
941
  Parameters
782
942
  ----------
783
943
  results : Dict[str, Any]
784
944
  Classification results
785
-
945
+
786
946
  Returns
787
947
  -------
788
948
  Dict[str, Any]
789
949
  Summary statistics
790
950
  """
791
951
  summary = {}
792
-
793
- if 'folds' in results:
952
+
953
+ if "folds" in results:
794
954
  # Aggregate metrics across folds
795
955
  metrics_to_aggregate = [
796
- 'balanced_accuracy', 'roc_auc', 'pr_auc', 'mcc',
797
- 'precision', 'recall', 'f1_score'
956
+ "balanced_accuracy",
957
+ "roc_auc",
958
+ "pr_auc",
959
+ "mcc",
960
+ "precision",
961
+ "recall",
962
+ "f1_score",
798
963
  ]
799
-
964
+
800
965
  for metric_name in metrics_to_aggregate:
801
966
  values = []
802
- for fold_data in results['folds']:
967
+ for fold_data in results["folds"]:
803
968
  if metric_name in fold_data:
804
969
  value = _extract_metric_value(fold_data[metric_name])
805
970
  if value is not None:
806
971
  values.append(value)
807
-
972
+
808
973
  if values:
809
974
  values = np.array(values)
810
975
  summary[metric_name] = {
811
- 'mean': float(np.mean(values)),
812
- 'std': float(np.std(values)),
813
- 'min': float(np.min(values)),
814
- 'max': float(np.max(values)),
815
- 'median': float(np.median(values)),
816
- 'values': values.tolist()
976
+ "mean": float(np.mean(values)),
977
+ "std": float(np.std(values)),
978
+ "min": float(np.min(values)),
979
+ "max": float(np.max(values)),
980
+ "median": float(np.median(values)),
981
+ "values": values.tolist(),
817
982
  }
818
-
983
+
819
984
  return summary
820
985
 
821
986
 
@@ -826,7 +991,7 @@ def export_for_paper(
826
991
  ) -> Dict[str, Path]:
827
992
  """
828
993
  Export results in formats suitable for academic papers.
829
-
994
+
830
995
  Parameters
831
996
  ----------
832
997
  results : Dict[str, Any]
@@ -835,7 +1000,7 @@ def export_for_paper(
835
1000
  Output directory for exports
836
1001
  verbose : bool, default True
837
1002
  Whether to print progress messages
838
-
1003
+
839
1004
  Returns
840
1005
  -------
841
1006
  Dict[str, Path]
@@ -843,83 +1008,88 @@ def export_for_paper(
843
1008
  """
844
1009
  output_dir = Path(output_dir)
845
1010
  output_dir.mkdir(parents=True, exist_ok=True)
846
-
1011
+
847
1012
  exported_files = {}
848
-
1013
+
849
1014
  # Export summary metrics as LaTeX table
850
- if 'summary' in results:
1015
+ if "summary" in results:
851
1016
  latex_table_path = output_dir / "metrics_table.tex"
852
- _export_metrics_table_latex(results['summary'], latex_table_path)
853
- exported_files['metrics_table'] = latex_table_path
854
-
1017
+ _export_metrics_table_latex(results["summary"], latex_table_path)
1018
+ exported_files["metrics_table"] = latex_table_path
1019
+
855
1020
  # Also export as CSV for easier processing
856
1021
  csv_table_path = output_dir / "summary_table.csv"
857
- _export_summary_table_csv(results['summary'], csv_table_path)
858
- exported_files['summary_table'] = csv_table_path
859
-
1022
+ _export_summary_table_csv(results["summary"], csv_table_path)
1023
+ exported_files["summary_table"] = csv_table_path
1024
+
860
1025
  # Export raw results as JSON
861
1026
  import json
1027
+
862
1028
  raw_results_path = output_dir / "raw_results.json"
863
- with open(raw_results_path, 'w') as f:
1029
+ with open(raw_results_path, "w") as f:
864
1030
  # Create serializable version of results
865
1031
  serializable_results = _make_serializable(results)
866
1032
  json.dump(serializable_results, f, indent=2)
867
- exported_files['raw_results'] = raw_results_path
868
-
1033
+ exported_files["raw_results"] = raw_results_path
1034
+
869
1035
  # Export confusion matrix as CSV
870
- if 'overall_confusion_matrix' in results:
1036
+ if "overall_confusion_matrix" in results:
871
1037
  cm_path = output_dir / "confusion_matrix.csv"
872
- cm_data = np.array(results['overall_confusion_matrix'])
1038
+ cm_data = np.array(results["overall_confusion_matrix"])
873
1039
  cm_df = pd.DataFrame(cm_data)
874
1040
  cm_df.to_csv(cm_path, index=True)
875
- exported_files['confusion_matrix'] = cm_path
876
-
1041
+ exported_files["confusion_matrix"] = cm_path
1042
+
877
1043
  # Copy key plots
878
- config = results.get('config', {})
879
- base_dir = Path(results.get('config', {}).get('output_dir', '.'))
880
-
881
- if 'plots' in results:
1044
+ config = results.get("config", {})
1045
+ base_dir = Path(results.get("config", {}).get("output_dir", "."))
1046
+
1047
+ if "plots" in results:
882
1048
  plots_dir = output_dir / "figures"
883
1049
  plots_dir.mkdir(exist_ok=True)
884
-
1050
+
885
1051
  # Copy cv_summary plots with standardized names
886
- cv_summary_plots = {k: v for k, v in results['plots'].items()
887
- if 'cv_summary' in k}
888
-
1052
+ cv_summary_plots = {
1053
+ k: v for k, v in results["plots"].items() if "cv_summary" in k
1054
+ }
1055
+
889
1056
  for plot_key, plot_path in cv_summary_plots.items():
890
1057
  src_path = base_dir / plot_path
891
1058
  if src_path.exists():
892
1059
  # Standardize filename
893
- if 'confusion_matrix' in plot_key:
1060
+ if "confusion_matrix" in plot_key:
894
1061
  dest_name = "confusion_matrix_cv_summary.jpg"
895
- elif 'roc_curve' in plot_key:
1062
+ elif "roc_curve" in plot_key:
896
1063
  dest_name = "roc_curve_cv_summary.jpg"
897
- elif 'pr_curve' in plot_key:
1064
+ elif "pr_curve" in plot_key:
898
1065
  dest_name = "pr_curve_cv_summary.jpg"
899
1066
  else:
900
1067
  dest_name = src_path.name
901
-
1068
+
902
1069
  dest_path = plots_dir / dest_name
903
1070
  import shutil
1071
+
904
1072
  shutil.copy2(src_path, dest_path)
905
1073
  exported_files[f"figure_{dest_name.split('.')[0]}"] = dest_path
906
-
1074
+
907
1075
  if verbose:
908
1076
  from scitex.logging import getLogger
1077
+
909
1078
  logger = getLogger(__name__)
910
1079
  logger.info(f"Exported {len(exported_files)} files for paper to {output_dir}")
911
-
1080
+
912
1081
  return exported_files
913
1082
 
914
1083
 
915
1084
  # Helper functions
916
1085
 
1086
+
917
1087
  def _extract_metric_value(metric_data: Any) -> Optional[float]:
918
1088
  """Extract numeric value from metric data."""
919
1089
  if metric_data is None:
920
1090
  return None
921
- if isinstance(metric_data, dict) and 'value' in metric_data:
922
- return float(metric_data['value'])
1091
+ if isinstance(metric_data, dict) and "value" in metric_data:
1092
+ return float(metric_data["value"])
923
1093
  if isinstance(metric_data, (int, float, np.number)):
924
1094
  return float(metric_data)
925
1095
  return None
@@ -938,17 +1108,17 @@ def _make_relative_path(from_dir: Path, to_path: Path) -> str:
938
1108
  common_parts = 0
939
1109
  from_parts = from_dir.parts
940
1110
  to_parts = Path(to_path).parts
941
-
1111
+
942
1112
  for fp, tp in zip(from_parts, to_parts):
943
1113
  if fp == tp:
944
1114
  common_parts += 1
945
1115
  else:
946
1116
  break
947
-
1117
+
948
1118
  # Build relative path
949
1119
  ups = len(from_parts) - common_parts
950
- rel_parts = ['..'] * ups + list(to_parts[common_parts:])
951
- return '/'.join(rel_parts)
1120
+ rel_parts = [".."] * ups + list(to_parts[common_parts:])
1121
+ return "/".join(rel_parts)
952
1122
  except:
953
1123
  # Fallback to absolute path
954
1124
  return str(to_path)
@@ -958,32 +1128,29 @@ def _latex_escape(text: str) -> str:
958
1128
  """Escape special LaTeX characters."""
959
1129
  if not isinstance(text, str):
960
1130
  text = str(text)
961
-
1131
+
962
1132
  replacements = {
963
- '\\': '\\textbackslash{}',
964
- '{': '\\{',
965
- '}': '\\}',
966
- '$': '\\$',
967
- '&': '\\&',
968
- '%': '\\%',
969
- '#': '\\#',
970
- '_': '\\_',
971
- '~': '\\textasciitilde{}',
972
- '^': '\\textasciicircum{}'
1133
+ "\\": "\\textbackslash{}",
1134
+ "{": "\\{",
1135
+ "}": "\\}",
1136
+ "$": "\\$",
1137
+ "&": "\\&",
1138
+ "%": "\\%",
1139
+ "#": "\\#",
1140
+ "_": "\\_",
1141
+ "~": "\\textasciitilde{}",
1142
+ "^": "\\textasciicircum{}",
973
1143
  }
974
-
1144
+
975
1145
  for old, new in replacements.items():
976
1146
  text = text.replace(old, new)
977
-
1147
+
978
1148
  return text
979
1149
 
980
1150
 
981
- def _export_metrics_table_latex(
982
- summary: Dict[str, Any],
983
- output_path: Path
984
- ) -> None:
1151
+ def _export_metrics_table_latex(summary: Dict[str, Any], output_path: Path) -> None:
985
1152
  """Export summary metrics as a LaTeX table."""
986
- with open(output_path, 'w') as f:
1153
+ with open(output_path, "w") as f:
987
1154
  f.write("% Classification metrics summary table\n")
988
1155
  f.write("\\begin{table}[htbp]\n")
989
1156
  f.write("\\centering\n")
@@ -991,18 +1158,20 @@ def _export_metrics_table_latex(
991
1158
  f.write("\\toprule\n")
992
1159
  f.write("Metric & Mean $\\pm$ Std & Min & Max \\\\\n")
993
1160
  f.write("\\midrule\n")
994
-
1161
+
995
1162
  for metric_name, stats in summary.items():
996
- if isinstance(stats, dict) and 'mean' in stats:
997
- mean = stats.get('mean', 0)
998
- std = stats.get('std', 0)
999
- min_val = stats.get('min', 0)
1000
- max_val = stats.get('max', 0)
1001
- metric_display = metric_name.replace('_', ' ').title()
1002
- f.write(f"{_latex_escape(metric_display)} & "
1003
- f"${mean:.3f} \\pm {std:.3f}$ & "
1004
- f"{min_val:.3f} & {max_val:.3f} \\\\\n")
1005
-
1163
+ if isinstance(stats, dict) and "mean" in stats:
1164
+ mean = stats.get("mean", 0)
1165
+ std = stats.get("std", 0)
1166
+ min_val = stats.get("min", 0)
1167
+ max_val = stats.get("max", 0)
1168
+ metric_display = metric_name.replace("_", " ").title()
1169
+ f.write(
1170
+ f"{_latex_escape(metric_display)} & "
1171
+ f"${mean:.3f} \\pm {std:.3f}$ & "
1172
+ f"{min_val:.3f} & {max_val:.3f} \\\\\n"
1173
+ )
1174
+
1006
1175
  f.write("\\bottomrule\n")
1007
1176
  f.write("\\end{tabular}\n")
1008
1177
  f.write("\\caption{Classification performance metrics}\n")
@@ -1010,24 +1179,21 @@ def _export_metrics_table_latex(
1010
1179
  f.write("\\end{table}\n")
1011
1180
 
1012
1181
 
1013
- def _export_summary_table_csv(
1014
- summary: Dict[str, Any],
1015
- output_path: Path
1016
- ) -> None:
1182
+ def _export_summary_table_csv(summary: Dict[str, Any], output_path: Path) -> None:
1017
1183
  """Export summary metrics as CSV table."""
1018
1184
  # Create DataFrame from summary
1019
1185
  data = []
1020
1186
  for metric_name, stats in summary.items():
1021
- if isinstance(stats, dict) and 'mean' in stats:
1187
+ if isinstance(stats, dict) and "mean" in stats:
1022
1188
  row = {
1023
- 'Metric': metric_name.replace('_', ' ').title(),
1024
- 'Mean': stats.get('mean', 0),
1025
- 'Std': stats.get('std', 0),
1026
- 'Min': stats.get('min', 0),
1027
- 'Max': stats.get('max', 0)
1189
+ "Metric": metric_name.replace("_", " ").title(),
1190
+ "Mean": stats.get("mean", 0),
1191
+ "Std": stats.get("std", 0),
1192
+ "Min": stats.get("min", 0),
1193
+ "Max": stats.get("max", 0),
1028
1194
  }
1029
1195
  data.append(row)
1030
-
1196
+
1031
1197
  if data:
1032
1198
  df = pd.DataFrame(data)
1033
1199
  df.to_csv(output_path, index=False)
@@ -1041,7 +1207,7 @@ def _make_serializable(obj: Any) -> Any:
1041
1207
  return float(obj)
1042
1208
  elif isinstance(obj, pd.DataFrame):
1043
1209
  # Convert DataFrame to dict with orient='list' for JSON serialization
1044
- return obj.to_dict(orient='list')
1210
+ return obj.to_dict(orient="list")
1045
1211
  elif isinstance(obj, pd.Series):
1046
1212
  return obj.tolist()
1047
1213
  elif isinstance(obj, dict):
@@ -1053,4 +1219,5 @@ def _make_serializable(obj: Any) -> Any:
1053
1219
  else:
1054
1220
  return obj
1055
1221
 
1056
- # EOF
1222
+
1223
+ # EOF