scitex 2.5.0__py3-none-any.whl → 2.7.3__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 (1179) hide show
  1. scitex/__init__.py +25 -10
  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/audio/README.md +52 -0
  66. scitex/audio/__init__.py +384 -0
  67. scitex/audio/__main__.py +129 -0
  68. scitex/audio/_tts.py +334 -0
  69. scitex/audio/engines/__init__.py +44 -0
  70. scitex/audio/engines/base.py +275 -0
  71. scitex/audio/engines/elevenlabs_engine.py +143 -0
  72. scitex/audio/engines/gtts_engine.py +162 -0
  73. scitex/audio/engines/pyttsx3_engine.py +131 -0
  74. scitex/audio/mcp_server.py +757 -0
  75. scitex/benchmark/__init__.py +15 -25
  76. scitex/benchmark/benchmark.py +124 -117
  77. scitex/benchmark/monitor.py +117 -107
  78. scitex/benchmark/profiler.py +61 -58
  79. scitex/bridge/__init__.py +110 -0
  80. scitex/bridge/_helpers.py +149 -0
  81. scitex/bridge/_plt_vis.py +529 -0
  82. scitex/bridge/_protocol.py +283 -0
  83. scitex/bridge/_stats_plt.py +261 -0
  84. scitex/bridge/_stats_vis.py +265 -0
  85. scitex/browser/__init__.py +0 -2
  86. scitex/browser/auth/__init__.py +0 -0
  87. scitex/browser/auth/google.py +16 -11
  88. scitex/browser/automation/CookieHandler.py +2 -3
  89. scitex/browser/collaboration/__init__.py +3 -0
  90. scitex/browser/collaboration/auth_helpers.py +3 -1
  91. scitex/browser/collaboration/collaborative_agent.py +2 -0
  92. scitex/browser/collaboration/interactive_panel.py +2 -2
  93. scitex/browser/collaboration/shared_session.py +20 -11
  94. scitex/browser/collaboration/standard_interactions.py +1 -0
  95. scitex/browser/core/BrowserMixin.py +12 -30
  96. scitex/browser/core/ChromeProfileManager.py +9 -24
  97. scitex/browser/debugging/_browser_logger.py +15 -25
  98. scitex/browser/debugging/_failure_capture.py +9 -2
  99. scitex/browser/debugging/_highlight_element.py +15 -6
  100. scitex/browser/debugging/_show_grid.py +5 -6
  101. scitex/browser/debugging/_sync_session.py +4 -3
  102. scitex/browser/debugging/_test_monitor.py +14 -5
  103. scitex/browser/debugging/_visual_cursor.py +46 -35
  104. scitex/browser/interaction/click_center.py +4 -3
  105. scitex/browser/interaction/click_with_fallbacks.py +7 -10
  106. scitex/browser/interaction/close_popups.py +79 -66
  107. scitex/browser/interaction/fill_with_fallbacks.py +8 -8
  108. scitex/browser/pdf/__init__.py +3 -1
  109. scitex/browser/pdf/click_download_for_chrome_pdf_viewer.py +11 -10
  110. scitex/browser/pdf/detect_chrome_pdf_viewer.py +3 -6
  111. scitex/browser/remote/CaptchaHandler.py +109 -96
  112. scitex/browser/remote/ZenRowsAPIClient.py +91 -97
  113. scitex/browser/remote/ZenRowsBrowserManager.py +138 -112
  114. scitex/browser/stealth/HumanBehavior.py +4 -9
  115. scitex/browser/stealth/StealthManager.py +11 -26
  116. scitex/capture/__init__.py +17 -17
  117. scitex/capture/__main__.py +2 -3
  118. scitex/capture/capture.py +23 -51
  119. scitex/capture/cli.py +14 -39
  120. scitex/capture/gif.py +5 -9
  121. scitex/capture/mcp_server.py +7 -20
  122. scitex/capture/session.py +4 -3
  123. scitex/capture/utils.py +18 -53
  124. scitex/cli/__init__.py +1 -1
  125. scitex/cli/cloud.py +158 -116
  126. scitex/cli/config.py +224 -0
  127. scitex/cli/main.py +41 -40
  128. scitex/cli/scholar.py +60 -27
  129. scitex/cli/security.py +14 -20
  130. scitex/cli/web.py +87 -90
  131. scitex/cli/writer.py +51 -45
  132. scitex/cloud/__init__.py +14 -11
  133. scitex/cloud/_matplotlib_hook.py +6 -6
  134. scitex/config/README.md +313 -0
  135. scitex/config/{PriorityConfig.py → _PriorityConfig.py} +114 -17
  136. scitex/config/_ScitexConfig.py +319 -0
  137. scitex/config/__init__.py +41 -9
  138. scitex/config/_paths.py +325 -0
  139. scitex/config/default.yaml +81 -0
  140. scitex/context/_suppress_output.py +2 -3
  141. scitex/db/_BaseMixins/_BaseBackupMixin.py +3 -1
  142. scitex/db/_BaseMixins/_BaseBatchMixin.py +3 -1
  143. scitex/db/_BaseMixins/_BaseBlobMixin.py +3 -1
  144. scitex/db/_BaseMixins/_BaseImportExportMixin.py +1 -3
  145. scitex/db/_BaseMixins/_BaseIndexMixin.py +3 -1
  146. scitex/db/_BaseMixins/_BaseMaintenanceMixin.py +1 -3
  147. scitex/db/_BaseMixins/_BaseQueryMixin.py +3 -1
  148. scitex/db/_BaseMixins/_BaseRowMixin.py +3 -1
  149. scitex/db/_BaseMixins/_BaseTableMixin.py +3 -1
  150. scitex/db/_BaseMixins/_BaseTransactionMixin.py +1 -3
  151. scitex/db/_BaseMixins/__init__.py +1 -1
  152. scitex/db/__init__.py +9 -1
  153. scitex/db/__main__.py +8 -21
  154. scitex/db/_check_health.py +15 -31
  155. scitex/db/_delete_duplicates.py +7 -4
  156. scitex/db/_inspect.py +22 -38
  157. scitex/db/_inspect_optimized.py +89 -85
  158. scitex/db/_postgresql/_PostgreSQL.py +0 -1
  159. scitex/db/_postgresql/_PostgreSQLMixins/_BlobMixin.py +3 -1
  160. scitex/db/_postgresql/_PostgreSQLMixins/_ConnectionMixin.py +1 -3
  161. scitex/db/_postgresql/_PostgreSQLMixins/_ImportExportMixin.py +1 -3
  162. scitex/db/_postgresql/_PostgreSQLMixins/_MaintenanceMixin.py +1 -4
  163. scitex/db/_postgresql/_PostgreSQLMixins/_QueryMixin.py +3 -3
  164. scitex/db/_postgresql/_PostgreSQLMixins/_RowMixin.py +3 -1
  165. scitex/db/_postgresql/_PostgreSQLMixins/_TransactionMixin.py +1 -3
  166. scitex/db/_postgresql/__init__.py +1 -1
  167. scitex/db/_sqlite3/_SQLite3.py +2 -4
  168. scitex/db/_sqlite3/_SQLite3Mixins/_ArrayMixin.py +11 -12
  169. scitex/db/_sqlite3/_SQLite3Mixins/_ArrayMixin_v01-need-_hash-col.py +19 -14
  170. scitex/db/_sqlite3/_SQLite3Mixins/_BatchMixin.py +3 -1
  171. scitex/db/_sqlite3/_SQLite3Mixins/_BlobMixin.py +7 -7
  172. scitex/db/_sqlite3/_SQLite3Mixins/_ColumnMixin.py +118 -111
  173. scitex/db/_sqlite3/_SQLite3Mixins/_ConnectionMixin.py +8 -10
  174. scitex/db/_sqlite3/_SQLite3Mixins/_GitMixin.py +17 -45
  175. scitex/db/_sqlite3/_SQLite3Mixins/_ImportExportMixin.py +1 -3
  176. scitex/db/_sqlite3/_SQLite3Mixins/_IndexMixin.py +3 -1
  177. scitex/db/_sqlite3/_SQLite3Mixins/_QueryMixin.py +3 -4
  178. scitex/db/_sqlite3/_SQLite3Mixins/_RowMixin.py +9 -9
  179. scitex/db/_sqlite3/_SQLite3Mixins/_TableMixin.py +18 -11
  180. scitex/db/_sqlite3/_SQLite3Mixins/__init__.py +1 -0
  181. scitex/db/_sqlite3/__init__.py +1 -1
  182. scitex/db/_sqlite3/_delete_duplicates.py +13 -11
  183. scitex/decorators/__init__.py +29 -4
  184. scitex/decorators/_auto_order.py +43 -43
  185. scitex/decorators/_batch_fn.py +12 -6
  186. scitex/decorators/_cache_disk.py +8 -9
  187. scitex/decorators/_cache_disk_async.py +8 -7
  188. scitex/decorators/_combined.py +19 -13
  189. scitex/decorators/_converters.py +16 -3
  190. scitex/decorators/_deprecated.py +32 -22
  191. scitex/decorators/_numpy_fn.py +18 -4
  192. scitex/decorators/_pandas_fn.py +17 -5
  193. scitex/decorators/_signal_fn.py +17 -3
  194. scitex/decorators/_torch_fn.py +32 -15
  195. scitex/decorators/_xarray_fn.py +23 -9
  196. scitex/dev/_analyze_code_flow.py +0 -2
  197. scitex/dev/plt/__init__.py +272 -0
  198. scitex/dev/plt/plot_mpl_axhline.py +28 -0
  199. scitex/dev/plt/plot_mpl_axhspan.py +28 -0
  200. scitex/dev/plt/plot_mpl_axvline.py +28 -0
  201. scitex/dev/plt/plot_mpl_axvspan.py +28 -0
  202. scitex/dev/plt/plot_mpl_bar.py +29 -0
  203. scitex/dev/plt/plot_mpl_barh.py +29 -0
  204. scitex/dev/plt/plot_mpl_boxplot.py +28 -0
  205. scitex/dev/plt/plot_mpl_contour.py +31 -0
  206. scitex/dev/plt/plot_mpl_contourf.py +31 -0
  207. scitex/dev/plt/plot_mpl_errorbar.py +30 -0
  208. scitex/dev/plt/plot_mpl_eventplot.py +28 -0
  209. scitex/dev/plt/plot_mpl_fill.py +30 -0
  210. scitex/dev/plt/plot_mpl_fill_between.py +31 -0
  211. scitex/dev/plt/plot_mpl_hexbin.py +28 -0
  212. scitex/dev/plt/plot_mpl_hist.py +28 -0
  213. scitex/dev/plt/plot_mpl_hist2d.py +28 -0
  214. scitex/dev/plt/plot_mpl_imshow.py +29 -0
  215. scitex/dev/plt/plot_mpl_pcolormesh.py +31 -0
  216. scitex/dev/plt/plot_mpl_pie.py +29 -0
  217. scitex/dev/plt/plot_mpl_plot.py +29 -0
  218. scitex/dev/plt/plot_mpl_quiver.py +31 -0
  219. scitex/dev/plt/plot_mpl_scatter.py +28 -0
  220. scitex/dev/plt/plot_mpl_stackplot.py +31 -0
  221. scitex/dev/plt/plot_mpl_stem.py +29 -0
  222. scitex/dev/plt/plot_mpl_step.py +29 -0
  223. scitex/dev/plt/plot_mpl_violinplot.py +28 -0
  224. scitex/dev/plt/plot_sns_barplot.py +29 -0
  225. scitex/dev/plt/plot_sns_boxplot.py +29 -0
  226. scitex/dev/plt/plot_sns_heatmap.py +28 -0
  227. scitex/dev/plt/plot_sns_histplot.py +29 -0
  228. scitex/dev/plt/plot_sns_kdeplot.py +29 -0
  229. scitex/dev/plt/plot_sns_lineplot.py +31 -0
  230. scitex/dev/plt/plot_sns_scatterplot.py +29 -0
  231. scitex/dev/plt/plot_sns_stripplot.py +29 -0
  232. scitex/dev/plt/plot_sns_swarmplot.py +29 -0
  233. scitex/dev/plt/plot_sns_violinplot.py +29 -0
  234. scitex/dev/plt/plot_stx_bar.py +29 -0
  235. scitex/dev/plt/plot_stx_barh.py +29 -0
  236. scitex/dev/plt/plot_stx_box.py +28 -0
  237. scitex/dev/plt/plot_stx_boxplot.py +28 -0
  238. scitex/dev/plt/plot_stx_conf_mat.py +28 -0
  239. scitex/dev/plt/plot_stx_contour.py +31 -0
  240. scitex/dev/plt/plot_stx_ecdf.py +28 -0
  241. scitex/dev/plt/plot_stx_errorbar.py +30 -0
  242. scitex/dev/plt/plot_stx_fill_between.py +31 -0
  243. scitex/dev/plt/plot_stx_fillv.py +28 -0
  244. scitex/dev/plt/plot_stx_heatmap.py +28 -0
  245. scitex/dev/plt/plot_stx_image.py +28 -0
  246. scitex/dev/plt/plot_stx_imshow.py +28 -0
  247. scitex/dev/plt/plot_stx_joyplot.py +28 -0
  248. scitex/dev/plt/plot_stx_kde.py +28 -0
  249. scitex/dev/plt/plot_stx_line.py +28 -0
  250. scitex/dev/plt/plot_stx_mean_ci.py +28 -0
  251. scitex/dev/plt/plot_stx_mean_std.py +28 -0
  252. scitex/dev/plt/plot_stx_median_iqr.py +28 -0
  253. scitex/dev/plt/plot_stx_raster.py +28 -0
  254. scitex/dev/plt/plot_stx_rectangle.py +28 -0
  255. scitex/dev/plt/plot_stx_scatter.py +29 -0
  256. scitex/dev/plt/plot_stx_shaded_line.py +29 -0
  257. scitex/dev/plt/plot_stx_violin.py +28 -0
  258. scitex/dev/plt/plot_stx_violinplot.py +28 -0
  259. scitex/dict/_DotDict.py +15 -19
  260. scitex/dict/_flatten.py +1 -0
  261. scitex/dict/_listed_dict.py +1 -0
  262. scitex/dict/_pop_keys.py +1 -0
  263. scitex/dict/_replace.py +1 -0
  264. scitex/dict/_safe_merge.py +1 -0
  265. scitex/dict/_to_str.py +2 -3
  266. scitex/dsp/__init__.py +13 -4
  267. scitex/dsp/_crop.py +3 -1
  268. scitex/dsp/_detect_ripples.py +3 -1
  269. scitex/dsp/_modulation_index.py +3 -1
  270. scitex/dsp/_time.py +3 -1
  271. scitex/dsp/_wavelet.py +0 -1
  272. scitex/dsp/example.py +0 -5
  273. scitex/dsp/filt.py +4 -0
  274. scitex/dsp/utils/__init__.py +4 -1
  275. scitex/dsp/utils/pac.py +3 -3
  276. scitex/dt/_normalize_timestamp.py +4 -1
  277. scitex/errors.py +3 -6
  278. scitex/etc/__init__.py +1 -1
  279. scitex/fig/__init__.py +352 -0
  280. scitex/{vis → fig}/backend/__init__.py +3 -3
  281. scitex/{vis/backend/export.py → fig/backend/_export.py} +1 -1
  282. scitex/{vis/backend/parser.py → fig/backend/_parser.py} +2 -4
  283. scitex/{vis/backend/render.py → fig/backend/_render.py} +1 -1
  284. scitex/{vis → fig}/canvas.py +16 -4
  285. scitex/{vis → fig}/editor/__init__.py +0 -0
  286. scitex/{vis → fig}/editor/_dearpygui_editor.py +450 -304
  287. scitex/fig/editor/_defaults.py +300 -0
  288. scitex/fig/editor/_edit.py +751 -0
  289. scitex/{vis → fig}/editor/_flask_editor.py +8 -8
  290. scitex/{vis → fig}/editor/_mpl_editor.py +63 -48
  291. scitex/{vis → fig}/editor/_qt_editor.py +391 -160
  292. scitex/{vis → fig}/editor/_tkinter_editor.py +146 -89
  293. scitex/fig/editor/flask_editor/__init__.py +21 -0
  294. scitex/fig/editor/flask_editor/_bbox.py +1276 -0
  295. scitex/fig/editor/flask_editor/_core.py +624 -0
  296. scitex/fig/editor/flask_editor/_plotter.py +601 -0
  297. scitex/fig/editor/flask_editor/_renderer.py +739 -0
  298. scitex/{vis/editor/flask_editor/utils.py → fig/editor/flask_editor/_utils.py} +13 -14
  299. scitex/{vis → fig}/editor/flask_editor/templates/__init__.py +6 -6
  300. scitex/fig/editor/flask_editor/templates/_html.py +834 -0
  301. scitex/fig/editor/flask_editor/templates/_scripts.py +3136 -0
  302. scitex/fig/editor/flask_editor/templates/_styles.py +1346 -0
  303. scitex/{vis → fig}/io/__init__.py +18 -6
  304. scitex/fig/io/_bundle.py +973 -0
  305. scitex/{vis/io/canvas.py → fig/io/_canvas.py} +9 -5
  306. scitex/{vis/io/data.py → fig/io/_data.py} +14 -10
  307. scitex/{vis/io/directory.py → fig/io/_directory.py} +7 -4
  308. scitex/{vis/io/export.py → fig/io/_export.py} +16 -13
  309. scitex/{vis/io/load.py → fig/io/_load.py} +2 -2
  310. scitex/{vis/io/panel.py → fig/io/_panel.py} +22 -14
  311. scitex/{vis/io/save.py → fig/io/_save.py} +1 -1
  312. scitex/{vis → fig}/model/__init__.py +8 -8
  313. scitex/{vis/model/annotations.py → fig/model/_annotations.py} +3 -5
  314. scitex/{vis/model/axes.py → fig/model/_axes.py} +2 -2
  315. scitex/{vis/model/figure.py → fig/model/_figure.py} +1 -1
  316. scitex/{vis/model/guides.py → fig/model/_guides.py} +2 -2
  317. scitex/{vis/model/plot.py → fig/model/_plot.py} +3 -5
  318. scitex/{vis/model/plot_types.py → fig/model/_plot_types.py} +0 -0
  319. scitex/{vis/model/styles.py → fig/model/_styles.py} +1 -1
  320. scitex/{vis → fig}/utils/__init__.py +3 -3
  321. scitex/{vis/utils/defaults.py → fig/utils/_defaults.py} +1 -2
  322. scitex/{vis/utils/validate.py → fig/utils/_validate.py} +3 -9
  323. scitex/gen/_DimHandler.py +6 -6
  324. scitex/gen/__init__.py +5 -1
  325. scitex/gen/_deprecated_close.py +1 -0
  326. scitex/gen/_deprecated_start.py +5 -3
  327. scitex/gen/_detect_environment.py +44 -41
  328. scitex/gen/_detect_notebook_path.py +51 -47
  329. scitex/gen/_embed.py +1 -1
  330. scitex/gen/_get_notebook_path.py +81 -62
  331. scitex/gen/_inspect_module.py +0 -1
  332. scitex/gen/_norm.py +16 -7
  333. scitex/gen/_norm_cache.py +78 -65
  334. scitex/gen/_print_config.py +0 -3
  335. scitex/gen/_src.py +2 -3
  336. scitex/gen/_title_case.py +3 -2
  337. scitex/gen/_to_even.py +8 -8
  338. scitex/gen/_transpose.py +3 -3
  339. scitex/gen/misc.py +0 -3
  340. scitex/gists/_SigMacro_processFigure_S.py +2 -2
  341. scitex/gists/_SigMacro_toBlue.py +2 -2
  342. scitex/gists/__init__.py +4 -1
  343. scitex/git/_branch.py +19 -11
  344. scitex/git/_clone.py +23 -15
  345. scitex/git/_commit.py +10 -12
  346. scitex/git/_init.py +15 -38
  347. scitex/git/_remote.py +9 -3
  348. scitex/git/_result.py +3 -0
  349. scitex/git/_retry.py +2 -5
  350. scitex/git/_types.py +4 -0
  351. scitex/git/_validation.py +8 -8
  352. scitex/git/_workflow.py +4 -4
  353. scitex/io/__init__.py +12 -27
  354. scitex/io/_bundle.py +434 -0
  355. scitex/io/_flush.py +5 -2
  356. scitex/io/_glob.py +2 -2
  357. scitex/io/_json2md.py +3 -3
  358. scitex/io/_load.py +104 -8
  359. scitex/io/_load_cache.py +71 -71
  360. scitex/io/_load_configs.py +2 -3
  361. scitex/io/_load_modules/_H5Explorer.py +11 -14
  362. scitex/io/_load_modules/_ZarrExplorer.py +3 -3
  363. scitex/io/_load_modules/_bibtex.py +62 -63
  364. scitex/io/_load_modules/_canvas.py +6 -11
  365. scitex/io/_load_modules/_catboost.py +7 -2
  366. scitex/io/_load_modules/_hdf5.py +2 -0
  367. scitex/io/_load_modules/_image.py +7 -4
  368. scitex/io/_load_modules/_matlab.py +3 -1
  369. scitex/io/_load_modules/_optuna.py +0 -1
  370. scitex/io/_load_modules/_pdf.py +38 -29
  371. scitex/io/_load_modules/_sqlite3.py +1 -0
  372. scitex/io/_load_modules/_txt.py +6 -2
  373. scitex/io/_load_modules/_xml.py +9 -9
  374. scitex/io/_load_modules/_zarr.py +12 -10
  375. scitex/io/_metadata.py +34 -285
  376. scitex/io/_metadata_modules/__init__.py +46 -0
  377. scitex/io/_metadata_modules/_embed.py +70 -0
  378. scitex/io/_metadata_modules/_read.py +64 -0
  379. scitex/io/_metadata_modules/_utils.py +79 -0
  380. scitex/io/_metadata_modules/embed_metadata_jpeg.py +74 -0
  381. scitex/io/_metadata_modules/embed_metadata_pdf.py +53 -0
  382. scitex/io/_metadata_modules/embed_metadata_png.py +26 -0
  383. scitex/io/_metadata_modules/embed_metadata_svg.py +62 -0
  384. scitex/io/_metadata_modules/read_metadata_jpeg.py +57 -0
  385. scitex/io/_metadata_modules/read_metadata_pdf.py +51 -0
  386. scitex/io/_metadata_modules/read_metadata_png.py +39 -0
  387. scitex/io/_metadata_modules/read_metadata_svg.py +44 -0
  388. scitex/io/_qr_utils.py +21 -14
  389. scitex/io/_save.py +755 -80
  390. scitex/io/_save_modules/__init__.py +7 -2
  391. scitex/io/_save_modules/_bibtex.py +66 -61
  392. scitex/io/_save_modules/_canvas.py +8 -9
  393. scitex/io/_save_modules/_catboost.py +2 -2
  394. scitex/io/_save_modules/_csv.py +4 -4
  395. scitex/io/_save_modules/_excel.py +5 -9
  396. scitex/io/_save_modules/_hdf5.py +9 -21
  397. scitex/io/_save_modules/_html.py +5 -5
  398. scitex/io/_save_modules/_image.py +107 -14
  399. scitex/io/_save_modules/_joblib.py +2 -2
  400. scitex/io/_save_modules/_json.py +51 -6
  401. scitex/io/_save_modules/_listed_dfs_as_csv.py +2 -1
  402. scitex/io/_save_modules/_listed_scalars_as_csv.py +2 -1
  403. scitex/io/_save_modules/_matlab.py +2 -2
  404. scitex/io/_save_modules/_numpy.py +6 -8
  405. scitex/io/_save_modules/_pickle.py +4 -4
  406. scitex/io/_save_modules/_plotly.py +3 -3
  407. scitex/io/_save_modules/_tex.py +30 -29
  408. scitex/io/_save_modules/_text.py +2 -2
  409. scitex/io/_save_modules/_yaml.py +9 -9
  410. scitex/io/_save_modules/_zarr.py +15 -15
  411. scitex/io/utils/__init__.py +2 -1
  412. scitex/io/utils/h5_to_zarr.py +183 -163
  413. scitex/linalg/__init__.py +1 -1
  414. scitex/linalg/_geometric_median.py +4 -3
  415. scitex/logging/_Tee.py +5 -7
  416. scitex/logging/__init__.py +18 -19
  417. scitex/logging/_config.py +4 -1
  418. scitex/logging/_context.py +6 -5
  419. scitex/logging/_formatters.py +2 -3
  420. scitex/logging/_handlers.py +19 -20
  421. scitex/logging/_levels.py +9 -17
  422. scitex/logging/_logger.py +74 -15
  423. scitex/logging/_print_capture.py +17 -17
  424. scitex/msword/__init__.py +255 -0
  425. scitex/msword/profiles.py +357 -0
  426. scitex/msword/reader.py +753 -0
  427. scitex/msword/utils.py +289 -0
  428. scitex/msword/writer.py +362 -0
  429. scitex/nn/_BNet.py +1 -3
  430. scitex/nn/_Filters.py +6 -2
  431. scitex/nn/_ModulationIndex.py +3 -1
  432. scitex/nn/_PAC.py +3 -2
  433. scitex/nn/_PSD.py +0 -1
  434. scitex/nn/__init__.py +16 -3
  435. scitex/path/_clean.py +10 -8
  436. scitex/path/_find.py +1 -1
  437. scitex/path/_get_spath.py +1 -2
  438. scitex/path/_mk_spath.py +1 -1
  439. scitex/path/_symlink.py +5 -10
  440. scitex/pd/__init__.py +4 -1
  441. scitex/pd/_force_df.py +24 -24
  442. scitex/pd/_get_unique.py +1 -0
  443. scitex/pd/_merge_columns.py +1 -1
  444. scitex/pd/_round.py +11 -7
  445. scitex/pd/_to_xy.py +0 -1
  446. scitex/plt/__init__.py +190 -89
  447. scitex/plt/_subplots/_AxesWrapper.py +28 -12
  448. scitex/plt/_subplots/_AxisWrapper.py +114 -47
  449. scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/__init__.py +36 -0
  450. scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/_labels.py +264 -0
  451. scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/_metadata.py +213 -0
  452. scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/_visual.py +128 -0
  453. scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/__init__.py +59 -0
  454. scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_base.py +34 -0
  455. scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_scientific.py +593 -0
  456. scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_statistical.py +654 -0
  457. scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_stx_aliases.py +527 -0
  458. scitex/plt/_subplots/_AxisWrapperMixins/_RawMatplotlibMixin.py +321 -0
  459. scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/__init__.py +33 -0
  460. scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_base.py +152 -0
  461. scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_wrappers.py +600 -0
  462. scitex/plt/_subplots/_AxisWrapperMixins/_TrackingMixin.py +26 -14
  463. scitex/plt/_subplots/_AxisWrapperMixins/_UnitAwareMixin.py +80 -73
  464. scitex/plt/_subplots/_AxisWrapperMixins/__init__.py +79 -5
  465. scitex/plt/_subplots/_FigWrapper.py +97 -64
  466. scitex/plt/_subplots/_SubplotsWrapper.py +161 -84
  467. scitex/plt/_subplots/__init__.py +10 -0
  468. scitex/plt/_subplots/_export_as_csv.py +124 -52
  469. scitex/plt/_subplots/_export_as_csv_formatters/__init__.py +9 -0
  470. scitex/plt/_subplots/_export_as_csv_formatters/_format_annotate.py +14 -23
  471. scitex/plt/_subplots/_export_as_csv_formatters/_format_bar.py +88 -38
  472. scitex/plt/_subplots/_export_as_csv_formatters/_format_barh.py +25 -31
  473. scitex/plt/_subplots/_export_as_csv_formatters/_format_boxplot.py +53 -23
  474. scitex/plt/_subplots/_export_as_csv_formatters/_format_contour.py +38 -25
  475. scitex/plt/_subplots/_export_as_csv_formatters/_format_contourf.py +17 -9
  476. scitex/plt/_subplots/_export_as_csv_formatters/_format_errorbar.py +70 -124
  477. scitex/plt/_subplots/_export_as_csv_formatters/_format_eventplot.py +30 -17
  478. scitex/plt/_subplots/_export_as_csv_formatters/_format_fill.py +31 -17
  479. scitex/plt/_subplots/_export_as_csv_formatters/_format_fill_between.py +33 -21
  480. scitex/plt/_subplots/_export_as_csv_formatters/_format_hexbin.py +14 -4
  481. scitex/plt/_subplots/_export_as_csv_formatters/_format_hist.py +43 -29
  482. scitex/plt/_subplots/_export_as_csv_formatters/_format_hist2d.py +14 -4
  483. scitex/plt/_subplots/_export_as_csv_formatters/_format_imshow.py +27 -11
  484. scitex/plt/_subplots/_export_as_csv_formatters/_format_imshow2d.py +34 -16
  485. scitex/plt/_subplots/_export_as_csv_formatters/_format_matshow.py +16 -8
  486. scitex/plt/_subplots/_export_as_csv_formatters/_format_pie.py +15 -6
  487. scitex/plt/_subplots/_export_as_csv_formatters/_format_plot.py +85 -46
  488. scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_box.py +52 -27
  489. scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_imshow.py +14 -1
  490. scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_kde.py +27 -18
  491. scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_scatter.py +14 -5
  492. scitex/plt/_subplots/_export_as_csv_formatters/_format_quiver.py +16 -8
  493. scitex/plt/_subplots/_export_as_csv_formatters/_format_scatter.py +17 -6
  494. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_barplot.py +43 -26
  495. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_boxplot.py +68 -47
  496. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_heatmap.py +52 -64
  497. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_histplot.py +55 -50
  498. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_jointplot.py +23 -10
  499. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_kdeplot.py +63 -29
  500. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_lineplot.py +48 -40
  501. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_pairplot.py +20 -6
  502. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_scatterplot.py +44 -40
  503. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_stripplot.py +46 -39
  504. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_swarmplot.py +46 -39
  505. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_violinplot.py +75 -94
  506. scitex/plt/_subplots/_export_as_csv_formatters/_format_stem.py +12 -3
  507. scitex/plt/_subplots/_export_as_csv_formatters/_format_step.py +12 -3
  508. scitex/plt/_subplots/_export_as_csv_formatters/_format_streamplot.py +17 -9
  509. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_bar.py +84 -0
  510. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_barh.py +85 -0
  511. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_conf_mat.py +31 -18
  512. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_contour.py +54 -0
  513. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_ecdf.py +24 -11
  514. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_errorbar.py +120 -0
  515. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_fillv.py +35 -31
  516. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_heatmap.py +33 -23
  517. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_image.py +44 -28
  518. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_imshow.py +63 -0
  519. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_joyplot.py +31 -12
  520. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_line.py +34 -23
  521. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_mean_ci.py +32 -26
  522. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_mean_std.py +29 -23
  523. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_median_iqr.py +32 -26
  524. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_raster.py +21 -11
  525. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_rectangle.py +84 -56
  526. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_scatter.py +51 -0
  527. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_scatter_hist.py +46 -34
  528. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_shaded_line.py +46 -30
  529. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_violin.py +51 -51
  530. scitex/plt/_subplots/_export_as_csv_formatters/_format_text.py +32 -31
  531. scitex/plt/_subplots/_export_as_csv_formatters/_format_violin.py +34 -31
  532. scitex/plt/_subplots/_export_as_csv_formatters/_format_violinplot.py +44 -37
  533. scitex/plt/_subplots/_export_as_csv_formatters/verify_formatters.py +91 -74
  534. scitex/plt/_tpl.py +6 -5
  535. scitex/plt/ax/_plot/__init__.py +24 -0
  536. scitex/plt/ax/_plot/_add_fitted_line.py +12 -11
  537. scitex/plt/ax/_plot/_plot_circular_hist.py +3 -1
  538. scitex/plt/ax/_plot/_plot_statistical_shaded_line.py +25 -19
  539. scitex/plt/ax/_plot/_stx_conf_mat.py +6 -3
  540. scitex/plt/ax/_plot/_stx_ecdf.py +9 -5
  541. scitex/plt/ax/_plot/_stx_fillv.py +4 -2
  542. scitex/plt/ax/_plot/_stx_heatmap.py +7 -4
  543. scitex/plt/ax/_plot/_stx_image.py +7 -5
  544. scitex/plt/ax/_plot/_stx_joyplot.py +32 -10
  545. scitex/plt/ax/_plot/_stx_raster.py +26 -11
  546. scitex/plt/ax/_plot/_stx_rectangle.py +2 -2
  547. scitex/plt/ax/_plot/_stx_shaded_line.py +15 -11
  548. scitex/plt/ax/_plot/_stx_violin.py +3 -1
  549. scitex/plt/ax/_style/_add_marginal_ax.py +6 -4
  550. scitex/plt/ax/_style/_auto_scale_axis.py +14 -10
  551. scitex/plt/ax/_style/_extend.py +3 -1
  552. scitex/plt/ax/_style/_force_aspect.py +5 -3
  553. scitex/plt/ax/_style/_format_units.py +2 -2
  554. scitex/plt/ax/_style/_hide_spines.py +5 -1
  555. scitex/plt/ax/_style/_map_ticks.py +5 -3
  556. scitex/plt/ax/_style/_rotate_labels.py +5 -4
  557. scitex/plt/ax/_style/_rotate_labels_v01.py +73 -63
  558. scitex/plt/ax/_style/_set_log_scale.py +120 -85
  559. scitex/plt/ax/_style/_set_meta.py +99 -76
  560. scitex/plt/ax/_style/_set_supxyt.py +33 -16
  561. scitex/plt/ax/_style/_set_xyt.py +27 -18
  562. scitex/plt/ax/_style/_share_axes.py +15 -5
  563. scitex/plt/ax/_style/_show_spines.py +58 -57
  564. scitex/plt/ax/_style/_style_barplot.py +1 -1
  565. scitex/plt/ax/_style/_style_boxplot.py +25 -14
  566. scitex/plt/ax/_style/_style_errorbar.py +0 -0
  567. scitex/plt/ax/_style/_style_scatter.py +1 -1
  568. scitex/plt/ax/_style/_style_suptitles.py +3 -3
  569. scitex/plt/ax/_style/_style_violinplot.py +8 -2
  570. scitex/plt/color/__init__.py +34 -2
  571. scitex/plt/color/_add_hue_col.py +1 -0
  572. scitex/plt/color/_colors.py +0 -1
  573. scitex/plt/color/_get_colors_from_conf_matap.py +3 -1
  574. scitex/plt/color/_vizualize_colors.py +0 -1
  575. scitex/plt/docs/FIGURE_ARCHITECTURE.md +155 -97
  576. scitex/plt/gallery/README.md +75 -0
  577. scitex/plt/gallery/__init__.py +29 -0
  578. scitex/plt/gallery/_generate.py +560 -0
  579. scitex/plt/gallery/_plots.py +594 -0
  580. scitex/plt/gallery/_registry.py +153 -0
  581. scitex/plt/io/__init__.py +53 -0
  582. scitex/plt/io/_bundle.py +490 -0
  583. scitex/plt/io/_layered_bundle.py +1343 -0
  584. scitex/plt/styles/SCITEX_STYLE.yaml +26 -0
  585. scitex/plt/styles/__init__.py +23 -9
  586. scitex/plt/styles/_plot_defaults.py +62 -61
  587. scitex/plt/styles/_plot_postprocess.py +126 -77
  588. scitex/plt/styles/_style_loader.py +0 -0
  589. scitex/plt/styles/presets.py +121 -18
  590. scitex/plt/utils/__init__.py +42 -3
  591. scitex/plt/utils/_close.py +8 -3
  592. scitex/plt/utils/_collect_figure_metadata.py +3033 -271
  593. scitex/plt/utils/_colorbar.py +15 -17
  594. scitex/plt/utils/_configure_mpl.py +26 -30
  595. scitex/plt/utils/_crop.py +87 -36
  596. scitex/plt/utils/_csv_column_naming.py +177 -72
  597. scitex/plt/utils/_dimension_viewer.py +7 -19
  598. scitex/plt/utils/_figure_from_axes_mm.py +70 -16
  599. scitex/plt/utils/_figure_mm.py +119 -3
  600. scitex/plt/utils/_get_actual_font.py +5 -4
  601. scitex/plt/utils/_histogram_utils.py +52 -48
  602. scitex/plt/utils/_hitmap.py +1643 -0
  603. scitex/plt/utils/_is_valid_axis.py +19 -13
  604. scitex/plt/utils/_mk_colorbar.py +3 -3
  605. scitex/plt/utils/_scientific_captions.py +202 -139
  606. scitex/plt/utils/_scitex_config.py +98 -98
  607. scitex/plt/utils/_units.py +0 -0
  608. scitex/plt/utils/metadata/__init__.py +61 -0
  609. scitex/plt/utils/metadata/_artist_extraction.py +119 -0
  610. scitex/plt/utils/metadata/_axes_metadata.py +93 -0
  611. scitex/plt/utils/metadata/_collection_artists.py +292 -0
  612. scitex/plt/utils/metadata/_core.py +207 -0
  613. scitex/plt/utils/metadata/_csv_column_extraction.py +186 -0
  614. scitex/plt/utils/metadata/_csv_hash.py +115 -0
  615. scitex/plt/utils/metadata/_csv_verification.py +95 -0
  616. scitex/plt/utils/metadata/_data_linkage.py +263 -0
  617. scitex/plt/utils/metadata/_dimensions.py +242 -0
  618. scitex/plt/utils/metadata/_editable_export.py +405 -0
  619. scitex/plt/utils/metadata/_figure_metadata.py +58 -0
  620. scitex/plt/utils/metadata/_geometry_extraction.py +570 -0
  621. scitex/plt/utils/metadata/_image_text_artists.py +168 -0
  622. scitex/plt/utils/metadata/_label_parsing.py +82 -0
  623. scitex/plt/utils/metadata/_legend_extraction.py +120 -0
  624. scitex/plt/utils/metadata/_line_artists.py +367 -0
  625. scitex/plt/utils/metadata/_line_semantic_handling.py +173 -0
  626. scitex/plt/utils/metadata/_patch_artists.py +211 -0
  627. scitex/plt/utils/metadata/_plot_content.py +26 -0
  628. scitex/plt/utils/metadata/_plot_type_detection.py +184 -0
  629. scitex/plt/utils/metadata/_precision.py +134 -0
  630. scitex/plt/utils/metadata/_precision_config.py +68 -0
  631. scitex/plt/utils/metadata/_precision_sections.py +211 -0
  632. scitex/plt/utils/metadata/_recipe_extraction.py +267 -0
  633. scitex/plt/utils/metadata/_style_parsing.py +174 -0
  634. scitex/repro/_RandomStateManager.py +33 -38
  635. scitex/repro/__init__.py +16 -7
  636. scitex/repro/_gen_ID.py +7 -9
  637. scitex/repro/_gen_timestamp.py +7 -6
  638. scitex/repro/_hash_array.py +8 -12
  639. scitex/reproduce/__init__.py +1 -1
  640. scitex/resource/_get_processor_usages.py +3 -1
  641. scitex/resource/_log_processor_usages.py +3 -1
  642. scitex/rng/__init__.py +1 -1
  643. scitex/schema/README.md +178 -0
  644. scitex/schema/__init__.py +237 -0
  645. scitex/schema/_canvas.py +444 -0
  646. scitex/schema/_plot.py +1015 -0
  647. scitex/schema/_stats.py +762 -0
  648. scitex/schema/_validation.py +590 -0
  649. scitex/scholar/.legacy/Scholar.py +5 -12
  650. scitex/scholar/.legacy/_Scholar.py +66 -99
  651. scitex/scholar/.legacy/_ScholarAPI.py +75 -66
  652. scitex/scholar/.legacy/_tmp/search_engine/_BaseSearchEngine.py +3 -3
  653. scitex/scholar/.legacy/_tmp/search_engine/_UnifiedSearcher.py +4 -9
  654. scitex/scholar/.legacy/_tmp/search_engine/__init__.py +14 -21
  655. scitex/scholar/.legacy/_tmp/search_engine/local/_LocalSearchEngine.py +40 -37
  656. scitex/scholar/.legacy/_tmp/search_engine/local/_VectorSearchEngine.py +31 -28
  657. scitex/scholar/.legacy/_tmp/search_engine/web/_ArxivSearchEngine.py +74 -65
  658. scitex/scholar/.legacy/_tmp/search_engine/web/_CrossRefSearchEngine.py +122 -116
  659. scitex/scholar/.legacy/_tmp/search_engine/web/_GoogleScholarSearchEngine.py +65 -59
  660. scitex/scholar/.legacy/_tmp/search_engine/web/_PubMedSearchEngine.py +121 -107
  661. scitex/scholar/.legacy/_tmp/search_engine/web/_SemanticScholarSearchEngine.py +5 -12
  662. scitex/scholar/.legacy/database/_DatabaseEntry.py +49 -45
  663. scitex/scholar/.legacy/database/_DatabaseIndex.py +131 -94
  664. scitex/scholar/.legacy/database/_LibraryManager.py +65 -63
  665. scitex/scholar/.legacy/database/_PaperDatabase.py +138 -124
  666. scitex/scholar/.legacy/database/_ScholarDatabaseIntegration.py +14 -36
  667. scitex/scholar/.legacy/database/_StorageIntegratedDB.py +192 -156
  668. scitex/scholar/.legacy/database/_ZoteroCompatibleDB.py +300 -237
  669. scitex/scholar/.legacy/database/__init__.py +2 -1
  670. scitex/scholar/.legacy/database/manage.py +92 -84
  671. scitex/scholar/.legacy/lookup/_LookupIndex.py +157 -101
  672. scitex/scholar/.legacy/lookup/__init__.py +2 -1
  673. scitex/scholar/.legacy/metadata/doi/batch/_MetadataHandlerForBatchDOIResolution.py +4 -9
  674. scitex/scholar/.legacy/metadata/doi/batch/_ProgressManagerForBatchDOIResolution.py +10 -23
  675. scitex/scholar/.legacy/metadata/doi/batch/_SourceStatsManagerForBatchDOIResolution.py +4 -9
  676. scitex/scholar/.legacy/metadata/doi/batch/__init__.py +3 -1
  677. scitex/scholar/.legacy/metadata/doi/resolvers/_BatchDOIResolver.py +10 -25
  678. scitex/scholar/.legacy/metadata/doi/resolvers/_BibTeXDOIResolver.py +19 -49
  679. scitex/scholar/.legacy/metadata/doi/resolvers/_DOIResolver.py +1 -0
  680. scitex/scholar/.legacy/metadata/doi/resolvers/_SingleDOIResolver.py +8 -20
  681. scitex/scholar/.legacy/metadata/doi/sources/.combined-SemanticScholarSource/_SemanticScholarSource.py +37 -35
  682. scitex/scholar/.legacy/metadata/doi/sources/.combined-SemanticScholarSource/_SemanticScholarSourceEnhanced.py +49 -37
  683. scitex/scholar/.legacy/metadata/doi/sources/_ArXivSource.py +11 -30
  684. scitex/scholar/.legacy/metadata/doi/sources/_BaseDOISource.py +19 -47
  685. scitex/scholar/.legacy/metadata/doi/sources/_CrossRefLocalSource.py +1 -0
  686. scitex/scholar/.legacy/metadata/doi/sources/_CrossRefSource.py +12 -33
  687. scitex/scholar/.legacy/metadata/doi/sources/_OpenAlexSource.py +8 -20
  688. scitex/scholar/.legacy/metadata/doi/sources/_PubMedSource.py +10 -27
  689. scitex/scholar/.legacy/metadata/doi/sources/_SemanticScholarSource.py +11 -29
  690. scitex/scholar/.legacy/metadata/doi/sources/_SourceManager.py +8 -21
  691. scitex/scholar/.legacy/metadata/doi/sources/_SourceResolutionStrategy.py +24 -55
  692. scitex/scholar/.legacy/metadata/doi/sources/_SourceRotationManager.py +8 -21
  693. scitex/scholar/.legacy/metadata/doi/sources/_URLDOISource.py +9 -16
  694. scitex/scholar/.legacy/metadata/doi/sources/_UnifiedSource.py +8 -22
  695. scitex/scholar/.legacy/metadata/doi/sources/__init__.py +1 -0
  696. scitex/scholar/.legacy/metadata/doi/utils/_PubMedConverter.py +4 -8
  697. scitex/scholar/.legacy/metadata/doi/utils/_RateLimitHandler.py +17 -43
  698. scitex/scholar/.legacy/metadata/doi/utils/_TextNormalizer.py +8 -18
  699. scitex/scholar/.legacy/metadata/doi/utils/_URLDOIExtractor.py +4 -8
  700. scitex/scholar/.legacy/metadata/doi/utils/__init__.py +1 -0
  701. scitex/scholar/.legacy/metadata/doi/utils/_to_complete_metadata_structure.py +1 -0
  702. scitex/scholar/.legacy/metadata/enrichment/_LibraryEnricher.py +2 -3
  703. scitex/scholar/.legacy/metadata/enrichment/enrichers/_ImpactFactorEnricher.py +6 -12
  704. scitex/scholar/.legacy/metadata/enrichment/enrichers/_SmartEnricher.py +5 -10
  705. scitex/scholar/.legacy/metadata/enrichment/sources/_UnifiedMetadataSource.py +4 -5
  706. scitex/scholar/.legacy/metadata/query_to_full_meta_json.py +8 -12
  707. scitex/scholar/.legacy/metadata/urls/_URLMetadataHandler.py +3 -3
  708. scitex/scholar/.legacy/metadata/urls/_ZoteroTranslatorRunner.py +15 -21
  709. scitex/scholar/.legacy/metadata/urls/__init__.py +3 -3
  710. scitex/scholar/.legacy/metadata/urls/_finder.py +4 -6
  711. scitex/scholar/.legacy/metadata/urls/_handler.py +7 -15
  712. scitex/scholar/.legacy/metadata/urls/_resolver.py +6 -12
  713. scitex/scholar/.legacy/search/_Embedder.py +74 -69
  714. scitex/scholar/.legacy/search/_SemanticSearch.py +91 -90
  715. scitex/scholar/.legacy/search/_SemanticSearchEngine.py +104 -109
  716. scitex/scholar/.legacy/search/_UnifiedSearcher.py +530 -471
  717. scitex/scholar/.legacy/search/_VectorDatabase.py +111 -92
  718. scitex/scholar/.legacy/search/__init__.py +1 -0
  719. scitex/scholar/.legacy/storage/_EnhancedStorageManager.py +182 -154
  720. scitex/scholar/.legacy/storage/__init__.py +2 -1
  721. scitex/scholar/__init__.py +0 -2
  722. scitex/scholar/__main__.py +1 -3
  723. scitex/scholar/auth/ScholarAuthManager.py +13 -36
  724. scitex/scholar/auth/core/AuthenticationGateway.py +15 -29
  725. scitex/scholar/auth/core/BrowserAuthenticator.py +22 -57
  726. scitex/scholar/auth/core/StrategyResolver.py +10 -27
  727. scitex/scholar/auth/core/__init__.py +5 -1
  728. scitex/scholar/auth/gateway/_OpenURLLinkFinder.py +11 -21
  729. scitex/scholar/auth/gateway/_OpenURLResolver.py +10 -18
  730. scitex/scholar/auth/gateway/_resolve_functions.py +3 -3
  731. scitex/scholar/auth/providers/BaseAuthenticator.py +1 -0
  732. scitex/scholar/auth/providers/EZProxyAuthenticator.py +7 -14
  733. scitex/scholar/auth/providers/OpenAthensAuthenticator.py +29 -57
  734. scitex/scholar/auth/providers/ShibbolethAuthenticator.py +87 -73
  735. scitex/scholar/auth/session/AuthCacheManager.py +12 -22
  736. scitex/scholar/auth/session/SessionManager.py +4 -6
  737. scitex/scholar/auth/sso/BaseSSOAutomator.py +13 -19
  738. scitex/scholar/auth/sso/OpenAthensSSOAutomator.py +16 -45
  739. scitex/scholar/auth/sso/SSOAutomator.py +8 -15
  740. scitex/scholar/auth/sso/UniversityOfMelbourneSSOAutomator.py +13 -23
  741. scitex/scholar/browser/ScholarBrowserManager.py +31 -56
  742. scitex/scholar/browser/__init__.py +1 -0
  743. scitex/scholar/browser/utils/click_and_wait.py +3 -4
  744. scitex/scholar/browser/utils/close_unwanted_pages.py +4 -7
  745. scitex/scholar/browser/utils/wait_redirects.py +15 -40
  746. scitex/scholar/citation_graph/__init__.py +0 -0
  747. scitex/scholar/citation_graph/builder.py +3 -7
  748. scitex/scholar/citation_graph/database.py +4 -11
  749. scitex/scholar/citation_graph/example.py +5 -10
  750. scitex/scholar/citation_graph/models.py +0 -0
  751. scitex/scholar/cli/_url_utils.py +1 -1
  752. scitex/scholar/cli/chrome.py +5 -3
  753. scitex/scholar/cli/download_pdf.py +13 -14
  754. scitex/scholar/cli/handlers/bibtex_handler.py +4 -12
  755. scitex/scholar/cli/handlers/doi_handler.py +1 -3
  756. scitex/scholar/cli/handlers/project_handler.py +6 -20
  757. scitex/scholar/cli/open_browser.py +41 -39
  758. scitex/scholar/cli/open_browser_auto.py +31 -39
  759. scitex/scholar/cli/open_browser_monitored.py +27 -24
  760. scitex/scholar/config/ScholarConfig.py +5 -8
  761. scitex/scholar/config/__init__.py +1 -0
  762. scitex/scholar/config/core/_CascadeConfig.py +3 -3
  763. scitex/scholar/config/core/_PathManager.py +16 -28
  764. scitex/scholar/core/Paper.py +79 -78
  765. scitex/scholar/core/Papers.py +16 -27
  766. scitex/scholar/core/Scholar.py +98 -229
  767. scitex/scholar/core/journal_normalizer.py +52 -49
  768. scitex/scholar/core/oa_cache.py +27 -23
  769. scitex/scholar/core/open_access.py +17 -8
  770. scitex/scholar/docs/template.py +4 -3
  771. scitex/scholar/docs/to_claude/examples/example-python-project-scitex/scripts/mnist/clf_svm.py +0 -0
  772. scitex/scholar/docs/to_claude/examples/example-python-project-scitex/scripts/mnist/download.py +0 -0
  773. scitex/scholar/docs/to_claude/examples/example-python-project-scitex/scripts/mnist/plot_conf_mat.py +0 -0
  774. scitex/scholar/docs/to_claude/examples/example-python-project-scitex/scripts/mnist/plot_digits.py +0 -0
  775. scitex/scholar/docs/to_claude/examples/example-python-project-scitex/scripts/mnist/plot_umap_space.py +0 -0
  776. scitex/scholar/examples/00_config.py +10 -9
  777. scitex/scholar/examples/01_auth.py +3 -0
  778. scitex/scholar/examples/02_browser.py +14 -10
  779. scitex/scholar/examples/03_01-engine.py +3 -0
  780. scitex/scholar/examples/03_02-engine-for-bibtex.py +4 -3
  781. scitex/scholar/examples/04_01-url.py +9 -9
  782. scitex/scholar/examples/04_02-url-for-bibtex.py +7 -3
  783. scitex/scholar/examples/04_02-url-for-dois.py +87 -97
  784. scitex/scholar/examples/05_download_pdf.py +10 -4
  785. scitex/scholar/examples/06_find_and_download.py +6 -6
  786. scitex/scholar/examples/06_parse_bibtex.py +17 -17
  787. scitex/scholar/examples/07_storage_integration.py +6 -9
  788. scitex/scholar/examples/99_fullpipeline-for-bibtex.py +14 -15
  789. scitex/scholar/examples/99_fullpipeline-for-one-entry.py +31 -23
  790. scitex/scholar/examples/99_maintenance.py +3 -0
  791. scitex/scholar/examples/dev.py +2 -3
  792. scitex/scholar/examples/zotero_integration.py +11 -18
  793. scitex/scholar/impact_factor/ImpactFactorEngine.py +7 -9
  794. scitex/scholar/impact_factor/estimation/__init__.py +4 -4
  795. scitex/scholar/impact_factor/estimation/core/__init__.py +3 -7
  796. scitex/scholar/impact_factor/estimation/core/cache_manager.py +223 -211
  797. scitex/scholar/impact_factor/estimation/core/calculator.py +165 -131
  798. scitex/scholar/impact_factor/estimation/core/journal_matcher.py +217 -172
  799. scitex/scholar/impact_factor/jcr/ImpactFactorJCREngine.py +6 -14
  800. scitex/scholar/impact_factor/jcr/build_database.py +4 -3
  801. scitex/scholar/integration/base.py +9 -17
  802. scitex/scholar/integration/mendeley/exporter.py +2 -4
  803. scitex/scholar/integration/mendeley/importer.py +3 -3
  804. scitex/scholar/integration/mendeley/linker.py +3 -3
  805. scitex/scholar/integration/mendeley/mapper.py +9 -6
  806. scitex/scholar/integration/zotero/__main__.py +26 -43
  807. scitex/scholar/integration/zotero/exporter.py +15 -11
  808. scitex/scholar/integration/zotero/importer.py +12 -10
  809. scitex/scholar/integration/zotero/linker.py +8 -12
  810. scitex/scholar/integration/zotero/mapper.py +17 -12
  811. scitex/scholar/metadata_engines/.combined-SemanticScholarSource/_SemanticScholarSource.py +37 -35
  812. scitex/scholar/metadata_engines/.combined-SemanticScholarSource/_SemanticScholarSourceEnhanced.py +47 -35
  813. scitex/scholar/metadata_engines/ScholarEngine.py +21 -43
  814. scitex/scholar/metadata_engines/__init__.py +1 -0
  815. scitex/scholar/metadata_engines/individual/ArXivEngine.py +15 -37
  816. scitex/scholar/metadata_engines/individual/CrossRefEngine.py +15 -42
  817. scitex/scholar/metadata_engines/individual/CrossRefLocalEngine.py +24 -45
  818. scitex/scholar/metadata_engines/individual/OpenAlexEngine.py +11 -21
  819. scitex/scholar/metadata_engines/individual/PubMedEngine.py +10 -27
  820. scitex/scholar/metadata_engines/individual/SemanticScholarEngine.py +28 -35
  821. scitex/scholar/metadata_engines/individual/URLDOIEngine.py +11 -22
  822. scitex/scholar/metadata_engines/individual/_BaseDOIEngine.py +20 -49
  823. scitex/scholar/metadata_engines/utils/_PubMedConverter.py +4 -8
  824. scitex/scholar/metadata_engines/utils/_URLDOIExtractor.py +5 -10
  825. scitex/scholar/metadata_engines/utils/__init__.py +2 -0
  826. scitex/scholar/metadata_engines/utils/_metadata2bibtex.py +3 -0
  827. scitex/scholar/metadata_engines/utils/_standardize_metadata.py +2 -3
  828. scitex/scholar/pdf_download/ScholarPDFDownloader.py +25 -37
  829. scitex/scholar/pdf_download/strategies/chrome_pdf_viewer.py +11 -19
  830. scitex/scholar/pdf_download/strategies/direct_download.py +5 -9
  831. scitex/scholar/pdf_download/strategies/manual_download_fallback.py +3 -3
  832. scitex/scholar/pdf_download/strategies/manual_download_utils.py +6 -13
  833. scitex/scholar/pdf_download/strategies/open_access_download.py +49 -31
  834. scitex/scholar/pdf_download/strategies/response_body.py +8 -19
  835. scitex/scholar/pipelines/ScholarPipelineBibTeX.py +9 -18
  836. scitex/scholar/pipelines/ScholarPipelineMetadataParallel.py +25 -26
  837. scitex/scholar/pipelines/ScholarPipelineMetadataSingle.py +62 -23
  838. scitex/scholar/pipelines/ScholarPipelineParallel.py +13 -30
  839. scitex/scholar/pipelines/ScholarPipelineSearchParallel.py +299 -220
  840. scitex/scholar/pipelines/ScholarPipelineSearchSingle.py +202 -165
  841. scitex/scholar/pipelines/ScholarPipelineSingle.py +25 -51
  842. scitex/scholar/pipelines/SearchQueryParser.py +55 -55
  843. scitex/scholar/search_engines/ScholarSearchEngine.py +31 -27
  844. scitex/scholar/search_engines/_BaseSearchEngine.py +20 -23
  845. scitex/scholar/search_engines/individual/ArXivSearchEngine.py +53 -35
  846. scitex/scholar/search_engines/individual/CrossRefSearchEngine.py +47 -40
  847. scitex/scholar/search_engines/individual/OpenAlexSearchEngine.py +55 -50
  848. scitex/scholar/search_engines/individual/PubMedSearchEngine.py +8 -10
  849. scitex/scholar/search_engines/individual/SemanticScholarSearchEngine.py +55 -49
  850. scitex/scholar/storage/BibTeXHandler.py +150 -95
  851. scitex/scholar/storage/PaperIO.py +3 -6
  852. scitex/scholar/storage/ScholarLibrary.py +70 -49
  853. scitex/scholar/storage/_DeduplicationManager.py +52 -25
  854. scitex/scholar/storage/_LibraryCacheManager.py +19 -46
  855. scitex/scholar/storage/_LibraryManager.py +65 -175
  856. scitex/scholar/url_finder/ScholarURLFinder.py +9 -25
  857. scitex/scholar/url_finder/strategies/find_pdf_urls_by_direct_links.py +1 -1
  858. scitex/scholar/url_finder/strategies/find_pdf_urls_by_href.py +6 -10
  859. scitex/scholar/url_finder/strategies/find_pdf_urls_by_navigation.py +4 -6
  860. scitex/scholar/url_finder/strategies/find_pdf_urls_by_publisher_patterns.py +8 -15
  861. scitex/scholar/url_finder/strategies/find_pdf_urls_by_zotero_translators.py +3 -3
  862. scitex/scholar/url_finder/strategies/find_supplementary_urls_by_href.py +3 -3
  863. scitex/scholar/url_finder/translators/core/patterns.py +6 -4
  864. scitex/scholar/url_finder/translators/core/registry.py +6 -9
  865. scitex/scholar/url_finder/translators/individual/BOFiP_Impots.py +60 -52
  866. scitex/scholar/url_finder/translators/individual/Baidu_Scholar.py +54 -62
  867. scitex/scholar/url_finder/translators/individual/Bangkok_Post.py +38 -44
  868. scitex/scholar/url_finder/translators/individual/Baruch_Foundation.py +43 -47
  869. scitex/scholar/url_finder/translators/individual/Beobachter.py +46 -50
  870. scitex/scholar/url_finder/translators/individual/Bezneng_Gajit.py +37 -41
  871. scitex/scholar/url_finder/translators/individual/BibLaTeX.py +59 -52
  872. scitex/scholar/url_finder/translators/individual/BibTeX.py +83 -79
  873. scitex/scholar/url_finder/translators/individual/Biblio_com.py +48 -51
  874. scitex/scholar/url_finder/translators/individual/Bibliontology_RDF.py +58 -56
  875. scitex/scholar/url_finder/translators/individual/Camara_Brasileira_do_Livro_ISBN.py +102 -99
  876. scitex/scholar/url_finder/translators/individual/CanLII.py +49 -43
  877. scitex/scholar/url_finder/translators/individual/Canada_com.py +36 -40
  878. scitex/scholar/url_finder/translators/individual/Canadian_Letters_and_Images.py +43 -43
  879. scitex/scholar/url_finder/translators/individual/Canadiana_ca.py +77 -66
  880. scitex/scholar/url_finder/translators/individual/Cascadilla_Proceedings_Project.py +68 -62
  881. scitex/scholar/url_finder/translators/individual/Central_and_Eastern_European_Online_Library_Journals.py +60 -60
  882. scitex/scholar/url_finder/translators/individual/Champlain_Society_Collection.py +63 -61
  883. scitex/scholar/url_finder/translators/individual/Chicago_Journal_of_Theoretical_Computer_Science.py +74 -58
  884. scitex/scholar/url_finder/translators/individual/Christian_Science_Monitor.py +32 -38
  885. scitex/scholar/url_finder/translators/individual/Columbia_University_Press.py +51 -47
  886. scitex/scholar/url_finder/translators/individual/Common_Place.py +66 -57
  887. scitex/scholar/url_finder/translators/individual/Cornell_LII.py +66 -62
  888. scitex/scholar/url_finder/translators/individual/Cornell_University_Press.py +38 -45
  889. scitex/scholar/url_finder/translators/individual/CourtListener.py +52 -56
  890. scitex/scholar/url_finder/translators/individual/DAI_Zenon.py +53 -54
  891. scitex/scholar/url_finder/translators/individual/access_medicine.py +27 -33
  892. scitex/scholar/url_finder/translators/individual/acm.py +1 -1
  893. scitex/scholar/url_finder/translators/individual/acm_digital_library.py +93 -63
  894. scitex/scholar/url_finder/translators/individual/airiti.py +3 -1
  895. scitex/scholar/url_finder/translators/individual/aosic.py +3 -1
  896. scitex/scholar/url_finder/translators/individual/archive_ouverte_aosic.py +3 -1
  897. scitex/scholar/url_finder/translators/individual/archive_ouverte_en_sciences_de_l_information_et_de_la_communication___aosic_.py +6 -2
  898. scitex/scholar/url_finder/translators/individual/artforum.py +35 -27
  899. scitex/scholar/url_finder/translators/individual/arxiv.py +1 -1
  900. scitex/scholar/url_finder/translators/individual/arxiv_org.py +8 -4
  901. scitex/scholar/url_finder/translators/individual/atlanta_journal_constitution.py +22 -18
  902. scitex/scholar/url_finder/translators/individual/atypon_journals.py +19 -11
  903. scitex/scholar/url_finder/translators/individual/austlii_and_nzlii.py +48 -44
  904. scitex/scholar/url_finder/translators/individual/australian_dictionary_of_biography.py +21 -17
  905. scitex/scholar/url_finder/translators/individual/bailii.py +22 -19
  906. scitex/scholar/url_finder/translators/individual/bbc.py +46 -42
  907. scitex/scholar/url_finder/translators/individual/bbc_genome.py +37 -25
  908. scitex/scholar/url_finder/translators/individual/biblioteca_nacional_de_maestros.py +24 -20
  909. scitex/scholar/url_finder/translators/individual/bibliotheque_archives_nationale_quebec_pistard.py +42 -43
  910. scitex/scholar/url_finder/translators/individual/bibliotheque_archives_nationales_quebec.py +87 -81
  911. scitex/scholar/url_finder/translators/individual/bibliotheque_nationale_france.py +39 -37
  912. scitex/scholar/url_finder/translators/individual/bibsys.py +32 -28
  913. scitex/scholar/url_finder/translators/individual/bioconductor.py +58 -52
  914. scitex/scholar/url_finder/translators/individual/biomed_central.py +23 -15
  915. scitex/scholar/url_finder/translators/individual/biorxiv.py +26 -13
  916. scitex/scholar/url_finder/translators/individual/blogger.py +39 -43
  917. scitex/scholar/url_finder/translators/individual/bloomberg.py +48 -52
  918. scitex/scholar/url_finder/translators/individual/bloomsbury_food_library.py +37 -37
  919. scitex/scholar/url_finder/translators/individual/bluesky.py +30 -28
  920. scitex/scholar/url_finder/translators/individual/bnf_isbn.py +1 -1
  921. scitex/scholar/url_finder/translators/individual/bocc.py +66 -60
  922. scitex/scholar/url_finder/translators/individual/boe.py +52 -52
  923. scitex/scholar/url_finder/translators/individual/brill.py +3 -1
  924. scitex/scholar/url_finder/translators/individual/business_standard.py +36 -38
  925. scitex/scholar/url_finder/translators/individual/cabi_cab_abstracts.py +39 -41
  926. scitex/scholar/url_finder/translators/individual/cambridge.py +3 -1
  927. scitex/scholar/url_finder/translators/individual/cambridge_core.py +30 -24
  928. scitex/scholar/url_finder/translators/individual/caod.py +50 -46
  929. scitex/scholar/url_finder/translators/individual/cbc.py +91 -67
  930. scitex/scholar/url_finder/translators/individual/ccfr_bnf.py +49 -53
  931. scitex/scholar/url_finder/translators/individual/cia_world_factbook.py +43 -33
  932. scitex/scholar/url_finder/translators/individual/crossref_rest.py +208 -174
  933. scitex/scholar/url_finder/translators/individual/current_affairs.py +29 -35
  934. scitex/scholar/url_finder/translators/individual/dabi.py +70 -66
  935. scitex/scholar/url_finder/translators/individual/dagens_nyheter.py +3 -1
  936. scitex/scholar/url_finder/translators/individual/dagstuhl.py +10 -15
  937. scitex/scholar/url_finder/translators/individual/dar_almandumah.py +13 -9
  938. scitex/scholar/url_finder/translators/individual/dart_europe.py +19 -22
  939. scitex/scholar/url_finder/translators/individual/data_gov.py +2 -2
  940. scitex/scholar/url_finder/translators/individual/databrary.py +27 -28
  941. scitex/scholar/url_finder/translators/individual/datacite_json.py +152 -137
  942. scitex/scholar/url_finder/translators/individual/dataverse.py +68 -64
  943. scitex/scholar/url_finder/translators/individual/daum_news.py +38 -38
  944. scitex/scholar/url_finder/translators/individual/dblp.py +4 -8
  945. scitex/scholar/url_finder/translators/individual/dblp_computer_science_bibliography.py +8 -3
  946. scitex/scholar/url_finder/translators/individual/dbpia.py +5 -3
  947. scitex/scholar/url_finder/translators/individual/defense_technical_information_center.py +30 -28
  948. scitex/scholar/url_finder/translators/individual/delpher.py +102 -79
  949. scitex/scholar/url_finder/translators/individual/demographic_research.py +35 -31
  950. scitex/scholar/url_finder/translators/individual/denik_cz.py +58 -54
  951. scitex/scholar/url_finder/translators/individual/depatisnet.py +7 -10
  952. scitex/scholar/url_finder/translators/individual/der_freitag.py +81 -66
  953. scitex/scholar/url_finder/translators/individual/der_spiegel.py +56 -54
  954. scitex/scholar/url_finder/translators/individual/digibib_net.py +3 -1
  955. scitex/scholar/url_finder/translators/individual/digizeitschriften.py +3 -1
  956. scitex/scholar/url_finder/translators/individual/dpla.py +13 -14
  957. scitex/scholar/url_finder/translators/individual/dspace.py +2 -2
  958. scitex/scholar/url_finder/translators/individual/ebrary.py +3 -1
  959. scitex/scholar/url_finder/translators/individual/ebscohost.py +3 -1
  960. scitex/scholar/url_finder/translators/individual/electronic_colloquium_on_computational_complexity.py +3 -1
  961. scitex/scholar/url_finder/translators/individual/elife.py +3 -1
  962. scitex/scholar/url_finder/translators/individual/elsevier_health_journals.py +3 -1
  963. scitex/scholar/url_finder/translators/individual/emerald.py +3 -1
  964. scitex/scholar/url_finder/translators/individual/emerald_insight.py +3 -1
  965. scitex/scholar/url_finder/translators/individual/epicurious.py +3 -1
  966. scitex/scholar/url_finder/translators/individual/eurogamerusgamer.py +3 -1
  967. scitex/scholar/url_finder/translators/individual/fachportal_padagogik.py +3 -1
  968. scitex/scholar/url_finder/translators/individual/frontiers.py +1 -1
  969. scitex/scholar/url_finder/translators/individual/gale_databases.py +3 -1
  970. scitex/scholar/url_finder/translators/individual/gms_german_medical_science.py +6 -2
  971. scitex/scholar/url_finder/translators/individual/ieee_computer_society.py +6 -2
  972. scitex/scholar/url_finder/translators/individual/ieee_xplore.py +41 -35
  973. scitex/scholar/url_finder/translators/individual/inter_research_science_center.py +6 -2
  974. scitex/scholar/url_finder/translators/individual/jisc_historical_texts.py +3 -1
  975. scitex/scholar/url_finder/translators/individual/jstor.py +14 -12
  976. scitex/scholar/url_finder/translators/individual/korean_national_library.py +3 -1
  977. scitex/scholar/url_finder/translators/individual/la_times.py +3 -1
  978. scitex/scholar/url_finder/translators/individual/landesbibliographie_baden_wurttemberg.py +3 -1
  979. scitex/scholar/url_finder/translators/individual/legislative_insight.py +3 -1
  980. scitex/scholar/url_finder/translators/individual/libraries_tasmania.py +3 -1
  981. scitex/scholar/url_finder/translators/individual/library_catalog__koha_.py +3 -1
  982. scitex/scholar/url_finder/translators/individual/lingbuzz.py +2 -2
  983. scitex/scholar/url_finder/translators/individual/max_planck_institute_for_the_history_of_science_virtual_laboratory_library.py +3 -1
  984. scitex/scholar/url_finder/translators/individual/mdpi.py +12 -6
  985. scitex/scholar/url_finder/translators/individual/microbiology_society_journals.py +3 -1
  986. scitex/scholar/url_finder/translators/individual/midas_journals.py +3 -1
  987. scitex/scholar/url_finder/translators/individual/nagoya_university_opac.py +3 -1
  988. scitex/scholar/url_finder/translators/individual/nature_publishing_group.py +32 -19
  989. scitex/scholar/url_finder/translators/individual/ntsb_accident_reports.py +3 -1
  990. scitex/scholar/url_finder/translators/individual/openedition_journals.py +8 -4
  991. scitex/scholar/url_finder/translators/individual/orcid.py +16 -15
  992. scitex/scholar/url_finder/translators/individual/oxford.py +25 -19
  993. scitex/scholar/url_finder/translators/individual/oxford_dictionaries_premium.py +3 -1
  994. scitex/scholar/url_finder/translators/individual/ozon_ru.py +3 -1
  995. scitex/scholar/url_finder/translators/individual/plos.py +9 -12
  996. scitex/scholar/url_finder/translators/individual/polygon.py +3 -1
  997. scitex/scholar/url_finder/translators/individual/primo.py +3 -1
  998. scitex/scholar/url_finder/translators/individual/project_muse.py +3 -1
  999. scitex/scholar/url_finder/translators/individual/pubfactory_journals.py +3 -1
  1000. scitex/scholar/url_finder/translators/individual/pubmed.py +71 -65
  1001. scitex/scholar/url_finder/translators/individual/pubmed_central.py +8 -6
  1002. scitex/scholar/url_finder/translators/individual/rechtspraak_nl.py +3 -1
  1003. scitex/scholar/url_finder/translators/individual/sage_journals.py +25 -17
  1004. scitex/scholar/url_finder/translators/individual/sciencedirect.py +36 -17
  1005. scitex/scholar/url_finder/translators/individual/semantics_visual_library.py +3 -1
  1006. scitex/scholar/url_finder/translators/individual/silverchair.py +70 -52
  1007. scitex/scholar/url_finder/translators/individual/sora.py +3 -1
  1008. scitex/scholar/url_finder/translators/individual/springer.py +15 -11
  1009. scitex/scholar/url_finder/translators/individual/ssrn.py +3 -3
  1010. scitex/scholar/url_finder/translators/individual/stanford_encyclopedia_of_philosophy.py +3 -1
  1011. scitex/scholar/url_finder/translators/individual/superlib.py +3 -1
  1012. scitex/scholar/url_finder/translators/individual/treesearch.py +3 -1
  1013. scitex/scholar/url_finder/translators/individual/university_of_chicago_press_books.py +3 -1
  1014. scitex/scholar/url_finder/translators/individual/vlex.py +3 -1
  1015. scitex/scholar/url_finder/translators/individual/web_of_science.py +3 -1
  1016. scitex/scholar/url_finder/translators/individual/web_of_science_nextgen.py +3 -1
  1017. scitex/scholar/url_finder/translators/individual/wiley.py +31 -25
  1018. scitex/scholar/url_finder/translators/individual/wilson_center_digital_archive.py +3 -1
  1019. scitex/scholar/utils/bibtex/_parse_bibtex.py +3 -3
  1020. scitex/scholar/utils/cleanup/_cleanup_scholar_processes.py +5 -9
  1021. scitex/scholar/utils/text/_TextNormalizer.py +249 -176
  1022. scitex/scholar/utils/validation/DOIValidator.py +31 -28
  1023. scitex/scholar/utils/validation/__init__.py +0 -0
  1024. scitex/scholar/utils/validation/validate_library_dois.py +61 -57
  1025. scitex/scholar/zotero/__init__.py +1 -1
  1026. scitex/security/cli.py +7 -20
  1027. scitex/security/github.py +45 -32
  1028. scitex/session/__init__.py +8 -9
  1029. scitex/session/_decorator.py +49 -42
  1030. scitex/session/_lifecycle.py +39 -39
  1031. scitex/session/_manager.py +24 -20
  1032. scitex/sh/__init__.py +4 -3
  1033. scitex/sh/_execute.py +10 -7
  1034. scitex/sh/_security.py +3 -3
  1035. scitex/sh/_types.py +2 -3
  1036. scitex/stats/__init__.py +174 -6
  1037. scitex/stats/_schema.py +42 -569
  1038. scitex/stats/auto/__init__.py +188 -0
  1039. scitex/stats/auto/_context.py +331 -0
  1040. scitex/stats/auto/_formatting.py +679 -0
  1041. scitex/stats/auto/_rules.py +901 -0
  1042. scitex/stats/auto/_selector.py +554 -0
  1043. scitex/stats/auto/_styles.py +721 -0
  1044. scitex/stats/correct/__init__.py +4 -4
  1045. scitex/stats/correct/_correct_bonferroni.py +43 -34
  1046. scitex/stats/correct/_correct_fdr.py +14 -40
  1047. scitex/stats/correct/_correct_fdr_.py +39 -46
  1048. scitex/stats/correct/_correct_holm.py +14 -32
  1049. scitex/stats/correct/_correct_sidak.py +36 -21
  1050. scitex/stats/descriptive/_circular.py +20 -21
  1051. scitex/stats/descriptive/_describe.py +19 -5
  1052. scitex/stats/descriptive/_nan.py +5 -7
  1053. scitex/stats/descriptive/_real.py +4 -3
  1054. scitex/stats/effect_sizes/__init__.py +10 -11
  1055. scitex/stats/effect_sizes/_cliffs_delta.py +35 -32
  1056. scitex/stats/effect_sizes/_cohens_d.py +30 -31
  1057. scitex/stats/effect_sizes/_epsilon_squared.py +19 -22
  1058. scitex/stats/effect_sizes/_eta_squared.py +23 -27
  1059. scitex/stats/effect_sizes/_prob_superiority.py +18 -21
  1060. scitex/stats/io/__init__.py +29 -0
  1061. scitex/stats/io/_bundle.py +156 -0
  1062. scitex/stats/posthoc/__init__.py +3 -3
  1063. scitex/stats/posthoc/_dunnett.py +75 -55
  1064. scitex/stats/posthoc/_games_howell.py +61 -43
  1065. scitex/stats/posthoc/_tukey_hsd.py +42 -34
  1066. scitex/stats/power/__init__.py +2 -2
  1067. scitex/stats/power/_power.py +56 -56
  1068. scitex/stats/tests/__init__.py +1 -1
  1069. scitex/stats/tests/correlation/__init__.py +1 -1
  1070. scitex/stats/tests/correlation/_test_pearson.py +28 -38
  1071. scitex/stats/utils/__init__.py +14 -17
  1072. scitex/stats/utils/_effect_size.py +85 -78
  1073. scitex/stats/utils/_formatters.py +49 -43
  1074. scitex/stats/utils/_normalizers.py +7 -14
  1075. scitex/stats/utils/_power.py +56 -56
  1076. scitex/str/__init__.py +1 -0
  1077. scitex/str/_clean_path.py +3 -3
  1078. scitex/str/_factor_out_digits.py +86 -58
  1079. scitex/str/_format_plot_text.py +180 -111
  1080. scitex/str/_latex.py +19 -19
  1081. scitex/str/_latex_fallback.py +9 -10
  1082. scitex/str/_parse.py +3 -6
  1083. scitex/str/_print_debug.py +13 -13
  1084. scitex/str/_printc.py +2 -0
  1085. scitex/str/_search.py +3 -3
  1086. scitex/template/.legacy/_clone_project.py +9 -13
  1087. scitex/template/__init__.py +10 -2
  1088. scitex/template/_clone_project.py +7 -2
  1089. scitex/template/_copy.py +1 -0
  1090. scitex/template/_customize.py +3 -6
  1091. scitex/template/_git_strategy.py +2 -3
  1092. scitex/template/_rename.py +1 -0
  1093. scitex/template/clone_pip_project.py +6 -7
  1094. scitex/template/clone_research.py +7 -10
  1095. scitex/template/clone_singularity.py +6 -7
  1096. scitex/template/clone_writer_directory.py +6 -7
  1097. scitex/tex/__init__.py +4 -0
  1098. scitex/tex/_export.py +890 -0
  1099. scitex/tex/_preview.py +26 -11
  1100. scitex/tex/_to_vec.py +10 -7
  1101. scitex/torch/__init__.py +11 -1
  1102. scitex/types/_ArrayLike.py +2 -0
  1103. scitex/types/_is_listed_X.py +3 -3
  1104. scitex/units.py +110 -77
  1105. scitex/utils/_compress_hdf5.py +3 -3
  1106. scitex/utils/_email.py +8 -4
  1107. scitex/utils/_notify.py +14 -8
  1108. scitex/utils/_search.py +6 -6
  1109. scitex/utils/_verify_scitex_format.py +17 -42
  1110. scitex/utils/_verify_scitex_format_v01.py +12 -34
  1111. scitex/utils/template.py +4 -3
  1112. scitex/web/__init__.py +7 -1
  1113. scitex/web/_scraping.py +54 -38
  1114. scitex/web/_search_pubmed.py +30 -14
  1115. scitex/writer/.legacy/Writer_v01-refactored.py +4 -4
  1116. scitex/writer/.legacy/_compile.py +18 -28
  1117. scitex/writer/Writer.py +8 -21
  1118. scitex/writer/__init__.py +11 -11
  1119. scitex/writer/_clone_writer_project.py +2 -6
  1120. scitex/writer/_compile/__init__.py +1 -0
  1121. scitex/writer/_compile/_parser.py +1 -0
  1122. scitex/writer/_compile/_runner.py +35 -38
  1123. scitex/writer/_compile/_validator.py +1 -0
  1124. scitex/writer/_compile/manuscript.py +1 -0
  1125. scitex/writer/_compile/revision.py +1 -0
  1126. scitex/writer/_compile/supplementary.py +1 -0
  1127. scitex/writer/_compile_async.py +5 -12
  1128. scitex/writer/_project/__init__.py +1 -0
  1129. scitex/writer/_project/_create.py +10 -25
  1130. scitex/writer/_project/_trees.py +4 -9
  1131. scitex/writer/_project/_validate.py +2 -3
  1132. scitex/writer/_validate_tree_structures.py +7 -18
  1133. scitex/writer/dataclasses/__init__.py +8 -10
  1134. scitex/writer/dataclasses/config/_CONSTANTS.py +2 -3
  1135. scitex/writer/dataclasses/config/_WriterConfig.py +4 -9
  1136. scitex/writer/dataclasses/contents/_ManuscriptContents.py +14 -25
  1137. scitex/writer/dataclasses/contents/_RevisionContents.py +21 -16
  1138. scitex/writer/dataclasses/contents/_SupplementaryContents.py +21 -24
  1139. scitex/writer/dataclasses/core/_Document.py +2 -3
  1140. scitex/writer/dataclasses/core/_DocumentSection.py +8 -23
  1141. scitex/writer/dataclasses/results/_CompilationResult.py +2 -3
  1142. scitex/writer/dataclasses/results/_LaTeXIssue.py +3 -6
  1143. scitex/writer/dataclasses/results/_SaveSectionsResponse.py +20 -9
  1144. scitex/writer/dataclasses/results/_SectionReadResponse.py +24 -10
  1145. scitex/writer/dataclasses/tree/_ConfigTree.py +7 -4
  1146. scitex/writer/dataclasses/tree/_ManuscriptTree.py +10 -13
  1147. scitex/writer/dataclasses/tree/_RevisionTree.py +16 -17
  1148. scitex/writer/dataclasses/tree/_ScriptsTree.py +10 -5
  1149. scitex/writer/dataclasses/tree/_SharedTree.py +10 -13
  1150. scitex/writer/dataclasses/tree/_SupplementaryTree.py +15 -14
  1151. scitex/writer/utils/.legacy_git_retry.py +3 -8
  1152. scitex/writer/utils/_parse_latex_logs.py +2 -3
  1153. scitex/writer/utils/_parse_script_args.py +20 -23
  1154. scitex/writer/utils/_watch.py +5 -5
  1155. {scitex-2.5.0.dist-info → scitex-2.7.3.dist-info}/METADATA +14 -10
  1156. {scitex-2.5.0.dist-info → scitex-2.7.3.dist-info}/RECORD +1149 -985
  1157. scitex/db/_sqlite3/_SQLite3Mixins/_ColumnMixin_v01-indentation-issues.py +0 -583
  1158. scitex/io/memo.md +0 -2827
  1159. scitex/plt/_subplots/TODO.md +0 -53
  1160. scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin.py +0 -537
  1161. scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin.py +0 -1499
  1162. scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin.py +0 -431
  1163. scitex/plt/_subplots/_export_as_csv_formatters.py +0 -112
  1164. scitex/vis/__init__.py +0 -177
  1165. scitex/vis/editor/_defaults.py +0 -244
  1166. scitex/vis/editor/_edit.py +0 -378
  1167. scitex/vis/editor/flask_editor/__init__.py +0 -21
  1168. scitex/vis/editor/flask_editor/bbox.py +0 -216
  1169. scitex/vis/editor/flask_editor/core.py +0 -152
  1170. scitex/vis/editor/flask_editor/plotter.py +0 -130
  1171. scitex/vis/editor/flask_editor/renderer.py +0 -184
  1172. scitex/vis/editor/flask_editor/templates/html.py +0 -295
  1173. scitex/vis/editor/flask_editor/templates/scripts.py +0 -614
  1174. scitex/vis/editor/flask_editor/templates/styles.py +0 -549
  1175. /scitex/{vis → fig}/README.md +0 -0
  1176. /scitex/{vis → fig}/docs/CANVAS_ARCHITECTURE.md +0 -0
  1177. {scitex-2.5.0.dist-info → scitex-2.7.3.dist-info}/WHEEL +0 -0
  1178. {scitex-2.5.0.dist-info → scitex-2.7.3.dist-info}/entry_points.txt +0 -0
  1179. {scitex-2.5.0.dist-info → scitex-2.7.3.dist-info}/licenses/LICENSE +0 -0
scitex/io/_save.py CHANGED
@@ -161,6 +161,7 @@ def save(
161
161
  auto_crop: bool = True,
162
162
  crop_margin_mm: float = 1.0,
163
163
  metadata_extra: dict = None,
164
+ json_schema: str = "editable",
164
165
  **kwargs,
165
166
  ) -> None:
166
167
  """
@@ -203,6 +204,12 @@ def save(
203
204
  }
204
205
  }
205
206
  Default is None.
207
+ json_schema : str, optional
208
+ Schema type for JSON metadata output. Options:
209
+ - "editable": Schema v0.3.0 with element geometry for interactive editing (default)
210
+ - "recipe": Minimal schema with method calls + data refs
211
+ - "verbose": Full schema with all artist details
212
+ Default is "editable".
206
213
  **kwargs
207
214
  Additional keyword arguments to pass to the underlying save function of the specific format.
208
215
 
@@ -212,9 +219,16 @@ def save(
212
219
 
213
220
  Notes
214
221
  -----
215
- Supported formats include CSV, NPY, PKL, JOBLIB, PNG, HTML, TIFF, MP4, YAML, JSON, HDF5, PTH, MAT, and CBM.
222
+ Supported formats include CSV, NPY, PKL, JOBLIB, PNG, HTML, TIFF, MP4, YAML, JSON, HDF5, PTH, MAT, CBM,
223
+ and SciTeX bundles (.figz, .pltz, .statsz).
216
224
  The function dynamically selects the appropriate saving mechanism based on the file extension.
217
225
 
226
+ Bundle Formats:
227
+ - .figz: Publication figure bundle (panels dict). Default: ZIP archive.
228
+ - .pltz: Plot bundle (matplotlib figure). Default: directory bundle.
229
+ - .statsz: Statistics bundle (comparisons list). Default: directory bundle.
230
+ - Use .d suffix (e.g., "Figure1.figz.d") to force directory format for .figz.
231
+
218
232
  Examples
219
233
  --------
220
234
  >>> import scitex
@@ -296,9 +310,7 @@ def save(
296
310
  elif var in frame.f_globals:
297
311
  format_dict[var] = frame.f_globals[var]
298
312
  else:
299
- raise ValueError(
300
- f"Invalid variable name in f-string: {var}"
301
- )
313
+ raise ValueError(f"Invalid variable name in f-string: {var}")
302
314
 
303
315
  # Use str.format() which is safe
304
316
  specified_path = path_content.format(**format_dict)
@@ -370,7 +382,7 @@ def save(
370
382
  or ("<stdin>" in script_path)
371
383
  or env_type in ["ipython", "interactive"]
372
384
  ):
373
- script_path = f'/tmp/{_os.getenv("USER")}'
385
+ script_path = f"/tmp/{_os.getenv('USER')}"
374
386
  sdir = script_path
375
387
  else:
376
388
  # Unknown environment, use current directory
@@ -427,6 +439,7 @@ def save(
427
439
  auto_crop=auto_crop,
428
440
  crop_margin_mm=crop_margin_mm,
429
441
  metadata_extra=metadata_extra,
442
+ json_schema=json_schema,
430
443
  **kwargs,
431
444
  )
432
445
 
@@ -436,6 +449,9 @@ def save(
436
449
  return Path(spath)
437
450
  # return True
438
451
 
452
+ except AssertionError:
453
+ # Re-raise assertion errors - these are validation failures that should stop execution
454
+ raise
439
455
  except Exception as e:
440
456
  logger.error(
441
457
  f"Error occurred while saving: {str(e)}\n"
@@ -471,9 +487,7 @@ def _symlink_to(spath_final, symlink_to, verbose):
471
487
 
472
488
  # Ensure the symlink directory exists (only if there is a directory component)
473
489
  symlink_dir = _os.path.dirname(symlink_to)
474
- if (
475
- symlink_dir
476
- ): # Only create directory if there's a directory component
490
+ if symlink_dir: # Only create directory if there's a directory component
477
491
  _os.makedirs(symlink_dir, exist_ok=True)
478
492
 
479
493
  # Remove existing symlink or file
@@ -484,13 +498,487 @@ def _symlink_to(spath_final, symlink_to, verbose):
484
498
 
485
499
  if verbose:
486
500
  symlink_to_full = (
487
- os.path.realpath(symlink_to)
488
- + "/"
489
- + os.path.basename(spath_final)
501
+ os.path.realpath(symlink_to) + "/" + os.path.basename(spath_final)
490
502
  )
491
503
  logger.success(f"Symlinked: {spath_final} -> {symlink_to_full}")
492
504
 
493
505
 
506
+ def _save_pltz_bundle(obj, spath, as_zip=False, data=None, layered=True, **kwargs):
507
+ """Save a matplotlib figure as a .pltz bundle.
508
+
509
+ Bundle structure v2.0 (layered - default):
510
+ plot.pltz.d/
511
+ spec.json # Semantic: WHAT to plot (canonical)
512
+ style.json # Appearance: HOW it looks (canonical)
513
+ data.csv # Raw data (immutable)
514
+ exports/ # PNG, SVG, hitmap
515
+ cache/ # geometry_px.json, render_manifest.json
516
+
517
+ Bundle structure v1.0 (legacy):
518
+ plot.json - specification (axes, styles, theme, etc.)
519
+ plot.csv - raw data (immutable)
520
+ plot.png - raster export (required)
521
+ plot.svg - vector export (optional)
522
+ plot.pdf - publication export (optional)
523
+
524
+ Parameters
525
+ ----------
526
+ obj : matplotlib.figure.Figure
527
+ The figure to save.
528
+ spath : str or Path
529
+ Output path (e.g., "plot.pltz.d" or "plot.pltz").
530
+ as_zip : bool
531
+ If True, save as ZIP archive.
532
+ data : pandas.DataFrame, optional
533
+ Data to embed in the bundle as plot.csv.
534
+ layered : bool
535
+ If True (default), use new layered format (spec/style/geometry).
536
+ If False, use legacy single JSON format.
537
+ **kwargs
538
+ Additional arguments passed to savefig.
539
+ """
540
+ from pathlib import Path
541
+ import tempfile
542
+ import json
543
+ import numpy as np
544
+ from ._bundle import save_bundle, BundleType
545
+
546
+ p = Path(spath)
547
+
548
+ # Extract basename from path (e.g., "myplot.pltz" -> "myplot", "myplot.pltz.d" -> "myplot")
549
+ basename = p.stem # e.g., "myplot.pltz" or "myplot"
550
+ if basename.endswith('.pltz'):
551
+ basename = basename[:-5] # Remove .pltz suffix
552
+ elif basename.endswith('.d'):
553
+ # Handle myplot.pltz.d -> myplot.pltz -> myplot
554
+ basename = Path(basename).stem
555
+ if basename.endswith('.pltz'):
556
+ basename = basename[:-5]
557
+
558
+ # Extract figure from various matplotlib object types
559
+ import matplotlib.figure
560
+ fig = obj
561
+ if hasattr(obj, 'figure'):
562
+ fig = obj.figure
563
+ elif hasattr(obj, 'fig'):
564
+ fig = obj.fig
565
+
566
+ if not isinstance(fig, matplotlib.figure.Figure):
567
+ raise TypeError(f"Expected matplotlib Figure, got {type(obj).__name__}")
568
+
569
+ dpi = kwargs.pop('dpi', 300)
570
+
571
+ # === Always use layered format ===
572
+ from scitex.plt.io import save_layered_pltz_bundle
573
+ import shutil
574
+ import tempfile
575
+
576
+ # Determine bundle directory path
577
+ if as_zip:
578
+ # For ZIP: save to temp dir, then compress
579
+ temp_dir = Path(tempfile.mkdtemp())
580
+ bundle_dir = temp_dir / f"{basename}.pltz.d"
581
+ zip_path = p if not str(p).endswith('.d') else Path(str(p)[:-2])
582
+ else:
583
+ # For directory: save directly
584
+ bundle_dir = p if str(p).endswith('.d') else Path(str(p) + '.d')
585
+
586
+ # Get CSV data from figure if not provided
587
+ csv_df = data
588
+ if csv_df is None:
589
+ csv_source = _get_figure_with_data(obj)
590
+ if csv_source is not None and hasattr(csv_source, 'export_as_csv'):
591
+ try:
592
+ csv_df = csv_source.export_as_csv()
593
+ except Exception:
594
+ pass
595
+
596
+ save_layered_pltz_bundle(
597
+ fig=fig,
598
+ bundle_dir=bundle_dir,
599
+ basename=basename,
600
+ dpi=dpi,
601
+ csv_df=csv_df,
602
+ )
603
+
604
+ # Compress to ZIP if requested
605
+ if as_zip:
606
+ from ._bundle import pack_bundle
607
+ pack_bundle(bundle_dir, zip_path)
608
+ shutil.rmtree(temp_dir) # Clean up temp directory
609
+
610
+ return # Done with layered format
611
+
612
+ # === Legacy format below (DEPRECATED - kept for reference) ===
613
+
614
+ # Calculate size info
615
+ fig_width_inch, fig_height_inch = fig.get_size_inches()
616
+ fig_dpi = fig.get_dpi()
617
+
618
+ # Build spec according to contract (using basename for file references)
619
+ spec = {
620
+ 'schema': {'name': 'scitex.plt.plot', 'version': '1.0.0'},
621
+ 'backend': 'mpl',
622
+ 'data': {
623
+ 'source': f'{basename}.csv',
624
+ 'path': f'{basename}.csv',
625
+ 'hash': None, # Will be computed after data extraction
626
+ 'columns': [], # Will be populated after data extraction
627
+ },
628
+ 'size': {
629
+ 'width_inch': round(fig_width_inch, 2),
630
+ 'height_inch': round(fig_height_inch, 2),
631
+ 'width_mm': round(fig_width_inch * 25.4, 2),
632
+ 'height_mm': round(fig_height_inch * 25.4, 2),
633
+ 'width_px': int(fig_width_inch * dpi),
634
+ 'height_px': int(fig_height_inch * dpi),
635
+ 'dpi': dpi,
636
+ 'crop_margin_mm': 1.0,
637
+ },
638
+ 'axes': [],
639
+ 'theme': {
640
+ 'mode': 'light',
641
+ 'colors': {
642
+ 'background': 'transparent',
643
+ 'axes_bg': 'white',
644
+ 'text': 'black',
645
+ 'spine': 'black',
646
+ 'tick': 'black',
647
+ }
648
+ },
649
+ }
650
+
651
+ # Extract data from plot lines if no data provided
652
+ extracted_data = {}
653
+
654
+ # Extract axes metadata
655
+ for i, ax in enumerate(fig.axes):
656
+ # Get axes bounding box in figure coordinates (0-1)
657
+ bbox = ax.get_position()
658
+
659
+ ax_info = {
660
+ 'xlabel': ax.get_xlabel() or None,
661
+ 'ylabel': ax.get_ylabel() or None,
662
+ 'title': ax.get_title() or None,
663
+ 'xlim': [round(v, 2) for v in ax.get_xlim()],
664
+ 'ylim': [round(v, 2) for v in ax.get_ylim()],
665
+ 'plot_type': 'line', # Default, could be detected
666
+ # Bounding box in normalized figure coordinates (0-1)
667
+ 'bbox': {
668
+ 'x0': round(bbox.x0, 4),
669
+ 'y0': round(bbox.y0, 4),
670
+ 'x1': round(bbox.x1, 4),
671
+ 'y1': round(bbox.y1, 4),
672
+ 'width': round(bbox.width, 4),
673
+ 'height': round(bbox.height, 4),
674
+ },
675
+ # Bounding box in mm
676
+ 'bbox_mm': {
677
+ 'x0': round(bbox.x0 * fig_width_inch * 25.4, 2),
678
+ 'y0': round(bbox.y0 * fig_height_inch * 25.4, 2),
679
+ 'x1': round(bbox.x1 * fig_width_inch * 25.4, 2),
680
+ 'y1': round(bbox.y1 * fig_height_inch * 25.4, 2),
681
+ 'width': round(bbox.width * fig_width_inch * 25.4, 2),
682
+ 'height': round(bbox.height * fig_height_inch * 25.4, 2),
683
+ },
684
+ # Bounding box in pixels
685
+ 'bbox_px': {
686
+ 'x0': int(bbox.x0 * fig_width_inch * dpi),
687
+ 'y0': int(bbox.y0 * fig_height_inch * dpi),
688
+ 'x1': int(bbox.x1 * fig_width_inch * dpi),
689
+ 'y1': int(bbox.y1 * fig_height_inch * dpi),
690
+ 'width': int(bbox.width * fig_width_inch * dpi),
691
+ 'height': int(bbox.height * fig_height_inch * dpi),
692
+ },
693
+ }
694
+
695
+ # SciTeX-specific axis dimensions
696
+ if hasattr(ax, '_scitex_axes_width_mm'):
697
+ ax_info['axes_width_mm'] = ax._scitex_axes_width_mm
698
+ else:
699
+ ax_info['axes_width_mm'] = round(bbox.width * fig_width_inch * 25.4, 1)
700
+
701
+ if hasattr(ax, '_scitex_axes_height_mm'):
702
+ ax_info['axes_height_mm'] = ax._scitex_axes_height_mm
703
+ else:
704
+ ax_info['axes_height_mm'] = round(bbox.height * fig_height_inch * 25.4, 1)
705
+
706
+ # Extract line data for CSV and build lines array
707
+ lines_info = []
708
+ for j, line in enumerate(ax.get_lines()):
709
+ label = line.get_label()
710
+ if label is None or label.startswith('_'):
711
+ label = f'series_{j}'
712
+ xdata, ydata = line.get_data()
713
+ if len(xdata) > 0:
714
+ col_x = f'{label}_x' if i == 0 else f'ax{i}_{label}_x'
715
+ col_y = f'{label}_y' if i == 0 else f'ax{i}_{label}_y'
716
+ extracted_data[col_x] = np.array(xdata)
717
+ extracted_data[col_y] = np.array(ydata)
718
+
719
+ # Get line color (convert RGBA to hex)
720
+ color = line.get_color()
721
+ if isinstance(color, (list, tuple)):
722
+ import matplotlib.colors as mcolors
723
+ color = mcolors.to_hex(color)
724
+
725
+ lines_info.append({
726
+ 'label': label,
727
+ 'x_col': col_x,
728
+ 'y_col': col_y,
729
+ 'color': color,
730
+ 'linewidth': line.get_linewidth(),
731
+ })
732
+
733
+ if lines_info:
734
+ ax_info['lines'] = lines_info
735
+
736
+ spec['axes'].append(ax_info)
737
+
738
+ # Handle theme from figure
739
+ if hasattr(fig, '_scitex_theme'):
740
+ theme_mode = fig._scitex_theme
741
+ spec['theme']['mode'] = theme_mode
742
+ # Update colors based on theme mode
743
+ if theme_mode == 'dark':
744
+ spec['theme']['colors'] = {
745
+ 'background': 'transparent',
746
+ 'axes_bg': 'transparent',
747
+ 'text': '#e8e8e8',
748
+ 'spine': '#e8e8e8',
749
+ 'tick': '#e8e8e8',
750
+ }
751
+ # Re-apply theme colors to ensure legends and other elements get the correct colors
752
+ from scitex.plt.utils._figure_mm import _apply_theme_colors
753
+ for ax in fig.axes:
754
+ _apply_theme_colors(ax, theme='dark')
755
+
756
+ # Build bundle data (include basename for file naming)
757
+ bundle_data = {'spec': spec, 'basename': basename}
758
+
759
+ # Use provided data or extracted data for CSV
760
+ # Priority: 1) explicit data param, 2) export_as_csv method, 3) line extraction fallback
761
+ csv_df = None
762
+ if data is not None:
763
+ csv_df = data
764
+ bundle_data['data'] = data
765
+ else:
766
+ # Try to use export_as_csv from SciTeX wrapped objects (handles all plot types)
767
+ csv_source = _get_figure_with_data(obj)
768
+ if csv_source is not None and hasattr(csv_source, 'export_as_csv'):
769
+ try:
770
+ csv_df = csv_source.export_as_csv()
771
+ if csv_df is not None and not csv_df.empty:
772
+ bundle_data['data'] = csv_df
773
+ logger.debug(f"CSV data extracted via export_as_csv: {len(csv_df)} rows, {len(csv_df.columns)} cols")
774
+ except Exception as e:
775
+ logger.debug(f"export_as_csv failed: {e}")
776
+ csv_df = None
777
+
778
+ # Fallback to line extraction if export_as_csv didn't work
779
+ if csv_df is None and extracted_data:
780
+ try:
781
+ import pandas as pd
782
+ # Pad arrays to same length
783
+ max_len = max(len(v) for v in extracted_data.values())
784
+ padded = {}
785
+ for k, v in extracted_data.items():
786
+ if len(v) < max_len:
787
+ padded[k] = np.pad(v, (0, max_len - len(v)), constant_values=np.nan)
788
+ else:
789
+ padded[k] = v
790
+ csv_df = pd.DataFrame(padded)
791
+ bundle_data['data'] = csv_df
792
+ logger.debug(f"CSV data extracted via line fallback: {len(csv_df)} rows")
793
+ except ImportError:
794
+ pass
795
+
796
+ # Compute hash and columns for data section
797
+ if csv_df is not None:
798
+ import hashlib
799
+ # Get CSV string for hash computation
800
+ csv_str = csv_df.to_csv(index=False)
801
+ csv_hash = hashlib.sha256(csv_str.encode()).hexdigest()
802
+ spec['data']['hash'] = f'sha256:{csv_hash[:16]}'
803
+ spec['data']['columns'] = list(csv_df.columns)
804
+
805
+ # Save figure to multiple formats
806
+ import warnings
807
+ from PIL import Image as PILImage
808
+ from scitex.plt.utils._hitmap import (
809
+ apply_hitmap_colors, restore_original_colors, extract_path_data,
810
+ extract_selectable_regions, HITMAP_BACKGROUND_COLOR, HITMAP_AXES_COLOR
811
+ )
812
+
813
+ crop_box = None
814
+ color_map = {}
815
+
816
+ with tempfile.TemporaryDirectory() as tmp_dir:
817
+ tmp_path = Path(tmp_dir)
818
+
819
+ # Suppress tight_layout warnings for SciTeX figures with custom axes
820
+ with warnings.catch_warnings():
821
+ warnings.filterwarnings('ignore', message='.*tight_layout.*')
822
+
823
+ # Always use transparent background for SciTeX figures (both light and dark themes)
824
+ use_transparent = True
825
+
826
+ # Save PNG (raster) - required
827
+ png_path = tmp_path / "plot.png"
828
+ fig.savefig(png_path, dpi=dpi, bbox_inches='tight', format='png', transparent=use_transparent)
829
+
830
+ # Save SVG (vector) - optional
831
+ svg_path = tmp_path / "plot.svg"
832
+ fig.savefig(svg_path, bbox_inches='tight', format='svg')
833
+
834
+ # Save PDF (vector) - optional
835
+ pdf_path = tmp_path / "plot.pdf"
836
+ fig.savefig(pdf_path, bbox_inches='tight', format='pdf')
837
+
838
+ # Now generate hitmap by applying ID colors to data elements ONLY
839
+ # Keep axes/spines/labels with original colors to preserve bbox_inches='tight' bounds
840
+ # Also detects logical groups (histogram, bar_series, etc.)
841
+ original_props, color_map, groups = apply_hitmap_colors(fig)
842
+
843
+ # Store original background colors and set hitmap colors
844
+ original_fig_facecolor = fig.patch.get_facecolor()
845
+ original_ax_facecolors = []
846
+ original_ax_props = []
847
+ for ax in fig.axes:
848
+ original_ax_facecolors.append(ax.get_facecolor())
849
+ # Store axis element colors for restoration
850
+ ax_props = {
851
+ 'ax': ax,
852
+ 'spine_colors': {k: v.get_edgecolor() for k, v in ax.spines.items()},
853
+ 'tick_colors': ax.tick_params, # Will restore later
854
+ 'xlabel_color': ax.xaxis.label.get_color(),
855
+ 'ylabel_color': ax.yaxis.label.get_color(),
856
+ 'title_color': ax.title.get_color(),
857
+ }
858
+ original_ax_props.append(ax_props)
859
+ # Set hitmap colors for non-data elements
860
+ ax.set_facecolor(HITMAP_BACKGROUND_COLOR)
861
+ for spine in ax.spines.values():
862
+ spine.set_color(HITMAP_AXES_COLOR)
863
+ ax.tick_params(colors=HITMAP_AXES_COLOR, labelcolor=HITMAP_AXES_COLOR)
864
+ ax.xaxis.label.set_color(HITMAP_AXES_COLOR)
865
+ ax.yaxis.label.set_color(HITMAP_AXES_COLOR)
866
+ ax.title.set_color(HITMAP_AXES_COLOR)
867
+
868
+ fig.patch.set_facecolor(HITMAP_BACKGROUND_COLOR)
869
+
870
+ # Save hitmap PNG with same bbox_inches='tight'
871
+ hitmap_path = tmp_path / "plot_hitmap.png"
872
+ fig.savefig(hitmap_path, dpi=dpi, bbox_inches='tight', format='png', facecolor=HITMAP_BACKGROUND_COLOR)
873
+
874
+ # Optimize hitmap PNG size using zlib compression
875
+ try:
876
+ hitmap_img = PILImage.open(hitmap_path).convert('RGB')
877
+ hitmap_img.save(hitmap_path, format='PNG', optimize=True, compress_level=9)
878
+ except Exception:
879
+ pass # Keep original if optimization fails
880
+
881
+ # Save hitmap SVG with same bbox_inches='tight'
882
+ hitmap_svg_path = tmp_path / "plot_hitmap.svg"
883
+ fig.savefig(hitmap_svg_path, bbox_inches='tight', format='svg')
884
+
885
+ # Restore original colors (data elements)
886
+ restore_original_colors(original_props)
887
+
888
+ # Restore original figure and axes colors
889
+ fig.patch.set_facecolor(original_fig_facecolor)
890
+ for i, ax in enumerate(fig.axes):
891
+ ax.set_facecolor(original_ax_facecolors[i])
892
+ if i < len(original_ax_props):
893
+ props = original_ax_props[i]
894
+ for spine_name, color in props['spine_colors'].items():
895
+ ax.spines[spine_name].set_edgecolor(color)
896
+ ax.xaxis.label.set_color(props['xlabel_color'])
897
+ ax.yaxis.label.set_color(props['ylabel_color'])
898
+ ax.title.set_color(props['title_color'])
899
+
900
+ # Now apply auto-crop to BOTH PNG and hitmap with same parameters
901
+ try:
902
+ from scitex.plt.utils._crop import crop
903
+
904
+ # Crop PNG and get crop coordinates
905
+ _, crop_offset = crop(
906
+ str(png_path),
907
+ output_path=str(png_path),
908
+ overwrite=True,
909
+ margin=12, # ~1mm at 300 DPI
910
+ verbose=False,
911
+ return_offset=True,
912
+ )
913
+ crop_box = (crop_offset['left'], crop_offset['upper'],
914
+ crop_offset['right'], crop_offset['lower'])
915
+
916
+ # Apply SAME crop to hitmap PNG
917
+ crop(
918
+ str(hitmap_path),
919
+ output_path=str(hitmap_path),
920
+ overwrite=True,
921
+ crop_box=crop_box,
922
+ verbose=False,
923
+ )
924
+ except Exception as e:
925
+ crop_box = None
926
+ logger.debug(f"Crop failed: {e}")
927
+
928
+ # Validate sizes match
929
+ with PILImage.open(png_path) as png_img, PILImage.open(hitmap_path) as hm_img:
930
+ if png_img.size != hm_img.size:
931
+ logger.warning(f"Size mismatch: PNG={png_img.size}, Hitmap={hm_img.size}")
932
+
933
+ with open(png_path, 'rb') as f:
934
+ bundle_data['png'] = f.read()
935
+
936
+ with open(hitmap_path, 'rb') as f:
937
+ bundle_data['hitmap_png'] = f.read()
938
+
939
+ with open(svg_path, 'rb') as f:
940
+ bundle_data['svg'] = f.read()
941
+
942
+ with open(hitmap_svg_path, 'rb') as f:
943
+ bundle_data['hitmap_svg'] = f.read()
944
+
945
+ with open(pdf_path, 'rb') as f:
946
+ bundle_data['pdf'] = f.read()
947
+
948
+ # Add hit_regions to spec
949
+ try:
950
+ path_data = extract_path_data(fig)
951
+
952
+ spec['hit_regions'] = {
953
+ 'strategy': 'hybrid',
954
+ 'hit_map': f'{basename}_hitmap.png',
955
+ 'hit_map_svg': f'{basename}_hitmap.svg',
956
+ 'color_map': {str(k): v for k, v in color_map.items()},
957
+ 'groups': groups, # Logical groups (histogram, bar_series, etc.)
958
+ 'path_data': path_data,
959
+ }
960
+
961
+ if crop_box is not None:
962
+ spec['hit_regions']['crop_box'] = {
963
+ 'left': int(crop_box[0]),
964
+ 'upper': int(crop_box[1]),
965
+ 'right': int(crop_box[2]),
966
+ 'lower': int(crop_box[3]),
967
+ }
968
+
969
+ # Extract selectable regions (bounding boxes for axis/annotation elements)
970
+ # This complements hitmap color-based selection with bbox-based selection
971
+ selectable_regions = extract_selectable_regions(fig)
972
+ if selectable_regions and selectable_regions.get('axes'):
973
+ spec['selectable_regions'] = selectable_regions
974
+
975
+ except Exception as e:
976
+ logger.debug(f"Hit regions spec failed: {e}")
977
+
978
+ # Save the bundle
979
+ save_bundle(bundle_data, p, bundle_type=BundleType.PLTZ, as_zip=as_zip)
980
+
981
+
494
982
  def _save(
495
983
  obj,
496
984
  spath,
@@ -502,6 +990,7 @@ def _save(
502
990
  auto_crop=False,
503
991
  crop_margin_mm=1.0,
504
992
  metadata_extra=None,
993
+ json_schema="editable",
505
994
  **kwargs,
506
995
  ):
507
996
  # Don't use object's own save method - use consistent handlers
@@ -516,6 +1005,50 @@ def _save(
516
1005
  save_canvas(obj, spath, **kwargs)
517
1006
  return
518
1007
 
1008
+ # Handle bundle formats (.figz, .pltz, .statsz and their .d variants)
1009
+ # These use special naming: file.figz (ZIP) or file.figz.d (directory)
1010
+ # Note: .figz defaults to ZIP (as_zip=True), .pltz/.statsz default to directory
1011
+ bundle_extensions = (".figz", ".pltz", ".statsz")
1012
+ for bext in bundle_extensions:
1013
+ if spath.endswith(bext) or spath.endswith(f"{bext}.d"):
1014
+ # Remove as_zip from kwargs if present to avoid duplicate
1015
+ bundle_kwargs = {k: v for k, v in kwargs.items() if k != 'as_zip'}
1016
+ as_zip = kwargs.get('as_zip', not spath.endswith(".d"))
1017
+ if bext == ".figz":
1018
+ import scitex.fig as sfig
1019
+ # figz defaults to ZIP, so always pass as_zip explicitly
1020
+ sfig.save_figz(obj, spath, as_zip=as_zip, **bundle_kwargs)
1021
+ elif bext == ".pltz":
1022
+ _save_pltz_bundle(obj, spath, as_zip=as_zip, **bundle_kwargs)
1023
+ elif bext == ".statsz":
1024
+ import scitex.stats as sstats
1025
+ sstats.save_statsz(obj, spath, as_zip=as_zip, **bundle_kwargs)
1026
+
1027
+ # Log "Saved to:" for bundle formats (consistent with other formats)
1028
+ # For bundles, determine the actual saved path (zip or directory)
1029
+ bundle_path = spath if as_zip else f"{spath}.d" if not spath.endswith(".d") else spath
1030
+
1031
+ if verbose and _os.path.exists(bundle_path):
1032
+ file_size = getsize(bundle_path)
1033
+ file_size = readable_bytes(file_size)
1034
+ try:
1035
+ rel_path = _os.path.relpath(bundle_path, _os.getcwd())
1036
+ except ValueError:
1037
+ rel_path = bundle_path
1038
+ logger.success(f"Saved to: ./{rel_path} ({file_size})")
1039
+
1040
+ # Handle symlinks for bundle formats (consistent with other formats)
1041
+ if symlink_from_cwd and _os.path.exists(bundle_path):
1042
+ # Create symlink from cwd to bundle path
1043
+ bundle_basename = _os.path.basename(bundle_path)
1044
+ bundle_cwd = _os.path.join(_os.getcwd(), bundle_basename)
1045
+ _symlink(bundle_path, bundle_cwd, symlink_from_cwd, verbose)
1046
+
1047
+ if symlink_to and _os.path.exists(bundle_path):
1048
+ _symlink_to(bundle_path, symlink_to, verbose)
1049
+
1050
+ return
1051
+
519
1052
  # Try dispatch dictionary first for O(1) lookup
520
1053
  if ext in _FILE_HANDLERS:
521
1054
  # Check if handler needs special parameters
@@ -540,6 +1073,7 @@ def _save(
540
1073
  auto_crop=auto_crop,
541
1074
  crop_margin_mm=crop_margin_mm,
542
1075
  metadata_extra=metadata_extra,
1076
+ json_schema=json_schema,
543
1077
  **kwargs,
544
1078
  )
545
1079
  elif ext in [".hdf5", ".h5", ".zarr"]:
@@ -554,7 +1088,7 @@ def _save(
554
1088
  elif spath.endswith(".pkl.gz"):
555
1089
  save_pickle_compressed(obj, spath, **kwargs)
556
1090
  else:
557
- warnings.warn(f"Unsupported file format. {spath} was not saved.")
1091
+ logger.warning(f"Unsupported file format. {spath} was not saved.")
558
1092
 
559
1093
  if verbose:
560
1094
  if _os.path.exists(spath):
@@ -569,9 +1103,7 @@ def _save(
569
1103
  logger.success(f"Saved to: ./{rel_path} ({file_size})")
570
1104
 
571
1105
 
572
- def _save_separate_legends(
573
- obj, spath, symlink_from_cwd=False, dry_run=False, **kwargs
574
- ):
1106
+ def _save_separate_legends(obj, spath, symlink_from_cwd=False, dry_run=False, **kwargs):
575
1107
  """Save separate legend files if ax.legend('separate') was used."""
576
1108
  if dry_run:
577
1109
  return
@@ -653,6 +1185,7 @@ def _handle_image_with_csv(
653
1185
  auto_crop=True,
654
1186
  crop_margin_mm=1.0,
655
1187
  metadata_extra=None,
1188
+ json_schema="editable",
656
1189
  **kwargs,
657
1190
  ):
658
1191
  """Handle image file saving with optional CSV export and auto-cropping."""
@@ -661,7 +1194,7 @@ def _handle_image_with_csv(
661
1194
 
662
1195
  # Auto-collect metadata from scitex figures if not explicitly provided
663
1196
  collected_metadata = None
664
- if 'metadata' not in kwargs or kwargs['metadata'] is None:
1197
+ if "metadata" not in kwargs or kwargs["metadata"] is None:
665
1198
  try:
666
1199
  # Check if this is a matplotlib figure or scitex wrapper
667
1200
  import matplotlib.figure
@@ -669,93 +1202,155 @@ def _handle_image_with_csv(
669
1202
  fig_mpl = None
670
1203
  if isinstance(obj, matplotlib.figure.Figure):
671
1204
  fig_mpl = obj
672
- elif hasattr(obj, '_fig_mpl'): # FigWrapper
1205
+ elif hasattr(obj, "_fig_mpl"): # FigWrapper
673
1206
  fig_mpl = obj._fig_mpl
674
- elif hasattr(obj, 'figure') and isinstance(obj.figure, matplotlib.figure.Figure):
1207
+ elif hasattr(obj, "figure") and isinstance(
1208
+ obj.figure, matplotlib.figure.Figure
1209
+ ):
675
1210
  fig_mpl = obj.figure
676
1211
 
677
1212
  # If we have a figure, try to collect metadata
678
1213
  if fig_mpl is not None:
679
- # Get first axes if available
1214
+ # Get axes from scitex wrapper if available (for multi-axes support)
1215
+ # Priority: FigWrapper.axes (AxesWrapper) > mpl axes with _scitex_wrapper > mpl axes
680
1216
  ax = None
681
- if hasattr(fig_mpl, 'axes') and len(fig_mpl.axes) > 0:
682
- ax = fig_mpl.axes[0]
1217
+
1218
+ # First try to get AxesWrapper from FigWrapper (obj)
1219
+ if hasattr(obj, "axes"):
1220
+ # obj is FigWrapper, get its axes (could be AxisWrapper or AxesWrapper)
1221
+ ax = obj.axes
1222
+ elif hasattr(fig_mpl, "axes") and len(fig_mpl.axes) > 0:
1223
+ mpl_ax = fig_mpl.axes[0]
1224
+ # Try to get scitex wrapper which has history for recipe schema
1225
+ if hasattr(mpl_ax, '_scitex_wrapper'):
1226
+ ax = mpl_ax._scitex_wrapper
1227
+ else:
1228
+ ax = mpl_ax
683
1229
 
684
1230
  # Collect metadata using scitex's metadata collector
685
1231
  try:
686
- from scitex.plt.utils import collect_figure_metadata
687
-
688
- # Extract plot_id from filename (e.g., "01_plot.png" -> "01_plot")
689
- plot_id = _os.path.splitext(_os.path.basename(spath))[0]
690
-
691
- auto_metadata = collect_figure_metadata(fig_mpl, ax, plot_id=plot_id)
1232
+ if json_schema == "editable":
1233
+ from scitex.plt.utils.metadata import export_editable_figure
1234
+ auto_metadata = export_editable_figure(fig_mpl)
1235
+ elif json_schema == "recipe":
1236
+ from scitex.plt.utils import collect_recipe_metadata
1237
+ auto_metadata = collect_recipe_metadata(
1238
+ fig_mpl, ax,
1239
+ auto_crop=auto_crop,
1240
+ crop_margin_mm=crop_margin_mm,
1241
+ )
1242
+ else:
1243
+ from scitex.plt.utils import collect_figure_metadata
1244
+ auto_metadata = collect_figure_metadata(fig_mpl, ax)
692
1245
 
693
1246
  if auto_metadata:
694
- kwargs['metadata'] = auto_metadata
1247
+ kwargs["metadata"] = auto_metadata
695
1248
  collected_metadata = auto_metadata # Save for JSON export
696
1249
  if verbose:
697
- logger.info(" Auto-collected metadata from figure")
1250
+ schema_names = {"editable": "editable v0.3", "recipe": "recipe", "verbose": "verbose"}
1251
+ schema_name = schema_names.get(json_schema, json_schema)
1252
+ logger.info(f" • Auto-collected metadata ({schema_name} schema)")
698
1253
  except ImportError:
699
1254
  pass # collect_figure_metadata not available
700
1255
  except Exception as e:
701
1256
  if verbose:
702
- import warnings
703
- warnings.warn(f"Could not auto-collect metadata: {e}")
1257
+ logger.warning(f"Could not auto-collect metadata: {e}")
704
1258
  except Exception:
705
1259
  pass # Silently continue if auto-collection fails
706
1260
  else:
707
1261
  # Use explicitly provided metadata
708
- collected_metadata = kwargs.get('metadata')
1262
+ collected_metadata = kwargs.get("metadata")
709
1263
 
710
1264
  # Merge metadata_extra with collected_metadata
711
1265
  if metadata_extra is not None and collected_metadata is not None:
712
1266
  # Deep merge: metadata_extra takes precedence
713
1267
  import copy
1268
+
714
1269
  collected_metadata = copy.deepcopy(collected_metadata)
715
1270
 
716
1271
  # If metadata_extra has plot_type and it doesn't exist in collected, add it
717
- if 'plot_type' in metadata_extra:
718
- collected_metadata['plot_type'] = metadata_extra['plot_type']
1272
+ if "plot_type" in metadata_extra:
1273
+ collected_metadata["plot_type"] = metadata_extra["plot_type"]
719
1274
 
720
1275
  # Merge style information
721
- if 'style' in metadata_extra:
722
- collected_metadata['style'] = metadata_extra['style']
1276
+ if "style" in metadata_extra:
1277
+ collected_metadata["style"] = metadata_extra["style"]
723
1278
 
724
1279
  # Merge any other fields from metadata_extra
725
1280
  for key, value in metadata_extra.items():
726
- if key not in ['plot_type', 'style']:
1281
+ if key not in ["plot_type", "style"]:
727
1282
  collected_metadata[key] = value
728
1283
 
729
1284
  # Update kwargs metadata for image saving
730
- kwargs['metadata'] = collected_metadata
1285
+ kwargs["metadata"] = collected_metadata
731
1286
 
732
1287
  save_image(obj, spath, verbose=verbose, **kwargs)
733
1288
 
734
1289
  # Auto-crop if requested (only for raster formats)
1290
+ crop_offset = None
735
1291
  if auto_crop and not dry_run:
736
1292
  # Get file extension
737
1293
  ext = spath.lower()
738
1294
 
739
1295
  # Only crop raster formats (PNG, JPEG, TIFF)
740
1296
  # Skip vector formats (PDF, SVG) as they don't benefit from cropping
741
- if ext.endswith(('.png', '.jpg', '.jpeg', '.tiff', '.tif')):
1297
+ if ext.endswith((".png", ".jpg", ".jpeg", ".tiff", ".tif")):
742
1298
  try:
743
1299
  from scitex.plt.utils._crop import crop
744
1300
 
745
1301
  # Convert mm to pixels (assuming 300 DPI)
746
1302
  # 1mm at 300 DPI = 11.81 pixels ≈ 12 pixels
747
- dpi = kwargs.get('dpi', 300)
1303
+ dpi = kwargs.get("dpi", 300)
748
1304
  margin_px = int(crop_margin_mm * dpi / 25.4) # 25.4mm per inch
749
1305
 
750
- # Crop the saved image in place
751
- crop(spath, output_path=spath, margin=margin_px, overwrite=True, verbose=False)
1306
+ # Crop the saved image in place, get crop offset for metadata adjustment
1307
+ _, crop_offset = crop(
1308
+ spath,
1309
+ output_path=spath,
1310
+ margin=margin_px,
1311
+ overwrite=True,
1312
+ verbose=False,
1313
+ return_offset=True,
1314
+ )
1315
+
1316
+ # Adjust axes_bbox_px in metadata to account for crop offset
1317
+ if crop_offset and collected_metadata:
1318
+ if "axes_bbox_px" in collected_metadata:
1319
+ bbox = collected_metadata["axes_bbox_px"]
1320
+ # Subtract crop offset from all coordinates
1321
+ # left/upper is where the crop started
1322
+ left_offset = crop_offset["left"]
1323
+ upper_offset = crop_offset["upper"]
1324
+ bbox["x0"] = bbox.get("x0", 0) - left_offset
1325
+ bbox["x1"] = bbox.get("x1", 0) - left_offset
1326
+ bbox["y0"] = bbox.get("y0", 0) - upper_offset
1327
+ bbox["y1"] = bbox.get("y1", 0) - upper_offset
1328
+ # Update width/height to match new image size
1329
+ # (bbox width/height shouldn't change, but figure size does)
1330
+
1331
+ # Also update figure size in metadata
1332
+ if "figure" in collected_metadata:
1333
+ fig_meta = collected_metadata["figure"]
1334
+ if "size_px" in fig_meta:
1335
+ fig_meta["size_px"] = [
1336
+ crop_offset["new_width"],
1337
+ crop_offset["new_height"],
1338
+ ]
1339
+ if "dimensions" in collected_metadata:
1340
+ dim_meta = collected_metadata["dimensions"]
1341
+ if "figure_size_px" in dim_meta:
1342
+ dim_meta["figure_size_px"] = [
1343
+ crop_offset["new_width"],
1344
+ crop_offset["new_height"],
1345
+ ]
752
1346
 
753
1347
  if verbose:
754
- logger.info(f" • Auto-cropped with {crop_margin_mm}mm margin ({margin_px}px at {dpi} DPI)")
1348
+ logger.info(
1349
+ f" • Auto-cropped with {crop_margin_mm}mm margin ({margin_px}px at {dpi} DPI)"
1350
+ )
755
1351
 
756
1352
  except Exception as e:
757
- import warnings
758
- warnings.warn(f"Auto-crop failed: {e}. Image saved without cropping.")
1353
+ logger.warning(f"Auto-crop failed: {e}. Image saved without cropping.")
759
1354
 
760
1355
  # Handle separate legend saving
761
1356
  _save_separate_legends(
@@ -772,11 +1367,12 @@ def _handle_image_with_csv(
772
1367
 
773
1368
  # Check if the path contains an image extension directory (e.g., ./png/, ./jpg/)
774
1369
  # If so, save CSV in a parallel ./csv/ directory
775
- image_extensions = ['png', 'jpg', 'jpeg', 'gif', 'tiff', 'tif', 'svg', 'pdf']
1370
+ image_extensions = ["png", "jpg", "jpeg", "gif", "tiff", "tif", "svg", "pdf"]
776
1371
  parent_dir = _os.path.dirname(spath)
777
1372
  parent_name = _os.path.basename(parent_dir)
778
1373
  filename_without_ext = _os.path.splitext(_os.path.basename(spath))[0]
779
1374
 
1375
+ csv_path = None # Initialize to avoid UnboundLocalError when CSV export is skipped
780
1376
  try:
781
1377
  # Get the figure object that may contain plot data
782
1378
  fig_obj = _get_figure_with_data(obj)
@@ -792,7 +1388,9 @@ def _handle_image_with_csv(
792
1388
  # Create parallel csv/ directory
793
1389
  grandparent_dir = _os.path.dirname(parent_dir)
794
1390
  csv_dir = _os.path.join(grandparent_dir, "csv")
795
- csv_path = _os.path.join(csv_dir, filename_without_ext + ".csv")
1391
+ csv_path = _os.path.join(
1392
+ csv_dir, filename_without_ext + ".csv"
1393
+ )
796
1394
  else:
797
1395
  # Save CSV in same directory as image
798
1396
  csv_path = _os.path.splitext(spath)[0] + ".csv"
@@ -810,20 +1408,59 @@ def _handle_image_with_csv(
810
1408
  no_csv=True,
811
1409
  )
812
1410
 
1411
+ # Update metadata with actual CSV info (after export)
1412
+ # This ensures column names match exactly, including any
1413
+ # deduplication suffixes added by pandas
1414
+ if collected_metadata is not None:
1415
+ try:
1416
+ from scitex.plt.utils._collect_figure_metadata import (
1417
+ _compute_csv_hash,
1418
+ )
1419
+
1420
+ # Ensure data section exists
1421
+ if "data" not in collected_metadata:
1422
+ collected_metadata["data"] = {}
1423
+
1424
+ # Get actual column names from exported DataFrame
1425
+ actual_columns = list(csv_data.columns)
1426
+
1427
+ # Update data section with csv_path (relative to JSON)
1428
+ # Since JSON and CSV are in the same or parallel directories,
1429
+ # use just the filename for simplicity
1430
+ collected_metadata["data"]["csv_path"] = _os.path.basename(csv_path)
1431
+
1432
+ # Update columns to use flat list of actual columns
1433
+ collected_metadata["data"]["columns_actual"] = actual_columns
1434
+
1435
+ # Compute hash of actual CSV data
1436
+ collected_metadata["data"]["csv_hash"] = _compute_csv_hash(
1437
+ csv_data
1438
+ )
1439
+ except Exception:
1440
+ pass # Silently continue if update fails
1441
+
813
1442
  # Create symlink_to for CSV if it was specified for the image
814
1443
  if symlink_to:
815
1444
  # Apply same directory transformation for symlink
816
1445
  symlink_parent_dir = _os.path.dirname(symlink_to)
817
1446
  symlink_parent_name = _os.path.basename(symlink_parent_dir)
818
- symlink_filename_without_ext = _os.path.splitext(_os.path.basename(symlink_to))[0]
1447
+ symlink_filename_without_ext = _os.path.splitext(
1448
+ _os.path.basename(symlink_to)
1449
+ )[0]
819
1450
 
820
1451
  if symlink_parent_name.lower() in image_extensions:
821
- symlink_grandparent_dir = _os.path.dirname(symlink_parent_dir)
1452
+ symlink_grandparent_dir = _os.path.dirname(
1453
+ symlink_parent_dir
1454
+ )
822
1455
  csv_symlink_to = _os.path.join(
823
- symlink_grandparent_dir, "csv", symlink_filename_without_ext + ".csv"
1456
+ symlink_grandparent_dir,
1457
+ "csv",
1458
+ symlink_filename_without_ext + ".csv",
824
1459
  )
825
1460
  else:
826
- csv_symlink_to = _os.path.splitext(symlink_to)[0] + ".csv"
1461
+ csv_symlink_to = (
1462
+ _os.path.splitext(symlink_to)[0] + ".csv"
1463
+ )
827
1464
 
828
1465
  _symlink_to(csv_path, csv_symlink_to, True)
829
1466
 
@@ -842,14 +1479,24 @@ def _handle_image_with_csv(
842
1479
  ]
843
1480
  if isinstance(original_path, str):
844
1481
  # Apply same directory transformation for symlink
845
- orig_parent_dir = _os.path.dirname(original_path)
846
- orig_parent_name = _os.path.basename(orig_parent_dir)
847
- orig_filename_without_ext = _os.path.splitext(_os.path.basename(original_path))[0]
1482
+ orig_parent_dir = _os.path.dirname(
1483
+ original_path
1484
+ )
1485
+ orig_parent_name = _os.path.basename(
1486
+ orig_parent_dir
1487
+ )
1488
+ orig_filename_without_ext = _os.path.splitext(
1489
+ _os.path.basename(original_path)
1490
+ )[0]
848
1491
 
849
1492
  if orig_parent_name.lower() in image_extensions:
850
- orig_grandparent_dir = _os.path.dirname(orig_parent_dir)
1493
+ orig_grandparent_dir = _os.path.dirname(
1494
+ orig_parent_dir
1495
+ )
851
1496
  csv_relative = _os.path.join(
852
- orig_grandparent_dir, "csv", orig_filename_without_ext + ".csv"
1497
+ orig_grandparent_dir,
1498
+ "csv",
1499
+ orig_filename_without_ext + ".csv",
853
1500
  )
854
1501
  else:
855
1502
  csv_relative = original_path.replace(
@@ -865,9 +1512,7 @@ def _handle_image_with_csv(
865
1512
  else:
866
1513
  # Fallback to basename if we can't find the original path
867
1514
  csv_cwd = (
868
- _os.getcwd()
869
- + "/"
870
- + _os.path.basename(csv_path)
1515
+ _os.getcwd() + "/" + _os.path.basename(csv_path)
871
1516
  )
872
1517
  _symlink(csv_path, csv_cwd, True, True)
873
1518
 
@@ -879,14 +1524,18 @@ def _handle_image_with_csv(
879
1524
  if parent_name.lower() in image_extensions:
880
1525
  grandparent_dir = _os.path.dirname(parent_dir)
881
1526
  csv_dir = _os.path.join(grandparent_dir, "csv")
882
- csv_sigmaplot_path = _os.path.join(csv_dir, filename_without_ext + "_for_sigmaplot.csv")
1527
+ csv_sigmaplot_path = _os.path.join(
1528
+ csv_dir, filename_without_ext + "_for_sigmaplot.csv"
1529
+ )
883
1530
  else:
884
1531
  csv_sigmaplot_path = spath.replace(
885
1532
  ext_wo_dot, "csv"
886
1533
  ).replace(".csv", "_for_sigmaplot.csv")
887
1534
 
888
1535
  # Ensure parent directory exists
889
- _os.makedirs(_os.path.dirname(csv_sigmaplot_path), exist_ok=True)
1536
+ _os.makedirs(
1537
+ _os.path.dirname(csv_sigmaplot_path), exist_ok=True
1538
+ )
890
1539
  # Save directly using _save to avoid path doubling
891
1540
  # Don't pass image-specific kwargs to CSV save
892
1541
  _save(
@@ -902,12 +1551,18 @@ def _handle_image_with_csv(
902
1551
  if symlink_to:
903
1552
  symlink_parent_dir = _os.path.dirname(symlink_to)
904
1553
  symlink_parent_name = _os.path.basename(symlink_parent_dir)
905
- symlink_filename_without_ext = _os.path.splitext(_os.path.basename(symlink_to))[0]
1554
+ symlink_filename_without_ext = _os.path.splitext(
1555
+ _os.path.basename(symlink_to)
1556
+ )[0]
906
1557
 
907
1558
  if symlink_parent_name.lower() in image_extensions:
908
- symlink_grandparent_dir = _os.path.dirname(symlink_parent_dir)
1559
+ symlink_grandparent_dir = _os.path.dirname(
1560
+ symlink_parent_dir
1561
+ )
909
1562
  csv_sigmaplot_symlink_to = _os.path.join(
910
- symlink_grandparent_dir, "csv", symlink_filename_without_ext + "_for_sigmaplot.csv"
1563
+ symlink_grandparent_dir,
1564
+ "csv",
1565
+ symlink_filename_without_ext + "_for_sigmaplot.csv",
911
1566
  )
912
1567
  else:
913
1568
  csv_sigmaplot_symlink_to = (
@@ -930,12 +1585,7 @@ def _handle_image_with_csv(
930
1585
  )
931
1586
  _symlink(csv_sigmaplot_path, csv_cwd, True, True)
932
1587
  except Exception as e:
933
- import warnings
934
-
935
- warnings.warn(f"CSV export failed: {e}")
936
- import traceback
937
-
938
- traceback.print_exc()
1588
+ logger.warning(f"CSV export failed: {e}")
939
1589
 
940
1590
  # Save metadata as JSON if collected
941
1591
  if collected_metadata is not None and not dry_run:
@@ -944,7 +1594,16 @@ def _handle_image_with_csv(
944
1594
  # If so, save JSON in a parallel ./json/ directory
945
1595
  # Example: ./path/to/output/png/fig.png -> ./path/to/output/json/fig.json
946
1596
  # Example: ./path/to/output/fig.png -> ./path/to/output/fig.json (same dir)
947
- image_extensions = ['png', 'jpg', 'jpeg', 'gif', 'tiff', 'tif', 'svg', 'pdf']
1597
+ image_extensions = [
1598
+ "png",
1599
+ "jpg",
1600
+ "jpeg",
1601
+ "gif",
1602
+ "tiff",
1603
+ "tif",
1604
+ "svg",
1605
+ "pdf",
1606
+ ]
948
1607
  parent_dir = _os.path.dirname(spath)
949
1608
  parent_name = _os.path.basename(parent_dir)
950
1609
  filename_without_ext = _os.path.splitext(_os.path.basename(spath))[0]
@@ -972,17 +1631,29 @@ def _handle_image_with_csv(
972
1631
  no_csv=True,
973
1632
  )
974
1633
 
1634
+ # Verify CSV/JSON consistency (data_ref must match columns_actual)
1635
+ # Only check for verbose schema - recipe/editable schemas use different data_ref structure
1636
+ if csv_path and not dry_run and json_schema == "verbose":
1637
+ from scitex.plt.utils._collect_figure_metadata import (
1638
+ assert_csv_json_consistency,
1639
+ )
1640
+ assert_csv_json_consistency(csv_path, json_path)
1641
+
975
1642
  # Create symlink_to for JSON if it was specified for the image
976
1643
  if symlink_to:
977
1644
  # Apply same directory transformation for symlink
978
1645
  symlink_parent_dir = _os.path.dirname(symlink_to)
979
1646
  symlink_parent_name = _os.path.basename(symlink_parent_dir)
980
- symlink_filename_without_ext = _os.path.splitext(_os.path.basename(symlink_to))[0]
1647
+ symlink_filename_without_ext = _os.path.splitext(
1648
+ _os.path.basename(symlink_to)
1649
+ )[0]
981
1650
 
982
1651
  if symlink_parent_name.lower() in image_extensions:
983
1652
  symlink_grandparent_dir = _os.path.dirname(symlink_parent_dir)
984
1653
  json_symlink_to = _os.path.join(
985
- symlink_grandparent_dir, "json", symlink_filename_without_ext + ".json"
1654
+ symlink_grandparent_dir,
1655
+ "json",
1656
+ symlink_filename_without_ext + ".json",
986
1657
  )
987
1658
  else:
988
1659
  json_symlink_to = _os.path.splitext(symlink_to)[0] + ".json"
@@ -1004,12 +1675,16 @@ def _handle_image_with_csv(
1004
1675
  # Apply same directory transformation for symlink
1005
1676
  orig_parent_dir = _os.path.dirname(original_path)
1006
1677
  orig_parent_name = _os.path.basename(orig_parent_dir)
1007
- orig_filename_without_ext = _os.path.splitext(_os.path.basename(original_path))[0]
1678
+ orig_filename_without_ext = _os.path.splitext(
1679
+ _os.path.basename(original_path)
1680
+ )[0]
1008
1681
 
1009
1682
  if orig_parent_name.lower() in image_extensions:
1010
1683
  orig_grandparent_dir = _os.path.dirname(orig_parent_dir)
1011
1684
  json_relative = _os.path.join(
1012
- orig_grandparent_dir, "json", orig_filename_without_ext + ".json"
1685
+ orig_grandparent_dir,
1686
+ "json",
1687
+ orig_filename_without_ext + ".json",
1013
1688
  )
1014
1689
  else:
1015
1690
  json_relative = original_path.replace(
@@ -1025,16 +1700,16 @@ def _handle_image_with_csv(
1025
1700
  json_cwd = _os.getcwd() + "/" + _os.path.basename(json_path)
1026
1701
  _symlink(json_path, json_cwd, True, True)
1027
1702
 
1703
+ except AssertionError:
1704
+ # Re-raise assertion errors - these are validation failures that should stop execution
1705
+ raise
1028
1706
  except Exception as e:
1029
- import warnings
1030
- warnings.warn(f"JSON metadata export failed: {e}")
1031
- import traceback
1032
- traceback.print_exc()
1707
+ logger.warning(f"JSON metadata export failed: {e}")
1033
1708
 
1034
1709
 
1035
1710
  # Dispatch dictionary for O(1) file format lookup
1036
1711
  _FILE_HANDLERS = {
1037
- # Canvas directory format (scitex.vis)
1712
+ # Canvas directory format (scitex.fig)
1038
1713
  ".canvas": save_canvas,
1039
1714
  # Excel formats
1040
1715
  ".xlsx": save_excel,