scitex 2.14.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2498) hide show
  1. scitex/.mcp.json +46 -0
  2. scitex/__init__.py +268 -0
  3. scitex/__main__.py +45 -0
  4. scitex/__version__.py +16 -0
  5. scitex/_install_guide.py +262 -0
  6. scitex/_mcp_tools/__init__.py +36 -0
  7. scitex/_mcp_tools/audio.py +134 -0
  8. scitex/_mcp_tools/canvas.py +128 -0
  9. scitex/_mcp_tools/capture.py +174 -0
  10. scitex/_mcp_tools/diagram.py +106 -0
  11. scitex/_mcp_tools/plt.py +350 -0
  12. scitex/_mcp_tools/scholar.py +373 -0
  13. scitex/_mcp_tools/stats.py +206 -0
  14. scitex/_mcp_tools/template.py +66 -0
  15. scitex/_mcp_tools/ui.py +80 -0
  16. scitex/_mcp_tools/writer.py +220 -0
  17. scitex/_optional_deps.py +333 -0
  18. scitex/ai/README.md +295 -0
  19. scitex/ai/__init__.py +74 -0
  20. scitex/ai/_gen_ai/_Anthropic.py +173 -0
  21. scitex/ai/_gen_ai/_BaseGenAI.py +337 -0
  22. scitex/ai/_gen_ai/_DeepSeek.py +183 -0
  23. scitex/ai/_gen_ai/_Google.py +162 -0
  24. scitex/ai/_gen_ai/_Groq.py +95 -0
  25. scitex/ai/_gen_ai/_Llama.py +144 -0
  26. scitex/ai/_gen_ai/_OpenAI.py +235 -0
  27. scitex/ai/_gen_ai/_PARAMS.py +552 -0
  28. scitex/ai/_gen_ai/_Perplexity.py +207 -0
  29. scitex/ai/_gen_ai/__init__.py +44 -0
  30. scitex/ai/_gen_ai/_calc_cost.py +78 -0
  31. scitex/ai/_gen_ai/_format_output_func.py +185 -0
  32. scitex/ai/_gen_ai/_genai_factory.py +71 -0
  33. scitex/ai/activation/__init__.py +8 -0
  34. scitex/ai/activation/_define.py +11 -0
  35. scitex/ai/classification/Classifier.py +131 -0
  36. scitex/ai/classification/CrossValidationExperiment.py +368 -0
  37. scitex/ai/classification/README.md +251 -0
  38. scitex/ai/classification/__init__.py +46 -0
  39. scitex/ai/classification/examples/timeseries_cv_demo.py +416 -0
  40. scitex/ai/classification/examples/verify_multi/config.json +13 -0
  41. scitex/ai/classification/examples/verify_multi/multi_task_comparison.md +20 -0
  42. scitex/ai/classification/examples/verify_multi/multi_task_validation.json +103 -0
  43. scitex/ai/classification/examples/verify_multi/paper_export/README.md +20 -0
  44. scitex/ai/classification/examples/verify_multi/paper_export/raw_results.json +174 -0
  45. scitex/ai/classification/examples/verify_multi/task1/metadata.json +56 -0
  46. scitex/ai/classification/examples/verify_multi/task1/paper_export/README.md +20 -0
  47. scitex/ai/classification/examples/verify_multi/task1/paper_export/raw_results.json +93 -0
  48. scitex/ai/classification/examples/verify_multi/task1/paper_export/summary_table.tex +15 -0
  49. scitex/ai/classification/examples/verify_multi/task1/report.md +31 -0
  50. scitex/ai/classification/examples/verify_multi/task1/validation_report.json +47 -0
  51. scitex/ai/classification/examples/verify_multi/task2/metadata.json +56 -0
  52. scitex/ai/classification/examples/verify_multi/task2/paper_export/README.md +20 -0
  53. scitex/ai/classification/examples/verify_multi/task2/paper_export/raw_results.json +93 -0
  54. scitex/ai/classification/examples/verify_multi/task2/paper_export/summary_table.tex +15 -0
  55. scitex/ai/classification/examples/verify_multi/task2/report.md +31 -0
  56. scitex/ai/classification/examples/verify_multi/task2/validation_report.json +47 -0
  57. scitex/ai/classification/examples/verify_test/metadata.json +62 -0
  58. scitex/ai/classification/examples/verify_test/paper_export/README.md +20 -0
  59. scitex/ai/classification/examples/verify_test/paper_export/raw_results.json +131 -0
  60. scitex/ai/classification/examples/verify_test/paper_export/summary_table.tex +15 -0
  61. scitex/ai/classification/examples/verify_test/report.md +31 -0
  62. scitex/ai/classification/examples/verify_test/validation_report.json +52 -0
  63. scitex/ai/classification/reporters/_BaseClassificationReporter.py +283 -0
  64. scitex/ai/classification/reporters/_ClassificationReporter.py +758 -0
  65. scitex/ai/classification/reporters/_MultiClassificationReporter.py +403 -0
  66. scitex/ai/classification/reporters/_SingleClassificationReporter.py +1778 -0
  67. scitex/ai/classification/reporters/__init__.py +11 -0
  68. scitex/ai/classification/reporters/reporter_utils/_Plotter.py +1122 -0
  69. scitex/ai/classification/reporters/reporter_utils/__init__.py +72 -0
  70. scitex/ai/classification/reporters/reporter_utils/aggregation.py +439 -0
  71. scitex/ai/classification/reporters/reporter_utils/data_models.py +321 -0
  72. scitex/ai/classification/reporters/reporter_utils/reporting.py +1223 -0
  73. scitex/ai/classification/reporters/reporter_utils/storage.py +224 -0
  74. scitex/ai/classification/reporters/reporter_utils/validation.py +382 -0
  75. scitex/ai/classification/timeseries/README.md +313 -0
  76. scitex/ai/classification/timeseries/_TimeSeriesBlockingSplit.py +643 -0
  77. scitex/ai/classification/timeseries/_TimeSeriesCalendarSplit.py +732 -0
  78. scitex/ai/classification/timeseries/_TimeSeriesMetadata.py +139 -0
  79. scitex/ai/classification/timeseries/_TimeSeriesSlidingWindowSplit.py +1640 -0
  80. scitex/ai/classification/timeseries/_TimeSeriesSlidingWindowSplit_v01-not-using-n_splits.py +1609 -0
  81. scitex/ai/classification/timeseries/_TimeSeriesStrategy.py +83 -0
  82. scitex/ai/classification/timeseries/_TimeSeriesStratifiedSplit.py +697 -0
  83. scitex/ai/classification/timeseries/__init__.py +37 -0
  84. scitex/ai/classification/timeseries/_normalize_timestamp.py +439 -0
  85. scitex/ai/classification/timeseries/run_all.sh +30 -0
  86. scitex/ai/clustering/__init__.py +11 -0
  87. scitex/ai/clustering/_pca.py +114 -0
  88. scitex/ai/clustering/_umap.py +375 -0
  89. scitex/ai/feature_extraction/__init__.py +58 -0
  90. scitex/ai/feature_extraction/vit.py +148 -0
  91. scitex/ai/feature_selection/__init__.py +30 -0
  92. scitex/ai/feature_selection/feature_selection.py +359 -0
  93. scitex/ai/loss/_L1L2Losses.py +34 -0
  94. scitex/ai/loss/__init__.py +12 -0
  95. scitex/ai/loss/multi_task_loss.py +47 -0
  96. scitex/ai/metrics/__init__.py +56 -0
  97. scitex/ai/metrics/_calc_bacc.py +61 -0
  98. scitex/ai/metrics/_calc_bacc_from_conf_mat.py +38 -0
  99. scitex/ai/metrics/_calc_clf_report.py +78 -0
  100. scitex/ai/metrics/_calc_conf_mat.py +95 -0
  101. scitex/ai/metrics/_calc_feature_importance.py +179 -0
  102. scitex/ai/metrics/_calc_mcc.py +61 -0
  103. scitex/ai/metrics/_calc_pre_rec_auc.py +116 -0
  104. scitex/ai/metrics/_calc_roc_auc.py +112 -0
  105. scitex/ai/metrics/_calc_seizure_prediction_metrics.py +505 -0
  106. scitex/ai/metrics/_calc_silhouette_score.py +501 -0
  107. scitex/ai/metrics/_normalize_labels.py +83 -0
  108. scitex/ai/optim/MIGRATION.md +43 -0
  109. scitex/ai/optim/Ranger_Deep_Learning_Optimizer/LICENSE +201 -0
  110. scitex/ai/optim/Ranger_Deep_Learning_Optimizer/README.md +80 -0
  111. scitex/ai/optim/Ranger_Deep_Learning_Optimizer/__init__.py +0 -0
  112. scitex/ai/optim/Ranger_Deep_Learning_Optimizer/ranger/__init__.py +3 -0
  113. scitex/ai/optim/Ranger_Deep_Learning_Optimizer/ranger/ranger.py +204 -0
  114. scitex/ai/optim/Ranger_Deep_Learning_Optimizer/ranger/ranger2020.py +235 -0
  115. scitex/ai/optim/Ranger_Deep_Learning_Optimizer/ranger/ranger913A.py +212 -0
  116. scitex/ai/optim/Ranger_Deep_Learning_Optimizer/ranger/rangerqh.py +184 -0
  117. scitex/ai/optim/Ranger_Deep_Learning_Optimizer/ranger-init.jpg +0 -0
  118. scitex/ai/optim/Ranger_Deep_Learning_Optimizer/ranger-with-gc-options.jpg +0 -0
  119. scitex/ai/optim/Ranger_Deep_Learning_Optimizer/setup.py +24 -0
  120. scitex/ai/optim/__init__.py +13 -0
  121. scitex/ai/optim/_get_set.py +31 -0
  122. scitex/ai/optim/_optimizers.py +71 -0
  123. scitex/ai/plt/__init__.py +65 -0
  124. scitex/ai/plt/_plot_feature_importance.py +321 -0
  125. scitex/ai/plt/_plot_learning_curve.py +330 -0
  126. scitex/ai/plt/_plot_optuna_study.py +226 -0
  127. scitex/ai/plt/_plot_pre_rec_curve.py +284 -0
  128. scitex/ai/plt/_plot_roc_curve.py +253 -0
  129. scitex/ai/plt/_stx_conf_mat.py +662 -0
  130. scitex/ai/sampling/undersample.py +44 -0
  131. scitex/ai/sk/__init__.py +11 -0
  132. scitex/ai/sk/_clf.py +58 -0
  133. scitex/ai/sk/_to_sktime.py +100 -0
  134. scitex/ai/sklearn/__init__.py +26 -0
  135. scitex/ai/sklearn/clf.py +58 -0
  136. scitex/ai/sklearn/to_sktime.py +100 -0
  137. scitex/ai/training/_EarlyStopping.py +149 -0
  138. scitex/ai/training/_LearningCurveLogger.py +552 -0
  139. scitex/ai/training/__init__.py +7 -0
  140. scitex/ai/utils/__init__.py +22 -0
  141. scitex/ai/utils/_check_params.py +49 -0
  142. scitex/ai/utils/_default_dataset.py +46 -0
  143. scitex/ai/utils/_format_samples_for_sktime.py +26 -0
  144. scitex/ai/utils/_label_encoder.py +134 -0
  145. scitex/ai/utils/_merge_labels.py +22 -0
  146. scitex/ai/utils/_sliding_window_data_augmentation.py +11 -0
  147. scitex/ai/utils/_under_sample.py +51 -0
  148. scitex/ai/utils/_verify_n_gpus.py +16 -0
  149. scitex/ai/utils/grid_search.py +148 -0
  150. scitex/audio/README.md +129 -0
  151. scitex/audio/__init__.py +392 -0
  152. scitex/audio/__main__.py +125 -0
  153. scitex/audio/_cross_process_lock.py +139 -0
  154. scitex/audio/_mcp/__init__.py +4 -0
  155. scitex/audio/_mcp/handlers.py +366 -0
  156. scitex/audio/_mcp/tool_schemas.py +203 -0
  157. scitex/audio/_tts.py +334 -0
  158. scitex/audio/engines/__init__.py +44 -0
  159. scitex/audio/engines/base.py +275 -0
  160. scitex/audio/engines/elevenlabs_engine.py +146 -0
  161. scitex/audio/engines/gtts_engine.py +162 -0
  162. scitex/audio/engines/pyttsx3_engine.py +131 -0
  163. scitex/audio/mcp_server.py +293 -0
  164. scitex/benchmark/__init__.py +42 -0
  165. scitex/benchmark/benchmark.py +407 -0
  166. scitex/benchmark/monitor.py +380 -0
  167. scitex/benchmark/profiler.py +300 -0
  168. scitex/bridge/__init__.py +121 -0
  169. scitex/bridge/_figrecipe.py +277 -0
  170. scitex/bridge/_helpers.py +150 -0
  171. scitex/bridge/_plt_vis.py +542 -0
  172. scitex/bridge/_protocol.py +283 -0
  173. scitex/bridge/_stats_plt.py +272 -0
  174. scitex/bridge/_stats_vis.py +281 -0
  175. scitex/browser/README.md +167 -0
  176. scitex/browser/__init__.py +139 -0
  177. scitex/browser/auth/__init__.py +35 -0
  178. scitex/browser/auth/google.py +386 -0
  179. scitex/browser/automation/CookieHandler.py +215 -0
  180. scitex/browser/automation/__init__.py +11 -0
  181. scitex/browser/collaboration/__init__.py +62 -0
  182. scitex/browser/collaboration/auth_helpers.py +96 -0
  183. scitex/browser/collaboration/collaborative_agent.py +138 -0
  184. scitex/browser/collaboration/credential_manager.py +188 -0
  185. scitex/browser/collaboration/interactive_panel.py +400 -0
  186. scitex/browser/collaboration/persistent_browser.py +170 -0
  187. scitex/browser/collaboration/shared_session.py +392 -0
  188. scitex/browser/collaboration/standard_interactions.py +247 -0
  189. scitex/browser/collaboration/visual_feedback.py +181 -0
  190. scitex/browser/core/BrowserMixin.py +321 -0
  191. scitex/browser/core/ChromeProfileManager.py +438 -0
  192. scitex/browser/core/__init__.py +18 -0
  193. scitex/browser/debugging/__init__.py +74 -0
  194. scitex/browser/debugging/_browser_logger.py +644 -0
  195. scitex/browser/debugging/_failure_capture.py +379 -0
  196. scitex/browser/debugging/_highlight_element.py +152 -0
  197. scitex/browser/debugging/_show_grid.py +153 -0
  198. scitex/browser/debugging/_sync_session.py +260 -0
  199. scitex/browser/debugging/_test_monitor.py +293 -0
  200. scitex/browser/debugging/_visual_cursor.py +443 -0
  201. scitex/browser/docs/ABOUT_PLAYWRIGHT.md +49 -0
  202. scitex/browser/interaction/__init__.py +24 -0
  203. scitex/browser/interaction/click_center.py +150 -0
  204. scitex/browser/interaction/click_with_fallbacks.py +203 -0
  205. scitex/browser/interaction/close_popups.py +511 -0
  206. scitex/browser/interaction/fill_with_fallbacks.py +209 -0
  207. scitex/browser/pdf/__init__.py +16 -0
  208. scitex/browser/pdf/click_download_for_chrome_pdf_viewer.py +201 -0
  209. scitex/browser/pdf/detect_chrome_pdf_viewer.py +195 -0
  210. scitex/browser/remote/CaptchaHandler.py +447 -0
  211. scitex/browser/remote/ZenRowsAPIClient.py +341 -0
  212. scitex/browser/remote/ZenRowsBrowserManager.py +596 -0
  213. scitex/browser/remote/__init__.py +11 -0
  214. scitex/browser/stealth/HumanBehavior.py +339 -0
  215. scitex/browser/stealth/StealthManager.py +993 -0
  216. scitex/browser/stealth/__init__.py +9 -0
  217. scitex/canvas/README.md +386 -0
  218. scitex/canvas/__init__.py +340 -0
  219. scitex/canvas/_mcp/__init__.py +4 -0
  220. scitex/canvas/_mcp/handlers.py +372 -0
  221. scitex/canvas/_mcp/tool_schemas.py +219 -0
  222. scitex/canvas/backend/__init__.py +56 -0
  223. scitex/canvas/backend/_export.py +166 -0
  224. scitex/canvas/backend/_parser.py +188 -0
  225. scitex/canvas/backend/_render.py +385 -0
  226. scitex/canvas/canvas.py +434 -0
  227. scitex/canvas/docs/CANVAS_ARCHITECTURE.md +307 -0
  228. scitex/canvas/editor/__init__.py +26 -0
  229. scitex/canvas/editor/_dearpygui_editor.py +1976 -0
  230. scitex/canvas/editor/_defaults.py +300 -0
  231. scitex/canvas/editor/_flask_editor.py +37 -0
  232. scitex/canvas/editor/_mpl_editor.py +246 -0
  233. scitex/canvas/editor/_qt_editor.py +1096 -0
  234. scitex/canvas/editor/_tkinter_editor.py +523 -0
  235. scitex/canvas/editor/edit/__init__.py +47 -0
  236. scitex/canvas/editor/edit/backend_detector.py +109 -0
  237. scitex/canvas/editor/edit/bundle_resolver.py +248 -0
  238. scitex/canvas/editor/edit/editor_launcher.py +292 -0
  239. scitex/canvas/editor/edit/manual_handler.py +53 -0
  240. scitex/canvas/editor/edit/panel_loader.py +246 -0
  241. scitex/canvas/editor/edit/path_resolver.py +67 -0
  242. scitex/canvas/editor/flask_editor/__init__.py +21 -0
  243. scitex/canvas/editor/flask_editor/_bbox.py +1340 -0
  244. scitex/canvas/editor/flask_editor/_core.py +1688 -0
  245. scitex/canvas/editor/flask_editor/_plotter.py +601 -0
  246. scitex/canvas/editor/flask_editor/_renderer.py +854 -0
  247. scitex/canvas/editor/flask_editor/_utils.py +80 -0
  248. scitex/canvas/editor/flask_editor/static/css/base/reset.css +41 -0
  249. scitex/canvas/editor/flask_editor/static/css/base/typography.css +16 -0
  250. scitex/canvas/editor/flask_editor/static/css/base/variables.css +85 -0
  251. scitex/canvas/editor/flask_editor/static/css/components/buttons.css +217 -0
  252. scitex/canvas/editor/flask_editor/static/css/components/context-menu.css +93 -0
  253. scitex/canvas/editor/flask_editor/static/css/components/dropdown.css +57 -0
  254. scitex/canvas/editor/flask_editor/static/css/components/forms.css +112 -0
  255. scitex/canvas/editor/flask_editor/static/css/components/modal.css +59 -0
  256. scitex/canvas/editor/flask_editor/static/css/components/sections.css +212 -0
  257. scitex/canvas/editor/flask_editor/static/css/features/canvas.css +176 -0
  258. scitex/canvas/editor/flask_editor/static/css/features/element-inspector.css +190 -0
  259. scitex/canvas/editor/flask_editor/static/css/features/loading.css +59 -0
  260. scitex/canvas/editor/flask_editor/static/css/features/overlay.css +45 -0
  261. scitex/canvas/editor/flask_editor/static/css/features/panel-grid.css +95 -0
  262. scitex/canvas/editor/flask_editor/static/css/features/selection.css +101 -0
  263. scitex/canvas/editor/flask_editor/static/css/features/statistics.css +138 -0
  264. scitex/canvas/editor/flask_editor/static/css/index.css +31 -0
  265. scitex/canvas/editor/flask_editor/static/css/layout/container.css +7 -0
  266. scitex/canvas/editor/flask_editor/static/css/layout/controls.css +56 -0
  267. scitex/canvas/editor/flask_editor/static/css/layout/preview.css +78 -0
  268. scitex/canvas/editor/flask_editor/static/js/alignment/axis.js +314 -0
  269. scitex/canvas/editor/flask_editor/static/js/alignment/basic.js +107 -0
  270. scitex/canvas/editor/flask_editor/static/js/alignment/distribute.js +54 -0
  271. scitex/canvas/editor/flask_editor/static/js/canvas/canvas.js +172 -0
  272. scitex/canvas/editor/flask_editor/static/js/canvas/dragging.js +258 -0
  273. scitex/canvas/editor/flask_editor/static/js/canvas/resize.js +48 -0
  274. scitex/canvas/editor/flask_editor/static/js/canvas/selection.js +71 -0
  275. scitex/canvas/editor/flask_editor/static/js/core/api.js +287 -0
  276. scitex/canvas/editor/flask_editor/static/js/core/state.js +143 -0
  277. scitex/canvas/editor/flask_editor/static/js/core/utils.js +245 -0
  278. scitex/canvas/editor/flask_editor/static/js/dev/element-inspector.js +992 -0
  279. scitex/canvas/editor/flask_editor/static/js/editor/bbox.js +339 -0
  280. scitex/canvas/editor/flask_editor/static/js/editor/element-drag.js +286 -0
  281. scitex/canvas/editor/flask_editor/static/js/editor/overlay.js +371 -0
  282. scitex/canvas/editor/flask_editor/static/js/editor/preview.js +293 -0
  283. scitex/canvas/editor/flask_editor/static/js/main.js +426 -0
  284. scitex/canvas/editor/flask_editor/static/js/shortcuts/context-menu.js +152 -0
  285. scitex/canvas/editor/flask_editor/static/js/shortcuts/keyboard.js +265 -0
  286. scitex/canvas/editor/flask_editor/static/js/ui/controls.js +184 -0
  287. scitex/canvas/editor/flask_editor/static/js/ui/download.js +57 -0
  288. scitex/canvas/editor/flask_editor/static/js/ui/help.js +100 -0
  289. scitex/canvas/editor/flask_editor/static/js/ui/theme.js +34 -0
  290. scitex/canvas/editor/flask_editor/templates/__init__.py +123 -0
  291. scitex/canvas/editor/flask_editor/templates/_html.py +852 -0
  292. scitex/canvas/editor/flask_editor/templates/_scripts.py +4933 -0
  293. scitex/canvas/editor/flask_editor/templates/_styles.py +1658 -0
  294. scitex/canvas/io/__init__.py +105 -0
  295. scitex/canvas/io/_bundle.py +1121 -0
  296. scitex/canvas/io/_canvas.py +230 -0
  297. scitex/canvas/io/_data.py +208 -0
  298. scitex/canvas/io/_directory.py +205 -0
  299. scitex/canvas/io/_export.py +463 -0
  300. scitex/canvas/io/_load.py +168 -0
  301. scitex/canvas/io/_panel.py +432 -0
  302. scitex/canvas/io/_save.py +131 -0
  303. scitex/canvas/mcp_server.py +151 -0
  304. scitex/canvas/model/__init__.py +118 -0
  305. scitex/canvas/model/_annotations.py +116 -0
  306. scitex/canvas/model/_axes.py +153 -0
  307. scitex/canvas/model/_figure.py +139 -0
  308. scitex/canvas/model/_guides.py +105 -0
  309. scitex/canvas/model/_plot.py +124 -0
  310. scitex/canvas/model/_plot_types.py +682 -0
  311. scitex/canvas/model/_styles.py +246 -0
  312. scitex/canvas/utils/__init__.py +75 -0
  313. scitex/canvas/utils/_defaults.py +334 -0
  314. scitex/canvas/utils/_validate.py +198 -0
  315. scitex/capture/README.md +284 -0
  316. scitex/capture/TODO.md +40 -0
  317. scitex/capture/__init__.py +109 -0
  318. scitex/capture/__main__.py +24 -0
  319. scitex/capture/_mcp/__init__.py +11 -0
  320. scitex/capture/_mcp/handlers.py +438 -0
  321. scitex/capture/_mcp/tool_schemas.py +270 -0
  322. scitex/capture/capture.py +820 -0
  323. scitex/capture/cli.py +208 -0
  324. scitex/capture/gif.py +340 -0
  325. scitex/capture/grid.py +487 -0
  326. scitex/capture/mcp_server.py +977 -0
  327. scitex/capture/powershell/capture_all_desktops.ps1 +79 -0
  328. scitex/capture/powershell/capture_all_monitors.ps1 +63 -0
  329. scitex/capture/powershell/capture_single_monitor.ps1 +77 -0
  330. scitex/capture/powershell/capture_url.ps1 +170 -0
  331. scitex/capture/powershell/capture_window_by_handle.ps1 +129 -0
  332. scitex/capture/powershell/detect_monitors_and_desktops.ps1 +143 -0
  333. scitex/capture/powershell/enumerate_virtual_desktops.ps1 +60 -0
  334. scitex/capture/session.py +71 -0
  335. scitex/capture/utils.py +670 -0
  336. scitex/cli/__init__.py +64 -0
  337. scitex/cli/audio.py +331 -0
  338. scitex/cli/browser.py +310 -0
  339. scitex/cli/capture.py +325 -0
  340. scitex/cli/cloud.py +764 -0
  341. scitex/cli/config.py +224 -0
  342. scitex/cli/convert.py +425 -0
  343. scitex/cli/main.py +235 -0
  344. scitex/cli/mcp.py +336 -0
  345. scitex/cli/repro.py +251 -0
  346. scitex/cli/resource.py +258 -0
  347. scitex/cli/scholar/__init__.py +89 -0
  348. scitex/cli/scholar/_fetch.py +347 -0
  349. scitex/cli/scholar/_jobs.py +306 -0
  350. scitex/cli/scholar/_library.py +152 -0
  351. scitex/cli/scholar/_utils.py +35 -0
  352. scitex/cli/security.py +103 -0
  353. scitex/cli/stats.py +343 -0
  354. scitex/cli/template.py +254 -0
  355. scitex/cli/tex.py +304 -0
  356. scitex/cli/web.py +405 -0
  357. scitex/cli/writer.py +329 -0
  358. scitex/cloud/__init__.py +134 -0
  359. scitex/cloud/_matplotlib_hook.py +146 -0
  360. scitex/compat/__init__.py +74 -0
  361. scitex/config/README.md +313 -0
  362. scitex/config/_PriorityConfig.py +292 -0
  363. scitex/config/_ScitexConfig.py +319 -0
  364. scitex/config/__init__.py +56 -0
  365. scitex/config/_paths.py +325 -0
  366. scitex/config/default.yaml +112 -0
  367. scitex/context/__init__.py +9 -0
  368. scitex/context/_suppress_output.py +39 -0
  369. scitex/cv/__init__.py +87 -0
  370. scitex/cv/_draw.py +236 -0
  371. scitex/cv/_filters.py +217 -0
  372. scitex/cv/_io.py +157 -0
  373. scitex/cv/_transform.py +184 -0
  374. scitex/datetime/__init__.py +46 -0
  375. scitex/datetime/_linspace.py +100 -0
  376. scitex/datetime/_normalize_timestamp.py +306 -0
  377. scitex/db/README.md +281 -0
  378. scitex/db/_BaseMixins/_BaseBackupMixin.py +32 -0
  379. scitex/db/_BaseMixins/_BaseBatchMixin.py +33 -0
  380. scitex/db/_BaseMixins/_BaseBlobMixin.py +83 -0
  381. scitex/db/_BaseMixins/_BaseConnectionMixin.py +43 -0
  382. scitex/db/_BaseMixins/_BaseImportExportMixin.py +37 -0
  383. scitex/db/_BaseMixins/_BaseIndexMixin.py +31 -0
  384. scitex/db/_BaseMixins/_BaseMaintenanceMixin.py +31 -0
  385. scitex/db/_BaseMixins/_BaseQueryMixin.py +54 -0
  386. scitex/db/_BaseMixins/_BaseRowMixin.py +34 -0
  387. scitex/db/_BaseMixins/_BaseSchemaMixin.py +44 -0
  388. scitex/db/_BaseMixins/_BaseTableMixin.py +68 -0
  389. scitex/db/_BaseMixins/_BaseTransactionMixin.py +50 -0
  390. scitex/db/_BaseMixins/__init__.py +30 -0
  391. scitex/db/__init__.py +49 -0
  392. scitex/db/__main__.py +62 -0
  393. scitex/db/_check_health.py +365 -0
  394. scitex/db/_delete_duplicates.py +39 -0
  395. scitex/db/_inspect.py +368 -0
  396. scitex/db/_inspect_optimized.py +305 -0
  397. scitex/db/_postgresql/_PostgreSQL.py +125 -0
  398. scitex/db/_postgresql/_PostgreSQLMixins/_BackupMixin.py +166 -0
  399. scitex/db/_postgresql/_PostgreSQLMixins/_BatchMixin.py +82 -0
  400. scitex/db/_postgresql/_PostgreSQLMixins/_BlobMixin.py +233 -0
  401. scitex/db/_postgresql/_PostgreSQLMixins/_ConnectionMixin.py +90 -0
  402. scitex/db/_postgresql/_PostgreSQLMixins/_ImportExportMixin.py +57 -0
  403. scitex/db/_postgresql/_PostgreSQLMixins/_IndexMixin.py +64 -0
  404. scitex/db/_postgresql/_PostgreSQLMixins/_MaintenanceMixin.py +172 -0
  405. scitex/db/_postgresql/_PostgreSQLMixins/_QueryMixin.py +108 -0
  406. scitex/db/_postgresql/_PostgreSQLMixins/_RowMixin.py +77 -0
  407. scitex/db/_postgresql/_PostgreSQLMixins/_SchemaMixin.py +126 -0
  408. scitex/db/_postgresql/_PostgreSQLMixins/_TableMixin.py +176 -0
  409. scitex/db/_postgresql/_PostgreSQLMixins/_TransactionMixin.py +55 -0
  410. scitex/db/_postgresql/_PostgreSQLMixins/__init__.py +34 -0
  411. scitex/db/_postgresql/__init__.py +6 -0
  412. scitex/db/_sqlite3/README.md +191 -0
  413. scitex/db/_sqlite3/README_v01.md +126 -0
  414. scitex/db/_sqlite3/_SQLite3.py +208 -0
  415. scitex/db/_sqlite3/_SQLite3Mixins/_ArrayMixin.py +580 -0
  416. scitex/db/_sqlite3/_SQLite3Mixins/_ArrayMixin_v01-need-_hash-col.py +522 -0
  417. scitex/db/_sqlite3/_SQLite3Mixins/_BatchMixin.py +245 -0
  418. scitex/db/_sqlite3/_SQLite3Mixins/_BlobMixin.py +281 -0
  419. scitex/db/_sqlite3/_SQLite3Mixins/_ColumnMixin.py +555 -0
  420. scitex/db/_sqlite3/_SQLite3Mixins/_ConnectionMixin.py +122 -0
  421. scitex/db/_sqlite3/_SQLite3Mixins/_GitMixin.py +555 -0
  422. scitex/db/_sqlite3/_SQLite3Mixins/_ImportExportMixin.py +78 -0
  423. scitex/db/_sqlite3/_SQLite3Mixins/_IndexMixin.py +34 -0
  424. scitex/db/_sqlite3/_SQLite3Mixins/_MaintenanceMixin.py +177 -0
  425. scitex/db/_sqlite3/_SQLite3Mixins/_QueryMixin.py +109 -0
  426. scitex/db/_sqlite3/_SQLite3Mixins/_RowMixin.py +115 -0
  427. scitex/db/_sqlite3/_SQLite3Mixins/_TableMixin.py +236 -0
  428. scitex/db/_sqlite3/_SQLite3Mixins/_TransactionMixin.py +71 -0
  429. scitex/db/_sqlite3/_SQLite3Mixins/__init__.py +43 -0
  430. scitex/db/_sqlite3/__init__.py +7 -0
  431. scitex/db/_sqlite3/_delete_duplicates.py +285 -0
  432. scitex/decorators/BLUEPRINT.md +101 -0
  433. scitex/decorators/README.md +179 -0
  434. scitex/decorators/__init__.py +90 -0
  435. scitex/decorators/_auto_order.py +172 -0
  436. scitex/decorators/_batch_fn.py +134 -0
  437. scitex/decorators/_cache_disk.py +39 -0
  438. scitex/decorators/_cache_disk_async.py +50 -0
  439. scitex/decorators/_cache_mem.py +12 -0
  440. scitex/decorators/_combined.py +104 -0
  441. scitex/decorators/_converters.py +303 -0
  442. scitex/decorators/_deprecated.py +201 -0
  443. scitex/decorators/_not_implemented.py +30 -0
  444. scitex/decorators/_numpy_fn.py +101 -0
  445. scitex/decorators/_pandas_fn.py +138 -0
  446. scitex/decorators/_preserve_doc.py +19 -0
  447. scitex/decorators/_signal_fn.py +111 -0
  448. scitex/decorators/_timeout.py +55 -0
  449. scitex/decorators/_torch_fn.py +155 -0
  450. scitex/decorators/_wrap.py +39 -0
  451. scitex/decorators/_xarray_fn.py +108 -0
  452. scitex/dev/__init__.py +20 -0
  453. scitex/dev/_analyze_code_flow.py +282 -0
  454. scitex/dev/_pyproject.py +405 -0
  455. scitex/dev/_reload.py +59 -0
  456. scitex/dev/cv/__init__.py +60 -0
  457. scitex/dev/cv/_compose.py +330 -0
  458. scitex/dev/cv/_title_card.py +351 -0
  459. scitex/dev/plt/__init__.py +271 -0
  460. scitex/dev/plt/data/mpl/PLOTTING_FUNCTIONS.yaml +90 -0
  461. scitex/dev/plt/data/mpl/PLOTTING_SIGNATURES.yaml +1571 -0
  462. scitex/dev/plt/data/mpl/PLOTTING_SIGNATURES_DETAILED.yaml +6262 -0
  463. scitex/dev/plt/data/mpl/SIGNATURES_FLATTENED.yaml +1274 -0
  464. scitex/dev/plt/data/mpl/dir_ax.txt +459 -0
  465. scitex/dev/plt/demo_plotters/__init__.py +0 -0
  466. scitex/dev/plt/demo_plotters/plot_mpl_axhline.py +28 -0
  467. scitex/dev/plt/demo_plotters/plot_mpl_axhspan.py +28 -0
  468. scitex/dev/plt/demo_plotters/plot_mpl_axvline.py +28 -0
  469. scitex/dev/plt/demo_plotters/plot_mpl_axvspan.py +28 -0
  470. scitex/dev/plt/demo_plotters/plot_mpl_bar.py +29 -0
  471. scitex/dev/plt/demo_plotters/plot_mpl_barh.py +29 -0
  472. scitex/dev/plt/demo_plotters/plot_mpl_boxplot.py +28 -0
  473. scitex/dev/plt/demo_plotters/plot_mpl_contour.py +31 -0
  474. scitex/dev/plt/demo_plotters/plot_mpl_contourf.py +31 -0
  475. scitex/dev/plt/demo_plotters/plot_mpl_errorbar.py +30 -0
  476. scitex/dev/plt/demo_plotters/plot_mpl_eventplot.py +28 -0
  477. scitex/dev/plt/demo_plotters/plot_mpl_fill.py +30 -0
  478. scitex/dev/plt/demo_plotters/plot_mpl_fill_between.py +31 -0
  479. scitex/dev/plt/demo_plotters/plot_mpl_hexbin.py +28 -0
  480. scitex/dev/plt/demo_plotters/plot_mpl_hist.py +28 -0
  481. scitex/dev/plt/demo_plotters/plot_mpl_hist2d.py +28 -0
  482. scitex/dev/plt/demo_plotters/plot_mpl_imshow.py +29 -0
  483. scitex/dev/plt/demo_plotters/plot_mpl_pcolormesh.py +31 -0
  484. scitex/dev/plt/demo_plotters/plot_mpl_pie.py +29 -0
  485. scitex/dev/plt/demo_plotters/plot_mpl_plot.py +29 -0
  486. scitex/dev/plt/demo_plotters/plot_mpl_quiver.py +31 -0
  487. scitex/dev/plt/demo_plotters/plot_mpl_scatter.py +28 -0
  488. scitex/dev/plt/demo_plotters/plot_mpl_stackplot.py +31 -0
  489. scitex/dev/plt/demo_plotters/plot_mpl_stem.py +29 -0
  490. scitex/dev/plt/demo_plotters/plot_mpl_step.py +29 -0
  491. scitex/dev/plt/demo_plotters/plot_mpl_violinplot.py +28 -0
  492. scitex/dev/plt/demo_plotters/plot_sns_barplot.py +29 -0
  493. scitex/dev/plt/demo_plotters/plot_sns_boxplot.py +29 -0
  494. scitex/dev/plt/demo_plotters/plot_sns_heatmap.py +28 -0
  495. scitex/dev/plt/demo_plotters/plot_sns_histplot.py +29 -0
  496. scitex/dev/plt/demo_plotters/plot_sns_kdeplot.py +29 -0
  497. scitex/dev/plt/demo_plotters/plot_sns_lineplot.py +31 -0
  498. scitex/dev/plt/demo_plotters/plot_sns_scatterplot.py +29 -0
  499. scitex/dev/plt/demo_plotters/plot_sns_stripplot.py +29 -0
  500. scitex/dev/plt/demo_plotters/plot_sns_swarmplot.py +29 -0
  501. scitex/dev/plt/demo_plotters/plot_sns_violinplot.py +29 -0
  502. scitex/dev/plt/demo_plotters/plot_stx_bar.py +29 -0
  503. scitex/dev/plt/demo_plotters/plot_stx_barh.py +29 -0
  504. scitex/dev/plt/demo_plotters/plot_stx_box.py +28 -0
  505. scitex/dev/plt/demo_plotters/plot_stx_boxplot.py +28 -0
  506. scitex/dev/plt/demo_plotters/plot_stx_conf_mat.py +28 -0
  507. scitex/dev/plt/demo_plotters/plot_stx_contour.py +31 -0
  508. scitex/dev/plt/demo_plotters/plot_stx_ecdf.py +28 -0
  509. scitex/dev/plt/demo_plotters/plot_stx_errorbar.py +30 -0
  510. scitex/dev/plt/demo_plotters/plot_stx_fill_between.py +31 -0
  511. scitex/dev/plt/demo_plotters/plot_stx_fillv.py +28 -0
  512. scitex/dev/plt/demo_plotters/plot_stx_heatmap.py +28 -0
  513. scitex/dev/plt/demo_plotters/plot_stx_image.py +28 -0
  514. scitex/dev/plt/demo_plotters/plot_stx_imshow.py +28 -0
  515. scitex/dev/plt/demo_plotters/plot_stx_joyplot.py +28 -0
  516. scitex/dev/plt/demo_plotters/plot_stx_kde.py +28 -0
  517. scitex/dev/plt/demo_plotters/plot_stx_line.py +28 -0
  518. scitex/dev/plt/demo_plotters/plot_stx_mean_ci.py +28 -0
  519. scitex/dev/plt/demo_plotters/plot_stx_mean_std.py +28 -0
  520. scitex/dev/plt/demo_plotters/plot_stx_median_iqr.py +28 -0
  521. scitex/dev/plt/demo_plotters/plot_stx_raster.py +28 -0
  522. scitex/dev/plt/demo_plotters/plot_stx_rectangle.py +28 -0
  523. scitex/dev/plt/demo_plotters/plot_stx_scatter.py +29 -0
  524. scitex/dev/plt/demo_plotters/plot_stx_shaded_line.py +29 -0
  525. scitex/dev/plt/demo_plotters/plot_stx_violin.py +28 -0
  526. scitex/dev/plt/demo_plotters/plot_stx_violinplot.py +28 -0
  527. scitex/dev/plt/mpl/get_dir_ax.py +46 -0
  528. scitex/dev/plt/mpl/get_signatures.py +176 -0
  529. scitex/dev/plt/mpl/get_signatures_details.py +522 -0
  530. scitex/dev/plt/plot_mpl_axhline.py +28 -0
  531. scitex/dev/plt/plot_mpl_axhspan.py +28 -0
  532. scitex/dev/plt/plot_mpl_axvline.py +28 -0
  533. scitex/dev/plt/plot_mpl_axvspan.py +28 -0
  534. scitex/dev/plt/plot_mpl_bar.py +29 -0
  535. scitex/dev/plt/plot_mpl_barh.py +29 -0
  536. scitex/dev/plt/plot_mpl_boxplot.py +28 -0
  537. scitex/dev/plt/plot_mpl_contour.py +31 -0
  538. scitex/dev/plt/plot_mpl_contourf.py +31 -0
  539. scitex/dev/plt/plot_mpl_errorbar.py +30 -0
  540. scitex/dev/plt/plot_mpl_eventplot.py +28 -0
  541. scitex/dev/plt/plot_mpl_fill.py +30 -0
  542. scitex/dev/plt/plot_mpl_fill_between.py +31 -0
  543. scitex/dev/plt/plot_mpl_hexbin.py +28 -0
  544. scitex/dev/plt/plot_mpl_hist.py +28 -0
  545. scitex/dev/plt/plot_mpl_hist2d.py +28 -0
  546. scitex/dev/plt/plot_mpl_imshow.py +29 -0
  547. scitex/dev/plt/plot_mpl_pcolormesh.py +31 -0
  548. scitex/dev/plt/plot_mpl_pie.py +29 -0
  549. scitex/dev/plt/plot_mpl_plot.py +29 -0
  550. scitex/dev/plt/plot_mpl_quiver.py +31 -0
  551. scitex/dev/plt/plot_mpl_scatter.py +28 -0
  552. scitex/dev/plt/plot_mpl_stackplot.py +31 -0
  553. scitex/dev/plt/plot_mpl_stem.py +29 -0
  554. scitex/dev/plt/plot_mpl_step.py +29 -0
  555. scitex/dev/plt/plot_mpl_violinplot.py +28 -0
  556. scitex/dev/plt/plot_sns_barplot.py +29 -0
  557. scitex/dev/plt/plot_sns_boxplot.py +29 -0
  558. scitex/dev/plt/plot_sns_heatmap.py +28 -0
  559. scitex/dev/plt/plot_sns_histplot.py +29 -0
  560. scitex/dev/plt/plot_sns_kdeplot.py +29 -0
  561. scitex/dev/plt/plot_sns_lineplot.py +31 -0
  562. scitex/dev/plt/plot_sns_scatterplot.py +29 -0
  563. scitex/dev/plt/plot_sns_stripplot.py +29 -0
  564. scitex/dev/plt/plot_sns_swarmplot.py +29 -0
  565. scitex/dev/plt/plot_sns_violinplot.py +29 -0
  566. scitex/dev/plt/plot_stx_bar.py +29 -0
  567. scitex/dev/plt/plot_stx_barh.py +29 -0
  568. scitex/dev/plt/plot_stx_box.py +28 -0
  569. scitex/dev/plt/plot_stx_boxplot.py +28 -0
  570. scitex/dev/plt/plot_stx_conf_mat.py +28 -0
  571. scitex/dev/plt/plot_stx_contour.py +31 -0
  572. scitex/dev/plt/plot_stx_ecdf.py +28 -0
  573. scitex/dev/plt/plot_stx_errorbar.py +30 -0
  574. scitex/dev/plt/plot_stx_fill_between.py +31 -0
  575. scitex/dev/plt/plot_stx_fillv.py +28 -0
  576. scitex/dev/plt/plot_stx_heatmap.py +28 -0
  577. scitex/dev/plt/plot_stx_image.py +28 -0
  578. scitex/dev/plt/plot_stx_imshow.py +28 -0
  579. scitex/dev/plt/plot_stx_joyplot.py +28 -0
  580. scitex/dev/plt/plot_stx_kde.py +28 -0
  581. scitex/dev/plt/plot_stx_line.py +28 -0
  582. scitex/dev/plt/plot_stx_mean_ci.py +28 -0
  583. scitex/dev/plt/plot_stx_mean_std.py +28 -0
  584. scitex/dev/plt/plot_stx_median_iqr.py +28 -0
  585. scitex/dev/plt/plot_stx_raster.py +28 -0
  586. scitex/dev/plt/plot_stx_rectangle.py +28 -0
  587. scitex/dev/plt/plot_stx_scatter.py +29 -0
  588. scitex/dev/plt/plot_stx_shaded_line.py +29 -0
  589. scitex/dev/plt/plot_stx_violin.py +28 -0
  590. scitex/dev/plt/plot_stx_violinplot.py +28 -0
  591. scitex/diagram/README.md +197 -0
  592. scitex/diagram/__init__.py +48 -0
  593. scitex/diagram/_compile.py +312 -0
  594. scitex/diagram/_diagram.py +355 -0
  595. scitex/diagram/_mcp/__init__.py +4 -0
  596. scitex/diagram/_mcp/handlers.py +400 -0
  597. scitex/diagram/_mcp/tool_schemas.py +157 -0
  598. scitex/diagram/_presets.py +173 -0
  599. scitex/diagram/_schema.py +182 -0
  600. scitex/diagram/_split.py +278 -0
  601. scitex/diagram/mcp_server.py +151 -0
  602. scitex/dict/_DotDict.py +423 -0
  603. scitex/dict/__init__.py +21 -0
  604. scitex/dict/_flatten.py +20 -0
  605. scitex/dict/_listed_dict.py +42 -0
  606. scitex/dict/_pop_keys.py +30 -0
  607. scitex/dict/_replace.py +12 -0
  608. scitex/dict/_safe_merge.py +53 -0
  609. scitex/dict/_to_str.py +34 -0
  610. scitex/dsp/README.md +147 -0
  611. scitex/dsp/__init__.py +86 -0
  612. scitex/dsp/_crop.py +124 -0
  613. scitex/dsp/_demo_sig.py +377 -0
  614. scitex/dsp/_detect_ripples.py +214 -0
  615. scitex/dsp/_ensure_3d.py +18 -0
  616. scitex/dsp/_hilbert.py +78 -0
  617. scitex/dsp/_listen.py +702 -0
  618. scitex/dsp/_misc.py +30 -0
  619. scitex/dsp/_mne.py +43 -0
  620. scitex/dsp/_modulation_index.py +93 -0
  621. scitex/dsp/_pac.py +337 -0
  622. scitex/dsp/_psd.py +114 -0
  623. scitex/dsp/_resample.py +86 -0
  624. scitex/dsp/_time.py +39 -0
  625. scitex/dsp/_transform.py +81 -0
  626. scitex/dsp/_wavelet.py +212 -0
  627. scitex/dsp/add_noise.py +128 -0
  628. scitex/dsp/audio.md +22 -0
  629. scitex/dsp/example.py +262 -0
  630. scitex/dsp/filt.py +165 -0
  631. scitex/dsp/norm.py +33 -0
  632. scitex/dsp/params.py +51 -0
  633. scitex/dsp/reference.py +60 -0
  634. scitex/dsp/template.py +26 -0
  635. scitex/dsp/utils/__init__.py +19 -0
  636. scitex/dsp/utils/_differential_bandpass_filters.py +148 -0
  637. scitex/dsp/utils/_ensure_3d.py +18 -0
  638. scitex/dsp/utils/_ensure_even_len.py +10 -0
  639. scitex/dsp/utils/_zero_pad.py +62 -0
  640. scitex/dsp/utils/filter.py +408 -0
  641. scitex/dsp/utils/pac.py +177 -0
  642. scitex/dt/__init__.py +45 -0
  643. scitex/dt/_linspace.py +130 -0
  644. scitex/dt/_normalize_timestamp.py +435 -0
  645. scitex/errors.py +130 -0
  646. scitex/etc/__init__.py +15 -0
  647. scitex/etc/wait_key.py +34 -0
  648. scitex/gen/README.md +71 -0
  649. scitex/gen/_DimHandler.py +196 -0
  650. scitex/gen/_TimeStamper.py +244 -0
  651. scitex/gen/__init__.py +170 -0
  652. scitex/gen/_alternate_kwarg.py +13 -0
  653. scitex/gen/_cache.py +11 -0
  654. scitex/gen/_check_host.py +34 -0
  655. scitex/gen/_ci.py +12 -0
  656. scitex/gen/_deprecated_close.py +81 -0
  657. scitex/gen/_deprecated_start.py +28 -0
  658. scitex/gen/_detect_environment.py +155 -0
  659. scitex/gen/_detect_notebook_path.py +173 -0
  660. scitex/gen/_embed.py +82 -0
  661. scitex/gen/_get_notebook_path.py +276 -0
  662. scitex/gen/_inspect_module.py +256 -0
  663. scitex/gen/_is_ipython.py +12 -0
  664. scitex/gen/_less.py +48 -0
  665. scitex/gen/_list_packages.py +139 -0
  666. scitex/gen/_mat2py.py +88 -0
  667. scitex/gen/_norm.py +214 -0
  668. scitex/gen/_norm_cache.py +282 -0
  669. scitex/gen/_paste.py +18 -0
  670. scitex/gen/_print_config.py +81 -0
  671. scitex/gen/_shell.py +48 -0
  672. scitex/gen/_src.py +108 -0
  673. scitex/gen/_symlink.py +55 -0
  674. scitex/gen/_symlog.py +27 -0
  675. scitex/gen/_title2path.py +60 -0
  676. scitex/gen/_title_case.py +89 -0
  677. scitex/gen/_to_even.py +84 -0
  678. scitex/gen/_to_odd.py +34 -0
  679. scitex/gen/_to_rank.py +39 -0
  680. scitex/gen/_transpose.py +37 -0
  681. scitex/gen/_type.py +78 -0
  682. scitex/gen/_var_info.py +73 -0
  683. scitex/gen/_wrap.py +17 -0
  684. scitex/gen/_xml2dict.py +76 -0
  685. scitex/gen/misc.py +755 -0
  686. scitex/gen/path.py +0 -0
  687. scitex/gists/_SigMacro_processFigure_S.py +128 -0
  688. scitex/gists/_SigMacro_toBlue.py +172 -0
  689. scitex/gists/__init__.py +15 -0
  690. scitex/gists/guideline.md +42 -0
  691. scitex/git/README.md +121 -0
  692. scitex/git/__init__.py +61 -0
  693. scitex/git/_branch.py +185 -0
  694. scitex/git/_clone.py +167 -0
  695. scitex/git/_commit.py +170 -0
  696. scitex/git/_constants.py +17 -0
  697. scitex/git/_init.py +179 -0
  698. scitex/git/_remote.py +191 -0
  699. scitex/git/_result.py +70 -0
  700. scitex/git/_retry.py +113 -0
  701. scitex/git/_session.py +60 -0
  702. scitex/git/_types.py +136 -0
  703. scitex/git/_utils.py +28 -0
  704. scitex/git/_validation.py +117 -0
  705. scitex/git/_workflow.py +87 -0
  706. scitex/io/.gitkeep +0 -0
  707. scitex/io/README.md +376 -0
  708. scitex/io/__init__.py +116 -0
  709. scitex/io/_cache.py +101 -0
  710. scitex/io/_flush.py +27 -0
  711. scitex/io/_glob.py +109 -0
  712. scitex/io/_json2md.py +118 -0
  713. scitex/io/_load.py +453 -0
  714. scitex/io/_load_cache.py +303 -0
  715. scitex/io/_load_configs.py +175 -0
  716. scitex/io/_load_modules/_H5Explorer.py +352 -0
  717. scitex/io/_load_modules/_ZarrExplorer.py +114 -0
  718. scitex/io/_load_modules/__init__.py +41 -0
  719. scitex/io/_load_modules/_bibtex.py +206 -0
  720. scitex/io/_load_modules/_canvas.py +166 -0
  721. scitex/io/_load_modules/_catboost.py +71 -0
  722. scitex/io/_load_modules/_con.py +31 -0
  723. scitex/io/_load_modules/_docx.py +42 -0
  724. scitex/io/_load_modules/_eeg.py +125 -0
  725. scitex/io/_load_modules/_hdf5.py +73 -0
  726. scitex/io/_load_modules/_image.py +66 -0
  727. scitex/io/_load_modules/_joblib.py +19 -0
  728. scitex/io/_load_modules/_json.py +20 -0
  729. scitex/io/_load_modules/_markdown.py +103 -0
  730. scitex/io/_load_modules/_matlab.py +39 -0
  731. scitex/io/_load_modules/_numpy.py +39 -0
  732. scitex/io/_load_modules/_optuna.py +112 -0
  733. scitex/io/_load_modules/_pandas.py +75 -0
  734. scitex/io/_load_modules/_pdf.py +958 -0
  735. scitex/io/_load_modules/_pickle.py +24 -0
  736. scitex/io/_load_modules/_sqlite3.py +16 -0
  737. scitex/io/_load_modules/_torch.py +25 -0
  738. scitex/io/_load_modules/_txt.py +164 -0
  739. scitex/io/_load_modules/_xml.py +49 -0
  740. scitex/io/_load_modules/_yaml.py +24 -0
  741. scitex/io/_load_modules/_zarr.py +128 -0
  742. scitex/io/_metadata.py +47 -0
  743. scitex/io/_metadata_modules/__init__.py +46 -0
  744. scitex/io/_metadata_modules/_embed.py +70 -0
  745. scitex/io/_metadata_modules/_read.py +64 -0
  746. scitex/io/_metadata_modules/_utils.py +79 -0
  747. scitex/io/_metadata_modules/embed_metadata_jpeg.py +74 -0
  748. scitex/io/_metadata_modules/embed_metadata_pdf.py +53 -0
  749. scitex/io/_metadata_modules/embed_metadata_png.py +26 -0
  750. scitex/io/_metadata_modules/embed_metadata_svg.py +62 -0
  751. scitex/io/_metadata_modules/read_metadata_jpeg.py +57 -0
  752. scitex/io/_metadata_modules/read_metadata_pdf.py +51 -0
  753. scitex/io/_metadata_modules/read_metadata_png.py +39 -0
  754. scitex/io/_metadata_modules/read_metadata_svg.py +44 -0
  755. scitex/io/_mv_to_tmp.py +19 -0
  756. scitex/io/_path.py +286 -0
  757. scitex/io/_qr_utils.py +82 -0
  758. scitex/io/_reload.py +78 -0
  759. scitex/io/_save.py +497 -0
  760. scitex/io/_save_modules/__init__.py +130 -0
  761. scitex/io/_save_modules/_bibtex.py +199 -0
  762. scitex/io/_save_modules/_canvas.py +355 -0
  763. scitex/io/_save_modules/_catboost.py +22 -0
  764. scitex/io/_save_modules/_csv.py +95 -0
  765. scitex/io/_save_modules/_excel.py +210 -0
  766. scitex/io/_save_modules/_figure_utils.py +90 -0
  767. scitex/io/_save_modules/_hdf5.py +262 -0
  768. scitex/io/_save_modules/_html.py +48 -0
  769. scitex/io/_save_modules/_image.py +300 -0
  770. scitex/io/_save_modules/_image_csv.py +497 -0
  771. scitex/io/_save_modules/_joblib.py +25 -0
  772. scitex/io/_save_modules/_json.py +115 -0
  773. scitex/io/_save_modules/_legends.py +91 -0
  774. scitex/io/_save_modules/_listed_dfs_as_csv.py +56 -0
  775. scitex/io/_save_modules/_listed_scalars_as_csv.py +42 -0
  776. scitex/io/_save_modules/_matlab.py +24 -0
  777. scitex/io/_save_modules/_mp4.py +29 -0
  778. scitex/io/_save_modules/_numpy.py +55 -0
  779. scitex/io/_save_modules/_optuna_study_as_csv_and_pngs.py +49 -0
  780. scitex/io/_save_modules/_pickle.py +45 -0
  781. scitex/io/_save_modules/_plot_bundle.py +112 -0
  782. scitex/io/_save_modules/_plot_scitex.py +536 -0
  783. scitex/io/_save_modules/_plotly.py +27 -0
  784. scitex/io/_save_modules/_stx_bundle.py +104 -0
  785. scitex/io/_save_modules/_symlink.py +96 -0
  786. scitex/io/_save_modules/_tex.py +278 -0
  787. scitex/io/_save_modules/_text.py +23 -0
  788. scitex/io/_save_modules/_torch.py +35 -0
  789. scitex/io/_save_modules/_yaml.py +68 -0
  790. scitex/io/_save_modules/_zarr.py +206 -0
  791. scitex/io/bundle/README.md +221 -0
  792. scitex/io/bundle/_Bundle.py +759 -0
  793. scitex/io/bundle/__init__.py +142 -0
  794. scitex/io/bundle/_children.py +208 -0
  795. scitex/io/bundle/_conversion/__init__.py +15 -0
  796. scitex/io/bundle/_conversion/_bundle2dict.py +44 -0
  797. scitex/io/bundle/_conversion/_dict2bundle.py +50 -0
  798. scitex/io/bundle/_core.py +651 -0
  799. scitex/io/bundle/_dataclasses/_Axes.py +57 -0
  800. scitex/io/bundle/_dataclasses/_BBox.py +54 -0
  801. scitex/io/bundle/_dataclasses/_ColumnDef.py +72 -0
  802. scitex/io/bundle/_dataclasses/_DataFormat.py +40 -0
  803. scitex/io/bundle/_dataclasses/_DataInfo.py +135 -0
  804. scitex/io/bundle/_dataclasses/_DataSource.py +44 -0
  805. scitex/io/bundle/_dataclasses/_SizeMM.py +38 -0
  806. scitex/io/bundle/_dataclasses/_Spec.py +325 -0
  807. scitex/io/bundle/_dataclasses/_SpecRefs.py +45 -0
  808. scitex/io/bundle/_dataclasses/__init__.py +35 -0
  809. scitex/io/bundle/_extractors/__init__.py +32 -0
  810. scitex/io/bundle/_extractors/_extract_bar.py +131 -0
  811. scitex/io/bundle/_extractors/_extract_line.py +71 -0
  812. scitex/io/bundle/_extractors/_extract_scatter.py +79 -0
  813. scitex/io/bundle/_loader.py +134 -0
  814. scitex/io/bundle/_manifest.py +99 -0
  815. scitex/io/bundle/_mpl_helpers.py +480 -0
  816. scitex/io/bundle/_nested.py +726 -0
  817. scitex/io/bundle/_saver.py +268 -0
  818. scitex/io/bundle/_storage.py +200 -0
  819. scitex/io/bundle/_types.py +99 -0
  820. scitex/io/bundle/_validation.py +436 -0
  821. scitex/io/bundle/_zip.py +477 -0
  822. scitex/io/bundle/kinds/__init__.py +45 -0
  823. scitex/io/bundle/kinds/_figure/__init__.py +19 -0
  824. scitex/io/bundle/kinds/_figure/_composite.py +345 -0
  825. scitex/io/bundle/kinds/_plot/__init__.py +25 -0
  826. scitex/io/bundle/kinds/_plot/_backend/__init__.py +53 -0
  827. scitex/io/bundle/kinds/_plot/_backend/_export.py +165 -0
  828. scitex/io/bundle/kinds/_plot/_backend/_parser.py +188 -0
  829. scitex/io/bundle/kinds/_plot/_backend/_render.py +538 -0
  830. scitex/io/bundle/kinds/_plot/_dataclasses/_ChannelEncoding.py +46 -0
  831. scitex/io/bundle/kinds/_plot/_dataclasses/_Encoding.py +82 -0
  832. scitex/io/bundle/kinds/_plot/_dataclasses/_Theme.py +441 -0
  833. scitex/io/bundle/kinds/_plot/_dataclasses/_TraceEncoding.py +52 -0
  834. scitex/io/bundle/kinds/_plot/_dataclasses/__init__.py +47 -0
  835. scitex/io/bundle/kinds/_plot/_models/_Annotations.py +115 -0
  836. scitex/io/bundle/kinds/_plot/_models/_Axes.py +152 -0
  837. scitex/io/bundle/kinds/_plot/_models/_Figure.py +138 -0
  838. scitex/io/bundle/kinds/_plot/_models/_Guides.py +104 -0
  839. scitex/io/bundle/kinds/_plot/_models/_Plot.py +123 -0
  840. scitex/io/bundle/kinds/_plot/_models/_Styles.py +245 -0
  841. scitex/io/bundle/kinds/_plot/_models/__init__.py +80 -0
  842. scitex/io/bundle/kinds/_plot/_models/_plot_types/__init__.py +156 -0
  843. scitex/io/bundle/kinds/_plot/_models/_plot_types/_bar.py +43 -0
  844. scitex/io/bundle/kinds/_plot/_models/_plot_types/_box.py +38 -0
  845. scitex/io/bundle/kinds/_plot/_models/_plot_types/_distribution.py +36 -0
  846. scitex/io/bundle/kinds/_plot/_models/_plot_types/_errorbar.py +60 -0
  847. scitex/io/bundle/kinds/_plot/_models/_plot_types/_histogram.py +30 -0
  848. scitex/io/bundle/kinds/_plot/_models/_plot_types/_image.py +61 -0
  849. scitex/io/bundle/kinds/_plot/_models/_plot_types/_line.py +57 -0
  850. scitex/io/bundle/kinds/_plot/_models/_plot_types/_scatter.py +30 -0
  851. scitex/io/bundle/kinds/_plot/_models/_plot_types/_seaborn.py +121 -0
  852. scitex/io/bundle/kinds/_plot/_models/_plot_types/_violin.py +36 -0
  853. scitex/io/bundle/kinds/_plot/_utils/__init__.py +129 -0
  854. scitex/io/bundle/kinds/_plot/_utils/_auto_layout.py +127 -0
  855. scitex/io/bundle/kinds/_plot/_utils/_calc_bounds.py +111 -0
  856. scitex/io/bundle/kinds/_plot/_utils/_const_sizes.py +48 -0
  857. scitex/io/bundle/kinds/_plot/_utils/_convert_coords.py +77 -0
  858. scitex/io/bundle/kinds/_plot/_utils/_get_template.py +178 -0
  859. scitex/io/bundle/kinds/_plot/_utils/_normalize.py +73 -0
  860. scitex/io/bundle/kinds/_plot/_utils/_plot_layout.py +397 -0
  861. scitex/io/bundle/kinds/_plot/_utils/_validate.py +197 -0
  862. scitex/io/bundle/kinds/_shape/__init__.py +141 -0
  863. scitex/io/bundle/kinds/_stats/__init__.py +56 -0
  864. scitex/io/bundle/kinds/_stats/_dataclasses/_Stats.py +423 -0
  865. scitex/io/bundle/kinds/_stats/_dataclasses/__init__.py +48 -0
  866. scitex/io/bundle/kinds/_table/__init__.py +72 -0
  867. scitex/io/bundle/kinds/_table/_latex/__init__.py +93 -0
  868. scitex/io/bundle/kinds/_table/_latex/_editor/__init__.py +11 -0
  869. scitex/io/bundle/kinds/_table/_latex/_editor/_app.py +725 -0
  870. scitex/io/bundle/kinds/_table/_latex/_export.py +279 -0
  871. scitex/io/bundle/kinds/_table/_latex/_figure_exporter.py +153 -0
  872. scitex/io/bundle/kinds/_table/_latex/_stats_formatter.py +274 -0
  873. scitex/io/bundle/kinds/_table/_latex/_table_exporter.py +362 -0
  874. scitex/io/bundle/kinds/_table/_latex/_utils.py +369 -0
  875. scitex/io/bundle/kinds/_table/_latex/_validator.py +445 -0
  876. scitex/io/bundle/kinds/_text/__init__.py +77 -0
  877. scitex/io/bundle/schemas/__init__.py +30 -0
  878. scitex/io/bundle/schemas/data_info.schema.json +75 -0
  879. scitex/io/bundle/schemas/encoding.schema.json +90 -0
  880. scitex/io/bundle/schemas/node.schema.json +145 -0
  881. scitex/io/bundle/schemas/render_manifest.schema.json +62 -0
  882. scitex/io/bundle/schemas/stats.schema.json +132 -0
  883. scitex/io/bundle/schemas/theme.schema.json +141 -0
  884. scitex/io/utils/__init__.py +21 -0
  885. scitex/io/utils/h5_to_zarr.py +636 -0
  886. scitex/linalg/__init__.py +17 -0
  887. scitex/linalg/_distance.py +63 -0
  888. scitex/linalg/_geometric_median.py +69 -0
  889. scitex/linalg/_misc.py +73 -0
  890. scitex/logging/README.md +374 -0
  891. scitex/logging/_Tee.py +195 -0
  892. scitex/logging/__init__.py +216 -0
  893. scitex/logging/_config.py +161 -0
  894. scitex/logging/_context.py +105 -0
  895. scitex/logging/_errors.py +508 -0
  896. scitex/logging/_formatters.py +158 -0
  897. scitex/logging/_handlers.py +63 -0
  898. scitex/logging/_levels.py +27 -0
  899. scitex/logging/_logger.py +261 -0
  900. scitex/logging/_print_capture.py +95 -0
  901. scitex/logging/_warnings.py +261 -0
  902. scitex/mcp_server.py +91 -0
  903. scitex/ml/__init__.py +30 -0
  904. scitex/msword/__init__.py +255 -0
  905. scitex/msword/profiles.py +357 -0
  906. scitex/msword/reader.py +753 -0
  907. scitex/msword/utils.py +289 -0
  908. scitex/msword/writer.py +362 -0
  909. scitex/nn/_AxiswiseDropout.py +27 -0
  910. scitex/nn/_BNet.py +130 -0
  911. scitex/nn/_BNet_Res.py +164 -0
  912. scitex/nn/_ChannelGainChanger.py +44 -0
  913. scitex/nn/_DropoutChannels.py +50 -0
  914. scitex/nn/_Filters.py +493 -0
  915. scitex/nn/_FreqGainChanger.py +110 -0
  916. scitex/nn/_GaussianFilter.py +48 -0
  917. scitex/nn/_Hilbert.py +111 -0
  918. scitex/nn/_MNet_1000.py +157 -0
  919. scitex/nn/_ModulationIndex.py +223 -0
  920. scitex/nn/_PAC.py +415 -0
  921. scitex/nn/_PSD.py +39 -0
  922. scitex/nn/_ResNet1D.py +120 -0
  923. scitex/nn/_SpatialAttention.py +25 -0
  924. scitex/nn/_Spectrogram.py +170 -0
  925. scitex/nn/_SwapChannels.py +50 -0
  926. scitex/nn/_TransposeLayer.py +19 -0
  927. scitex/nn/_Wavelet.py +183 -0
  928. scitex/nn/__init__.py +75 -0
  929. scitex/os/__init__.py +8 -0
  930. scitex/os/_mv.py +50 -0
  931. scitex/parallel/__init__.py +8 -0
  932. scitex/parallel/_run.py +152 -0
  933. scitex/path/README.md +77 -0
  934. scitex/path/__init__.py +51 -0
  935. scitex/path/_clean.py +58 -0
  936. scitex/path/_find.py +90 -0
  937. scitex/path/_get_module_path.py +53 -0
  938. scitex/path/_get_spath.py +13 -0
  939. scitex/path/_getsize.py +32 -0
  940. scitex/path/_increment_version.py +80 -0
  941. scitex/path/_mk_spath.py +48 -0
  942. scitex/path/_path.py +12 -0
  943. scitex/path/_split.py +35 -0
  944. scitex/path/_symlink.py +343 -0
  945. scitex/path/_this_path.py +33 -0
  946. scitex/path/_version.py +102 -0
  947. scitex/pd/__init__.py +46 -0
  948. scitex/pd/_find_indi.py +126 -0
  949. scitex/pd/_find_pval.py +113 -0
  950. scitex/pd/_force_df.py +154 -0
  951. scitex/pd/_from_xyz.py +71 -0
  952. scitex/pd/_get_unique.py +100 -0
  953. scitex/pd/_ignore_SettingWithCopyWarning.py +34 -0
  954. scitex/pd/_melt_cols.py +81 -0
  955. scitex/pd/_merge_columns.py +221 -0
  956. scitex/pd/_mv.py +63 -0
  957. scitex/pd/_replace.py +62 -0
  958. scitex/pd/_round.py +97 -0
  959. scitex/pd/_slice.py +63 -0
  960. scitex/pd/_sort.py +91 -0
  961. scitex/pd/_to_numeric.py +53 -0
  962. scitex/pd/_to_xy.py +58 -0
  963. scitex/pd/_to_xyz.py +110 -0
  964. scitex/plt/README.md +1675 -0
  965. scitex/plt/__init__.py +803 -0
  966. scitex/plt/_figrecipe.py +236 -0
  967. scitex/plt/_figrecipe_integration.py +121 -0
  968. scitex/plt/_mcp/__init__.py +4 -0
  969. scitex/plt/_mcp/_handlers_annotation.py +102 -0
  970. scitex/plt/_mcp/_handlers_figure.py +195 -0
  971. scitex/plt/_mcp/_handlers_plot.py +252 -0
  972. scitex/plt/_mcp/_handlers_style.py +219 -0
  973. scitex/plt/_mcp/handlers.py +74 -0
  974. scitex/plt/_mcp/tool_schemas.py +497 -0
  975. scitex/plt/_subplots/_AxesWrapper.py +197 -0
  976. scitex/plt/_subplots/_AxisWrapper.py +374 -0
  977. scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/__init__.py +36 -0
  978. scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/_labels.py +264 -0
  979. scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/_metadata.py +213 -0
  980. scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/_visual.py +128 -0
  981. scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/__init__.py +60 -0
  982. scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_base.py +34 -0
  983. scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_scientific.py +593 -0
  984. scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_statistical.py +654 -0
  985. scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_stx_aliases.py +527 -0
  986. scitex/plt/_subplots/_AxisWrapperMixins/_RawMatplotlibMixin.py +321 -0
  987. scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/__init__.py +33 -0
  988. scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_base.py +152 -0
  989. scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_wrappers.py +600 -0
  990. scitex/plt/_subplots/_AxisWrapperMixins/_TrackingMixin.py +197 -0
  991. scitex/plt/_subplots/_AxisWrapperMixins/_UnitAwareMixin.py +440 -0
  992. scitex/plt/_subplots/_AxisWrapperMixins/__init__.py +91 -0
  993. scitex/plt/_subplots/_FigWrapper.py +475 -0
  994. scitex/plt/_subplots/_SubplotsWrapper.py +329 -0
  995. scitex/plt/_subplots/__init__.py +121 -0
  996. scitex/plt/_subplots/_export_as_csv.py +467 -0
  997. scitex/plt/_subplots/_export_as_csv_formatters/__init__.py +83 -0
  998. scitex/plt/_subplots/_export_as_csv_formatters/_format_annotate.py +72 -0
  999. scitex/plt/_subplots/_export_as_csv_formatters/_format_bar.py +138 -0
  1000. scitex/plt/_subplots/_export_as_csv_formatters/_format_barh.py +58 -0
  1001. scitex/plt/_subplots/_export_as_csv_formatters/_format_boxplot.py +74 -0
  1002. scitex/plt/_subplots/_export_as_csv_formatters/_format_contour.py +50 -0
  1003. scitex/plt/_subplots/_export_as_csv_formatters/_format_contourf.py +62 -0
  1004. scitex/plt/_subplots/_export_as_csv_formatters/_format_errorbar.py +88 -0
  1005. scitex/plt/_subplots/_export_as_csv_formatters/_format_eventplot.py +83 -0
  1006. scitex/plt/_subplots/_export_as_csv_formatters/_format_fill.py +46 -0
  1007. scitex/plt/_subplots/_export_as_csv_formatters/_format_fill_between.py +46 -0
  1008. scitex/plt/_subplots/_export_as_csv_formatters/_format_hexbin.py +51 -0
  1009. scitex/plt/_subplots/_export_as_csv_formatters/_format_hist.py +91 -0
  1010. scitex/plt/_subplots/_export_as_csv_formatters/_format_hist2d.py +51 -0
  1011. scitex/plt/_subplots/_export_as_csv_formatters/_format_imshow.py +85 -0
  1012. scitex/plt/_subplots/_export_as_csv_formatters/_format_imshow2d.py +48 -0
  1013. scitex/plt/_subplots/_export_as_csv_formatters/_format_matshow.py +50 -0
  1014. scitex/plt/_subplots/_export_as_csv_formatters/_format_pcolormesh.py +66 -0
  1015. scitex/plt/_subplots/_export_as_csv_formatters/_format_pie.py +51 -0
  1016. scitex/plt/_subplots/_export_as_csv_formatters/_format_plot.py +222 -0
  1017. scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_box.py +98 -0
  1018. scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_imshow.py +53 -0
  1019. scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_kde.py +59 -0
  1020. scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_scatter.py +44 -0
  1021. scitex/plt/_subplots/_export_as_csv_formatters/_format_quiver.py +61 -0
  1022. scitex/plt/_subplots/_export_as_csv_formatters/_format_scatter.py +43 -0
  1023. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_barplot.py +66 -0
  1024. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_boxplot.py +112 -0
  1025. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_heatmap.py +80 -0
  1026. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_histplot.py +95 -0
  1027. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_jointplot.py +76 -0
  1028. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_kdeplot.py +91 -0
  1029. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_lineplot.py +64 -0
  1030. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_pairplot.py +57 -0
  1031. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_scatterplot.py +72 -0
  1032. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_stripplot.py +80 -0
  1033. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_swarmplot.py +80 -0
  1034. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_violinplot.py +134 -0
  1035. scitex/plt/_subplots/_export_as_csv_formatters/_format_stackplot.py +62 -0
  1036. scitex/plt/_subplots/_export_as_csv_formatters/_format_stem.py +51 -0
  1037. scitex/plt/_subplots/_export_as_csv_formatters/_format_step.py +51 -0
  1038. scitex/plt/_subplots/_export_as_csv_formatters/_format_streamplot.py +56 -0
  1039. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_bar.py +84 -0
  1040. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_barh.py +85 -0
  1041. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_conf_mat.py +75 -0
  1042. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_contour.py +54 -0
  1043. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_ecdf.py +55 -0
  1044. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_errorbar.py +120 -0
  1045. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_fillv.py +72 -0
  1046. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_heatmap.py +74 -0
  1047. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_image.py +109 -0
  1048. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_imshow.py +63 -0
  1049. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_joyplot.py +84 -0
  1050. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_line.py +55 -0
  1051. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_mean_ci.py +50 -0
  1052. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_mean_std.py +50 -0
  1053. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_median_iqr.py +50 -0
  1054. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_raster.py +52 -0
  1055. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_rectangle.py +129 -0
  1056. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_scatter.py +51 -0
  1057. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_scatter_hist.py +92 -0
  1058. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_shaded_line.py +72 -0
  1059. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_violin.py +115 -0
  1060. scitex/plt/_subplots/_export_as_csv_formatters/_format_text.py +61 -0
  1061. scitex/plt/_subplots/_export_as_csv_formatters/_format_violin.py +65 -0
  1062. scitex/plt/_subplots/_export_as_csv_formatters/_format_violinplot.py +82 -0
  1063. scitex/plt/_subplots/_export_as_csv_formatters/test_formatters.py +208 -0
  1064. scitex/plt/_subplots/_export_as_csv_formatters/verify_formatters.py +359 -0
  1065. scitex/plt/_subplots/_fonts.py +71 -0
  1066. scitex/plt/_subplots/_mm_layout.py +282 -0
  1067. scitex/plt/_tpl.py +29 -0
  1068. scitex/plt/ax/__init__.py +123 -0
  1069. scitex/plt/ax/_plot/__init__.py +79 -0
  1070. scitex/plt/ax/_plot/_add_fitted_line.py +152 -0
  1071. scitex/plt/ax/_plot/_plot_circular_hist.py +126 -0
  1072. scitex/plt/ax/_plot/_plot_cube.py +57 -0
  1073. scitex/plt/ax/_plot/_plot_statistical_shaded_line.py +255 -0
  1074. scitex/plt/ax/_plot/_stx_conf_mat.py +139 -0
  1075. scitex/plt/ax/_plot/_stx_ecdf.py +113 -0
  1076. scitex/plt/ax/_plot/_stx_fillv.py +57 -0
  1077. scitex/plt/ax/_plot/_stx_heatmap.py +369 -0
  1078. scitex/plt/ax/_plot/_stx_image.py +96 -0
  1079. scitex/plt/ax/_plot/_stx_joyplot.py +135 -0
  1080. scitex/plt/ax/_plot/_stx_raster.py +199 -0
  1081. scitex/plt/ax/_plot/_stx_rectangle.py +70 -0
  1082. scitex/plt/ax/_plot/_stx_scatter_hist.py +133 -0
  1083. scitex/plt/ax/_plot/_stx_shaded_line.py +219 -0
  1084. scitex/plt/ax/_plot/_stx_violin.py +352 -0
  1085. scitex/plt/ax/_style/__init__.py +42 -0
  1086. scitex/plt/ax/_style/_add_marginal_ax.py +46 -0
  1087. scitex/plt/ax/_style/_add_panel.py +92 -0
  1088. scitex/plt/ax/_style/_auto_scale_axis.py +199 -0
  1089. scitex/plt/ax/_style/_extend.py +66 -0
  1090. scitex/plt/ax/_style/_force_aspect.py +39 -0
  1091. scitex/plt/ax/_style/_format_label.py +23 -0
  1092. scitex/plt/ax/_style/_format_units.py +103 -0
  1093. scitex/plt/ax/_style/_hide_spines.py +86 -0
  1094. scitex/plt/ax/_style/_map_ticks.py +184 -0
  1095. scitex/plt/ax/_style/_rotate_labels.py +320 -0
  1096. scitex/plt/ax/_style/_rotate_labels_v01.py +258 -0
  1097. scitex/plt/ax/_style/_sci_note.py +279 -0
  1098. scitex/plt/ax/_style/_set_log_scale.py +334 -0
  1099. scitex/plt/ax/_style/_set_meta.py +291 -0
  1100. scitex/plt/ax/_style/_set_n_ticks.py +37 -0
  1101. scitex/plt/ax/_style/_set_size.py +16 -0
  1102. scitex/plt/ax/_style/_set_supxyt.py +133 -0
  1103. scitex/plt/ax/_style/_set_ticks.py +276 -0
  1104. scitex/plt/ax/_style/_set_xyt.py +130 -0
  1105. scitex/plt/ax/_style/_share_axes.py +274 -0
  1106. scitex/plt/ax/_style/_shift.py +139 -0
  1107. scitex/plt/ax/_style/_show_spines.py +334 -0
  1108. scitex/plt/ax/_style/_style_barplot.py +69 -0
  1109. scitex/plt/ax/_style/_style_boxplot.py +155 -0
  1110. scitex/plt/ax/_style/_style_errorbar.py +82 -0
  1111. scitex/plt/ax/_style/_style_scatter.py +82 -0
  1112. scitex/plt/ax/_style/_style_suptitles.py +76 -0
  1113. scitex/plt/ax/_style/_style_violinplot.py +115 -0
  1114. scitex/plt/color/_PARAMS.py +70 -0
  1115. scitex/plt/color/__init__.py +84 -0
  1116. scitex/plt/color/_add_hue_col.py +42 -0
  1117. scitex/plt/color/_colors.py +204 -0
  1118. scitex/plt/color/_get_colors_from_conf_matap.py +136 -0
  1119. scitex/plt/color/_interpolate.py +29 -0
  1120. scitex/plt/color/_vizualize_colors.py +53 -0
  1121. scitex/plt/docs/FIGURE_ARCHITECTURE.md +315 -0
  1122. scitex/plt/gallery/README.md +75 -0
  1123. scitex/plt/gallery/__init__.py +126 -0
  1124. scitex/plt/gallery/_generate.py +586 -0
  1125. scitex/plt/gallery/_plots.py +594 -0
  1126. scitex/plt/gallery/_registry.py +153 -0
  1127. scitex/plt/gallery.md +170 -0
  1128. scitex/plt/io/__init__.py +51 -0
  1129. scitex/plt/io/_bundle.py +537 -0
  1130. scitex/plt/io/_layered_bundle.py +1478 -0
  1131. scitex/plt/mcp_server.py +231 -0
  1132. scitex/plt/styles/SCITEX_STYLE.yaml +130 -0
  1133. scitex/plt/styles/__init__.py +71 -0
  1134. scitex/plt/styles/_plot_defaults.py +210 -0
  1135. scitex/plt/styles/_plot_postprocess.py +487 -0
  1136. scitex/plt/styles/_postprocess_helpers.py +158 -0
  1137. scitex/plt/styles/_style_loader.py +268 -0
  1138. scitex/plt/styles/presets.py +311 -0
  1139. scitex/plt/utils/__init__.py +114 -0
  1140. scitex/plt/utils/_calc_bacc_from_conf_mat.py +46 -0
  1141. scitex/plt/utils/_calc_nice_ticks.py +101 -0
  1142. scitex/plt/utils/_close.py +73 -0
  1143. scitex/plt/utils/_collect_figure_metadata.py +3384 -0
  1144. scitex/plt/utils/_colorbar.py +156 -0
  1145. scitex/plt/utils/_configure_mpl.py +427 -0
  1146. scitex/plt/utils/_crop.py +297 -0
  1147. scitex/plt/utils/_csv_column_naming.py +474 -0
  1148. scitex/plt/utils/_dimension_viewer.py +425 -0
  1149. scitex/plt/utils/_figure_from_axes_mm.py +407 -0
  1150. scitex/plt/utils/_figure_mm.py +472 -0
  1151. scitex/plt/utils/_get_actual_font.py +57 -0
  1152. scitex/plt/utils/_histogram_utils.py +134 -0
  1153. scitex/plt/utils/_hitmap.py +1643 -0
  1154. scitex/plt/utils/_im2grid.py +70 -0
  1155. scitex/plt/utils/_is_valid_axis.py +82 -0
  1156. scitex/plt/utils/_mk_colorbar.py +65 -0
  1157. scitex/plt/utils/_mk_patches.py +26 -0
  1158. scitex/plt/utils/_scientific_captions.py +701 -0
  1159. scitex/plt/utils/_scitex_config.py +224 -0
  1160. scitex/plt/utils/_units.py +123 -0
  1161. scitex/plt/utils/metadata/__init__.py +61 -0
  1162. scitex/plt/utils/metadata/_artist_extraction.py +119 -0
  1163. scitex/plt/utils/metadata/_axes_metadata.py +93 -0
  1164. scitex/plt/utils/metadata/_collection_artists.py +292 -0
  1165. scitex/plt/utils/metadata/_core.py +207 -0
  1166. scitex/plt/utils/metadata/_csv_column_extraction.py +186 -0
  1167. scitex/plt/utils/metadata/_csv_hash.py +115 -0
  1168. scitex/plt/utils/metadata/_csv_verification.py +95 -0
  1169. scitex/plt/utils/metadata/_data_linkage.py +263 -0
  1170. scitex/plt/utils/metadata/_dimensions.py +242 -0
  1171. scitex/plt/utils/metadata/_editable_export.py +405 -0
  1172. scitex/plt/utils/metadata/_figure_metadata.py +58 -0
  1173. scitex/plt/utils/metadata/_geometry_extraction.py +570 -0
  1174. scitex/plt/utils/metadata/_image_text_artists.py +168 -0
  1175. scitex/plt/utils/metadata/_label_parsing.py +82 -0
  1176. scitex/plt/utils/metadata/_legend_extraction.py +120 -0
  1177. scitex/plt/utils/metadata/_line_artists.py +367 -0
  1178. scitex/plt/utils/metadata/_line_semantic_handling.py +173 -0
  1179. scitex/plt/utils/metadata/_patch_artists.py +211 -0
  1180. scitex/plt/utils/metadata/_plot_content.py +26 -0
  1181. scitex/plt/utils/metadata/_plot_type_detection.py +184 -0
  1182. scitex/plt/utils/metadata/_precision.py +134 -0
  1183. scitex/plt/utils/metadata/_precision_config.py +68 -0
  1184. scitex/plt/utils/metadata/_precision_sections.py +211 -0
  1185. scitex/plt/utils/metadata/_recipe_extraction.py +267 -0
  1186. scitex/plt/utils/metadata/_style_parsing.py +174 -0
  1187. scitex/repro/README_RandomStateManager.md +211 -0
  1188. scitex/repro/_RandomStateManager.py +684 -0
  1189. scitex/repro/__init__.py +66 -0
  1190. scitex/repro/_gen_ID.py +137 -0
  1191. scitex/repro/_gen_timestamp.py +114 -0
  1192. scitex/repro/_hash_array.py +139 -0
  1193. scitex/reproduce/__init__.py +26 -0
  1194. scitex/resource/README.md +81 -0
  1195. scitex/resource/__init__.py +31 -0
  1196. scitex/resource/_get_processor_usages.py +283 -0
  1197. scitex/resource/_get_specs/info.yaml +122 -0
  1198. scitex/resource/_get_specs/specs.yaml +129 -0
  1199. scitex/resource/_get_specs.py +280 -0
  1200. scitex/resource/_log_processor_usages.py +192 -0
  1201. scitex/resource/_utils/__init__.py +31 -0
  1202. scitex/resource/_utils/_get_env_info.py +469 -0
  1203. scitex/resource/limit_ram.py +33 -0
  1204. scitex/rng/__init__.py +32 -0
  1205. scitex/schema/README.md +178 -0
  1206. scitex/schema/__init__.py +222 -0
  1207. scitex/schema/_canvas.py +444 -0
  1208. scitex/schema/_encoding.py +273 -0
  1209. scitex/schema/_figure_elements.py +406 -0
  1210. scitex/schema/_plot.py +1015 -0
  1211. scitex/schema/_stats.py +762 -0
  1212. scitex/schema/_theme.py +360 -0
  1213. scitex/schema/_validation.py +492 -0
  1214. scitex/scholar/.gitignore +23 -0
  1215. scitex/scholar/README.md +122 -0
  1216. scitex/scholar/__init__.py +354 -0
  1217. scitex/scholar/__main__.py +339 -0
  1218. scitex/scholar/_mcp/__init__.py +4 -0
  1219. scitex/scholar/_mcp/all_handlers.py +74 -0
  1220. scitex/scholar/_mcp/handlers.py +1478 -0
  1221. scitex/scholar/_mcp/job_handlers.py +172 -0
  1222. scitex/scholar/_mcp/job_tool_schemas.py +166 -0
  1223. scitex/scholar/_mcp/tool_schemas.py +505 -0
  1224. scitex/scholar/auth/README.md +83 -0
  1225. scitex/scholar/auth/ScholarAuthManager.py +285 -0
  1226. scitex/scholar/auth/__init__.py +21 -0
  1227. scitex/scholar/auth/core/AuthenticationGateway.py +459 -0
  1228. scitex/scholar/auth/core/BrowserAuthenticator.py +468 -0
  1229. scitex/scholar/auth/core/StrategyResolver.py +292 -0
  1230. scitex/scholar/auth/core/__init__.py +20 -0
  1231. scitex/scholar/auth/gateway/_OpenURLLinkFinder.py +110 -0
  1232. scitex/scholar/auth/gateway/_OpenURLResolver.py +201 -0
  1233. scitex/scholar/auth/gateway/__init__.py +38 -0
  1234. scitex/scholar/auth/gateway/_resolve_functions.py +101 -0
  1235. scitex/scholar/auth/providers/BaseAuthenticator.py +167 -0
  1236. scitex/scholar/auth/providers/EZProxyAuthenticator.py +477 -0
  1237. scitex/scholar/auth/providers/OpenAthensAuthenticator.py +436 -0
  1238. scitex/scholar/auth/providers/ShibbolethAuthenticator.py +700 -0
  1239. scitex/scholar/auth/providers/__init__.py +18 -0
  1240. scitex/scholar/auth/providers/_notifications.py +417 -0
  1241. scitex/scholar/auth/session/AuthCacheManager.py +186 -0
  1242. scitex/scholar/auth/session/SessionManager.py +157 -0
  1243. scitex/scholar/auth/session/__init__.py +11 -0
  1244. scitex/scholar/auth/sso/BaseSSOAutomator.py +386 -0
  1245. scitex/scholar/auth/sso/OpenAthensSSOAutomator.py +349 -0
  1246. scitex/scholar/auth/sso/README.md +204 -0
  1247. scitex/scholar/auth/sso/SSOAutomator.py +173 -0
  1248. scitex/scholar/auth/sso/UniversityOfMelbourneSSOAutomator.py +363 -0
  1249. scitex/scholar/auth/sso/__init__.py +15 -0
  1250. scitex/scholar/browser/README.md +60 -0
  1251. scitex/scholar/browser/ScholarBrowserManager.py +680 -0
  1252. scitex/scholar/browser/__init__.py +39 -0
  1253. scitex/scholar/browser/utils/README.md +96 -0
  1254. scitex/scholar/browser/utils/__init__.py +13 -0
  1255. scitex/scholar/browser/utils/click_and_wait.py +204 -0
  1256. scitex/scholar/browser/utils/close_unwanted_pages.py +137 -0
  1257. scitex/scholar/browser/utils/wait_redirects.py +707 -0
  1258. scitex/scholar/citation_graph/README.md +117 -0
  1259. scitex/scholar/citation_graph/__init__.py +28 -0
  1260. scitex/scholar/citation_graph/builder.py +210 -0
  1261. scitex/scholar/citation_graph/database.py +239 -0
  1262. scitex/scholar/citation_graph/example.py +91 -0
  1263. scitex/scholar/citation_graph/models.py +80 -0
  1264. scitex/scholar/cli/README.md +368 -0
  1265. scitex/scholar/cli/_CentralArgumentParser.py +137 -0
  1266. scitex/scholar/cli/_argument_groups.py +222 -0
  1267. scitex/scholar/cli/_doi_operations.py +56 -0
  1268. scitex/scholar/cli/_url_utils.py +219 -0
  1269. scitex/scholar/cli/chrome.py +101 -0
  1270. scitex/scholar/cli/download_pdf.py +236 -0
  1271. scitex/scholar/cli/handlers/__init__.py +23 -0
  1272. scitex/scholar/cli/handlers/bibtex_handler.py +165 -0
  1273. scitex/scholar/cli/handlers/doi_handler.py +63 -0
  1274. scitex/scholar/cli/handlers/project_handler.py +261 -0
  1275. scitex/scholar/cli/open_browser.py +297 -0
  1276. scitex/scholar/cli/open_browser_auto.py +368 -0
  1277. scitex/scholar/cli/open_browser_monitored.py +407 -0
  1278. scitex/scholar/config/PublisherRules.py +132 -0
  1279. scitex/scholar/config/README.md +31 -0
  1280. scitex/scholar/config/ScholarConfig.py +143 -0
  1281. scitex/scholar/config/__init__.py +18 -0
  1282. scitex/scholar/config/_categories/README.md +149 -0
  1283. scitex/scholar/config/_categories/api_keys.yaml +19 -0
  1284. scitex/scholar/config/_categories/auth_gateway.yaml +149 -0
  1285. scitex/scholar/config/_categories/authentication.yaml +24 -0
  1286. scitex/scholar/config/_categories/browser.yaml +24 -0
  1287. scitex/scholar/config/_categories/core.yaml +24 -0
  1288. scitex/scholar/config/_categories/doi_publishers.yaml +53 -0
  1289. scitex/scholar/config/_categories/notifications.yaml +11 -0
  1290. scitex/scholar/config/_categories/pdf_download.yaml +15 -0
  1291. scitex/scholar/config/_categories/publishers_pdf_rules.yaml +184 -0
  1292. scitex/scholar/config/_categories/search_engines.yaml +22 -0
  1293. scitex/scholar/config/_categories/url_finder_openurl.yaml +87 -0
  1294. scitex/scholar/config/_categories/url_finder_selectors.yaml +147 -0
  1295. scitex/scholar/config/core/_CascadeConfig.py +156 -0
  1296. scitex/scholar/config/core/_PathManager.py +535 -0
  1297. scitex/scholar/config/default.yaml +727 -0
  1298. scitex/scholar/core/Paper.py +708 -0
  1299. scitex/scholar/core/Papers.py +711 -0
  1300. scitex/scholar/core/README.md +109 -0
  1301. scitex/scholar/core/Scholar.py +1857 -0
  1302. scitex/scholar/core/__init__.py +53 -0
  1303. scitex/scholar/core/journal_normalizer.py +538 -0
  1304. scitex/scholar/core/oa_cache.py +289 -0
  1305. scitex/scholar/core/open_access.py +466 -0
  1306. scitex/scholar/data/.gitkeep +0 -0
  1307. scitex/scholar/data/README.md +44 -0
  1308. scitex/scholar/data/bib_files/bibliography.bib +1952 -0
  1309. scitex/scholar/data/bib_files/neurovista.bib +277 -0
  1310. scitex/scholar/data/bib_files/neurovista_enriched.bib +441 -0
  1311. scitex/scholar/data/bib_files/neurovista_enriched_enriched.bib +441 -0
  1312. scitex/scholar/data/bib_files/neurovista_processed.bib +338 -0
  1313. scitex/scholar/data/bib_files/openaccess.bib +89 -0
  1314. scitex/scholar/data/bib_files/pac-seizure_prediction_enriched.bib +2178 -0
  1315. scitex/scholar/data/bib_files/pac.bib +698 -0
  1316. scitex/scholar/data/bib_files/pac_enriched.bib +1061 -0
  1317. scitex/scholar/data/bib_files/pac_processed.bib +0 -0
  1318. scitex/scholar/data/bib_files/pac_titles.txt +75 -0
  1319. scitex/scholar/data/bib_files/paywalled.bib +98 -0
  1320. scitex/scholar/data/bib_files/related-papers-by-coauthors.bib +58 -0
  1321. scitex/scholar/data/bib_files/related-papers-by-coauthors_enriched.bib +87 -0
  1322. scitex/scholar/data/bib_files/seizure_prediction.bib +694 -0
  1323. scitex/scholar/data/bib_files/seizure_prediction_processed.bib +0 -0
  1324. scitex/scholar/data/bib_files/test_complete_enriched.bib +437 -0
  1325. scitex/scholar/data/bib_files/test_final_enriched.bib +437 -0
  1326. scitex/scholar/data/bib_files/test_seizure.bib +46 -0
  1327. scitex/scholar/data/impact_factor/JCR_IF_2022.xlsx +0 -0
  1328. scitex/scholar/data/impact_factor/JCR_IF_2024.db +0 -0
  1329. scitex/scholar/data/impact_factor/JCR_IF_2024.xlsx +0 -0
  1330. scitex/scholar/data/impact_factor/JCR_IF_2024_v01.db +0 -0
  1331. scitex/scholar/data/impact_factor.db +0 -0
  1332. scitex/scholar/docs/00_SUMMARY.md +127 -0
  1333. scitex/scholar/docs/01_STORAGE_ARCHITECTURE.md +118 -0
  1334. scitex/scholar/docs/02_PIPELINE_ORGANIZATION.md +251 -0
  1335. scitex/scholar/docs/03_PIPELINE_IMPLEMENTATION_PLAN.md +425 -0
  1336. scitex/scholar/docs/04_PIPELINE_IMPLEMENTATION_COMPLETE.md +324 -0
  1337. scitex/scholar/docs/05_DETAILS_FOR_DEVELOPERS.md +464 -0
  1338. scitex/scholar/docs/06_IMPACT_FACTOR_CITATION_INTEGRATION.md +295 -0
  1339. scitex/scholar/docs/bibfile.bib +66 -0
  1340. scitex/scholar/docs/from_agents/OPENATHENS_SECURITY.md +101 -0
  1341. scitex/scholar/docs/from_agents/feature-requests/scholar-openathens-authentication.md +107 -0
  1342. scitex/scholar/docs/from_agents/scholar_enhancements_summary.md +66 -0
  1343. scitex/scholar/docs/from_user/crawl4ai.md +15 -0
  1344. scitex/scholar/docs/from_user/medium_article_on_logined_page_for_zenrows_1.md +363 -0
  1345. scitex/scholar/docs/from_user/medium_article_on_logined_page_for_zenrows_2.md +1496 -0
  1346. scitex/scholar/docs/from_user/papers.bib +698 -0
  1347. scitex/scholar/docs/from_user/renamed-async_functions.md +970 -0
  1348. scitex/scholar/docs/from_user/suggestions.md +97 -0
  1349. scitex/scholar/docs/sample_data/PAYWALLED.json +84 -0
  1350. scitex/scholar/docs/sample_data/openaccess.json +74 -0
  1351. scitex/scholar/docs/sample_data/papers-enriched.bib +675 -0
  1352. scitex/scholar/docs/sample_data/papers-partial-enriched.bib +601 -0
  1353. scitex/scholar/docs/sample_data/test_papers.bib +18 -0
  1354. scitex/scholar/docs/template.py +123 -0
  1355. scitex/scholar/docs/zenrows_official/FAQ.md +434 -0
  1356. scitex/scholar/docs/zenrows_official/captcha_integration.md +698 -0
  1357. scitex/scholar/docs/zenrows_official/final_url.md +204 -0
  1358. scitex/scholar/docs/zenrows_official/with_playwright.md +336 -0
  1359. scitex/scholar/examples/00_config.py +270 -0
  1360. scitex/scholar/examples/01_auth.py +165 -0
  1361. scitex/scholar/examples/02_browser.py +169 -0
  1362. scitex/scholar/examples/03_01-engine.py +486 -0
  1363. scitex/scholar/examples/03_02-engine-for-bibtex.py +166 -0
  1364. scitex/scholar/examples/04_01-url.py +218 -0
  1365. scitex/scholar/examples/04_02-url-for-bibtex.py +283 -0
  1366. scitex/scholar/examples/04_02-url-for-dois.py +305 -0
  1367. scitex/scholar/examples/05_download_pdf.py +225 -0
  1368. scitex/scholar/examples/06_find_and_download.py +217 -0
  1369. scitex/scholar/examples/06_parse_bibtex.py +256 -0
  1370. scitex/scholar/examples/07_storage_integration.py +253 -0
  1371. scitex/scholar/examples/99_fullpipeline-for-bibtex.py +349 -0
  1372. scitex/scholar/examples/99_fullpipeline-for-one-entry.py +293 -0
  1373. scitex/scholar/examples/99_maintenance.py +134 -0
  1374. scitex/scholar/examples/README.md +18 -0
  1375. scitex/scholar/examples/SUGGESTIONS.md +865 -0
  1376. scitex/scholar/examples/dev.py +38 -0
  1377. scitex/scholar/examples/zotero_integration.py +246 -0
  1378. scitex/scholar/impact_factor/ImpactFactorEngine.py +202 -0
  1379. scitex/scholar/impact_factor/README.md +135 -0
  1380. scitex/scholar/impact_factor/__init__.py +20 -0
  1381. scitex/scholar/impact_factor/estimation/ImpactFactorEstimationEngine.py +0 -0
  1382. scitex/scholar/impact_factor/estimation/README.md +351 -0
  1383. scitex/scholar/impact_factor/estimation/__init__.py +39 -0
  1384. scitex/scholar/impact_factor/estimation/build_database.py +0 -0
  1385. scitex/scholar/impact_factor/estimation/core/__init__.py +24 -0
  1386. scitex/scholar/impact_factor/estimation/core/cache_manager.py +535 -0
  1387. scitex/scholar/impact_factor/estimation/core/calculator.py +389 -0
  1388. scitex/scholar/impact_factor/estimation/core/journal_matcher.py +473 -0
  1389. scitex/scholar/impact_factor/jcr/ImpactFactorJCREngine.py +323 -0
  1390. scitex/scholar/impact_factor/jcr/README.md +84 -0
  1391. scitex/scholar/impact_factor/jcr/TODO.md +28 -0
  1392. scitex/scholar/impact_factor/jcr/build_database.py +268 -0
  1393. scitex/scholar/integration/MIGRATION.md +138 -0
  1394. scitex/scholar/integration/README.md +464 -0
  1395. scitex/scholar/integration/__init__.py +59 -0
  1396. scitex/scholar/integration/base.py +494 -0
  1397. scitex/scholar/integration/mendeley/__init__.py +22 -0
  1398. scitex/scholar/integration/mendeley/exporter.py +164 -0
  1399. scitex/scholar/integration/mendeley/importer.py +236 -0
  1400. scitex/scholar/integration/mendeley/linker.py +79 -0
  1401. scitex/scholar/integration/mendeley/mapper.py +215 -0
  1402. scitex/scholar/integration/zotero/README.md +336 -0
  1403. scitex/scholar/integration/zotero/__init__.py +27 -0
  1404. scitex/scholar/integration/zotero/__main__.py +247 -0
  1405. scitex/scholar/integration/zotero/exporter.py +355 -0
  1406. scitex/scholar/integration/zotero/importer.py +374 -0
  1407. scitex/scholar/integration/zotero/linker.py +411 -0
  1408. scitex/scholar/integration/zotero/mapper.py +291 -0
  1409. scitex/scholar/jobs/_Job.py +266 -0
  1410. scitex/scholar/jobs/_JobManager.py +458 -0
  1411. scitex/scholar/jobs/__init__.py +74 -0
  1412. scitex/scholar/jobs/_errors.py +346 -0
  1413. scitex/scholar/jobs/_executors.py +373 -0
  1414. scitex/scholar/mcp_server.py +365 -0
  1415. scitex/scholar/metadata_engines/.combined-SemanticScholarSource/_SemanticScholarSource.py +289 -0
  1416. scitex/scholar/metadata_engines/.combined-SemanticScholarSource/_SemanticScholarSourceEnhanced.py +229 -0
  1417. scitex/scholar/metadata_engines/README.md +42 -0
  1418. scitex/scholar/metadata_engines/ScholarEngine.py +574 -0
  1419. scitex/scholar/metadata_engines/__init__.py +28 -0
  1420. scitex/scholar/metadata_engines/individual/ArXivEngine.py +378 -0
  1421. scitex/scholar/metadata_engines/individual/CrossRefEngine.py +264 -0
  1422. scitex/scholar/metadata_engines/individual/CrossRefLocalEngine.py +303 -0
  1423. scitex/scholar/metadata_engines/individual/OpenAlexEngine.py +340 -0
  1424. scitex/scholar/metadata_engines/individual/PubMedEngine.py +312 -0
  1425. scitex/scholar/metadata_engines/individual/SemanticScholarEngine.py +431 -0
  1426. scitex/scholar/metadata_engines/individual/URLDOIEngine.py +399 -0
  1427. scitex/scholar/metadata_engines/individual/_BaseDOIEngine.py +458 -0
  1428. scitex/scholar/metadata_engines/individual/__init__.py +7 -0
  1429. scitex/scholar/metadata_engines/utils/_PubMedConverter.py +465 -0
  1430. scitex/scholar/metadata_engines/utils/_URLDOIExtractor.py +296 -0
  1431. scitex/scholar/metadata_engines/utils/__init__.py +32 -0
  1432. scitex/scholar/metadata_engines/utils/_metadata2bibtex.py +106 -0
  1433. scitex/scholar/metadata_engines/utils/_standardize_metadata.py +375 -0
  1434. scitex/scholar/pdf_download/README.md +51 -0
  1435. scitex/scholar/pdf_download/ScholarPDFDownloader.py +704 -0
  1436. scitex/scholar/pdf_download/__init__.py +5 -0
  1437. scitex/scholar/pdf_download/strategies/__init__.py +44 -0
  1438. scitex/scholar/pdf_download/strategies/chrome_pdf_viewer.py +368 -0
  1439. scitex/scholar/pdf_download/strategies/direct_download.py +127 -0
  1440. scitex/scholar/pdf_download/strategies/manual_download_fallback.py +167 -0
  1441. scitex/scholar/pdf_download/strategies/manual_download_utils.py +989 -0
  1442. scitex/scholar/pdf_download/strategies/open_access_download.py +204 -0
  1443. scitex/scholar/pdf_download/strategies/response_body.py +196 -0
  1444. scitex/scholar/pipelines/README.md +172 -0
  1445. scitex/scholar/pipelines/ScholarPipelineBibTeX.py +355 -0
  1446. scitex/scholar/pipelines/ScholarPipelineMetadataParallel.py +269 -0
  1447. scitex/scholar/pipelines/ScholarPipelineMetadataSingle.py +403 -0
  1448. scitex/scholar/pipelines/ScholarPipelineParallel.py +462 -0
  1449. scitex/scholar/pipelines/ScholarPipelineSearchParallel.py +873 -0
  1450. scitex/scholar/pipelines/ScholarPipelineSearchSingle.py +553 -0
  1451. scitex/scholar/pipelines/ScholarPipelineSingle.py +188 -0
  1452. scitex/scholar/pipelines/SearchQueryParser.py +270 -0
  1453. scitex/scholar/pipelines/__init__.py +55 -0
  1454. scitex/scholar/pipelines/_single_steps.py +430 -0
  1455. scitex/scholar/search_engines/ScholarSearchEngine.py +278 -0
  1456. scitex/scholar/search_engines/_BaseSearchEngine.py +180 -0
  1457. scitex/scholar/search_engines/__init__.py +0 -0
  1458. scitex/scholar/search_engines/individual/ArXivSearchEngine.py +194 -0
  1459. scitex/scholar/search_engines/individual/CrossRefSearchEngine.py +159 -0
  1460. scitex/scholar/search_engines/individual/OpenAlexSearchEngine.py +177 -0
  1461. scitex/scholar/search_engines/individual/PubMedSearchEngine.py +186 -0
  1462. scitex/scholar/search_engines/individual/SemanticScholarSearchEngine.py +158 -0
  1463. scitex/scholar/search_engines/individual/__init__.py +0 -0
  1464. scitex/scholar/storage/BibTeXHandler.py +1151 -0
  1465. scitex/scholar/storage/PaperIO.py +500 -0
  1466. scitex/scholar/storage/ScholarLibrary.py +203 -0
  1467. scitex/scholar/storage/_BibTeXValidator.py +571 -0
  1468. scitex/scholar/storage/_DeduplicationManager.py +575 -0
  1469. scitex/scholar/storage/_LibraryCacheManager.py +697 -0
  1470. scitex/scholar/storage/_LibraryManager.py +1725 -0
  1471. scitex/scholar/storage/__init__.py +42 -0
  1472. scitex/scholar/url_finder/README.md +74 -0
  1473. scitex/scholar/url_finder/ScholarURLFinder.py +364 -0
  1474. scitex/scholar/url_finder/__init__.py +7 -0
  1475. scitex/scholar/url_finder/docs/CORE_URL_TYPES.md +187 -0
  1476. scitex/scholar/url_finder/docs/URL_SCHEMA.md +223 -0
  1477. scitex/scholar/url_finder/strategies/__init__.py +33 -0
  1478. scitex/scholar/url_finder/strategies/find_pdf_urls_by_direct_links.py +261 -0
  1479. scitex/scholar/url_finder/strategies/find_pdf_urls_by_dropdown.py +67 -0
  1480. scitex/scholar/url_finder/strategies/find_pdf_urls_by_href.py +200 -0
  1481. scitex/scholar/url_finder/strategies/find_pdf_urls_by_navigation.py +254 -0
  1482. scitex/scholar/url_finder/strategies/find_pdf_urls_by_publisher_patterns.py +158 -0
  1483. scitex/scholar/url_finder/strategies/find_pdf_urls_by_zotero_translators.py +164 -0
  1484. scitex/scholar/url_finder/strategies/find_supplementary_urls_by_href.py +70 -0
  1485. scitex/scholar/url_finder/translators/__init__.py +31 -0
  1486. scitex/scholar/url_finder/translators/core/__init__.py +6 -0
  1487. scitex/scholar/url_finder/translators/core/base.py +50 -0
  1488. scitex/scholar/url_finder/translators/core/patterns.py +40 -0
  1489. scitex/scholar/url_finder/translators/core/registry.py +372 -0
  1490. scitex/scholar/url_finder/translators/individual/BOFiP_Impots.py +200 -0
  1491. scitex/scholar/url_finder/translators/individual/Baidu_Scholar.py +229 -0
  1492. scitex/scholar/url_finder/translators/individual/Bangkok_Post.py +152 -0
  1493. scitex/scholar/url_finder/translators/individual/Baruch_Foundation.py +197 -0
  1494. scitex/scholar/url_finder/translators/individual/Beobachter.py +193 -0
  1495. scitex/scholar/url_finder/translators/individual/Bezneng_Gajit.py +203 -0
  1496. scitex/scholar/url_finder/translators/individual/BibLaTeX.py +329 -0
  1497. scitex/scholar/url_finder/translators/individual/BibTeX.py +330 -0
  1498. scitex/scholar/url_finder/translators/individual/Biblio_com.py +222 -0
  1499. scitex/scholar/url_finder/translators/individual/Bibliontology_RDF.py +228 -0
  1500. scitex/scholar/url_finder/translators/individual/CLASE.py +23 -0
  1501. scitex/scholar/url_finder/translators/individual/COBISS.py +23 -0
  1502. scitex/scholar/url_finder/translators/individual/COinS.py +23 -0
  1503. scitex/scholar/url_finder/translators/individual/CQ_Press.py +23 -0
  1504. scitex/scholar/url_finder/translators/individual/CROSBI.py +23 -0
  1505. scitex/scholar/url_finder/translators/individual/CSL_JSON.py +23 -0
  1506. scitex/scholar/url_finder/translators/individual/CSV.py +23 -0
  1507. scitex/scholar/url_finder/translators/individual/CalMatters.py +23 -0
  1508. scitex/scholar/url_finder/translators/individual/Calisphere.py +23 -0
  1509. scitex/scholar/url_finder/translators/individual/Camara_Brasileira_do_Livro_ISBN.py +290 -0
  1510. scitex/scholar/url_finder/translators/individual/CanLII.py +151 -0
  1511. scitex/scholar/url_finder/translators/individual/Canada_com.py +127 -0
  1512. scitex/scholar/url_finder/translators/individual/Canadian_Letters_and_Images.py +152 -0
  1513. scitex/scholar/url_finder/translators/individual/Canadiana_ca.py +195 -0
  1514. scitex/scholar/url_finder/translators/individual/Cascadilla_Proceedings_Project.py +202 -0
  1515. scitex/scholar/url_finder/translators/individual/Central_and_Eastern_European_Online_Library_Journals.py +172 -0
  1516. scitex/scholar/url_finder/translators/individual/Champlain_Society_Collection.py +172 -0
  1517. scitex/scholar/url_finder/translators/individual/Chicago_Journal_of_Theoretical_Computer_Science.py +210 -0
  1518. scitex/scholar/url_finder/translators/individual/Christian_Science_Monitor.py +118 -0
  1519. scitex/scholar/url_finder/translators/individual/Columbia_University_Press.py +232 -0
  1520. scitex/scholar/url_finder/translators/individual/Common_Place.py +245 -0
  1521. scitex/scholar/url_finder/translators/individual/Cornell_LII.py +165 -0
  1522. scitex/scholar/url_finder/translators/individual/Cornell_University_Press.py +156 -0
  1523. scitex/scholar/url_finder/translators/individual/CourtListener.py +173 -0
  1524. scitex/scholar/url_finder/translators/individual/DAI_Zenon.py +160 -0
  1525. scitex/scholar/url_finder/translators/individual/FULL_DOWNLOAD_LOG.txt +2 -0
  1526. scitex/scholar/url_finder/translators/individual/FULL_DOWNLOAD_LOG_colored.txt +1 -0
  1527. scitex/scholar/url_finder/translators/individual/__init__.py +25 -0
  1528. scitex/scholar/url_finder/translators/individual/abc_news_australia.py +23 -0
  1529. scitex/scholar/url_finder/translators/individual/access_engineering.py +23 -0
  1530. scitex/scholar/url_finder/translators/individual/access_medicine.py +255 -0
  1531. scitex/scholar/url_finder/translators/individual/access_science.py +23 -0
  1532. scitex/scholar/url_finder/translators/individual/acls_humanities_ebook.py +23 -0
  1533. scitex/scholar/url_finder/translators/individual/aclweb.py +23 -0
  1534. scitex/scholar/url_finder/translators/individual/acm.py +11 -0
  1535. scitex/scholar/url_finder/translators/individual/acm_digital_library.py +355 -0
  1536. scitex/scholar/url_finder/translators/individual/acs.py +23 -0
  1537. scitex/scholar/url_finder/translators/individual/acs_publications.py +23 -0
  1538. scitex/scholar/url_finder/translators/individual/adam_matthew_digital.py +23 -0
  1539. scitex/scholar/url_finder/translators/individual/ads_bibcode.py +23 -0
  1540. scitex/scholar/url_finder/translators/individual/aea_web.py +23 -0
  1541. scitex/scholar/url_finder/translators/individual/agencia_del_isbn.py +23 -0
  1542. scitex/scholar/url_finder/translators/individual/agris.py +23 -0
  1543. scitex/scholar/url_finder/translators/individual/ahval_news.py +23 -0
  1544. scitex/scholar/url_finder/translators/individual/aip.py +23 -0
  1545. scitex/scholar/url_finder/translators/individual/air_university_journals.py +23 -0
  1546. scitex/scholar/url_finder/translators/individual/airiti.py +25 -0
  1547. scitex/scholar/url_finder/translators/individual/alexander_street_press.py +23 -0
  1548. scitex/scholar/url_finder/translators/individual/all_africa.py +23 -0
  1549. scitex/scholar/url_finder/translators/individual/allafrica.py +23 -0
  1550. scitex/scholar/url_finder/translators/individual/alsharekh.py +23 -0
  1551. scitex/scholar/url_finder/translators/individual/alternet.py +23 -0
  1552. scitex/scholar/url_finder/translators/individual/aluka.py +23 -0
  1553. scitex/scholar/url_finder/translators/individual/amazon.py +23 -0
  1554. scitex/scholar/url_finder/translators/individual/american_archive_public_broadcasting.py +23 -0
  1555. scitex/scholar/url_finder/translators/individual/american_institute_aeronautics_astronautics.py +23 -0
  1556. scitex/scholar/url_finder/translators/individual/american_prospect.py +23 -0
  1557. scitex/scholar/url_finder/translators/individual/ams_journals.py +23 -0
  1558. scitex/scholar/url_finder/translators/individual/ams_mathscinet.py +23 -0
  1559. scitex/scholar/url_finder/translators/individual/ams_mathscinet_legacy.py +23 -0
  1560. scitex/scholar/url_finder/translators/individual/ancestry_com_us_federal_census.py +23 -0
  1561. scitex/scholar/url_finder/translators/individual/ancestry_us_federal_census.py +23 -0
  1562. scitex/scholar/url_finder/translators/individual/annual_reviews.py +23 -0
  1563. scitex/scholar/url_finder/translators/individual/antikvarium_hu.py +23 -0
  1564. scitex/scholar/url_finder/translators/individual/aosic.py +25 -0
  1565. scitex/scholar/url_finder/translators/individual/apa_psycnet.py +23 -0
  1566. scitex/scholar/url_finder/translators/individual/apn_ru.py +23 -0
  1567. scitex/scholar/url_finder/translators/individual/aps.py +23 -0
  1568. scitex/scholar/url_finder/translators/individual/aps_physics.py +115 -0
  1569. scitex/scholar/url_finder/translators/individual/aquadocs.py +23 -0
  1570. scitex/scholar/url_finder/translators/individual/archeion.py +23 -0
  1571. scitex/scholar/url_finder/translators/individual/archiv_fuer_sozialgeschichte.py +23 -0
  1572. scitex/scholar/url_finder/translators/individual/archive_ouverte_aosic.py +25 -0
  1573. scitex/scholar/url_finder/translators/individual/archive_ouverte_en_sciences_de_l_information_et_de_la_communication___aosic_.py +27 -0
  1574. scitex/scholar/url_finder/translators/individual/archives_canada.py +23 -0
  1575. scitex/scholar/url_finder/translators/individual/ariana_news.py +23 -0
  1576. scitex/scholar/url_finder/translators/individual/art_institute_of_chicago.py +23 -0
  1577. scitex/scholar/url_finder/translators/individual/artefacts_canada.py +23 -0
  1578. scitex/scholar/url_finder/translators/individual/artfl_encyclopedie.py +23 -0
  1579. scitex/scholar/url_finder/translators/individual/artforum.py +152 -0
  1580. scitex/scholar/url_finder/translators/individual/artnet.py +23 -0
  1581. scitex/scholar/url_finder/translators/individual/artnews.py +23 -0
  1582. scitex/scholar/url_finder/translators/individual/artstor.py +23 -0
  1583. scitex/scholar/url_finder/translators/individual/arxiv.py +39 -0
  1584. scitex/scholar/url_finder/translators/individual/arxiv_org.py +122 -0
  1585. scitex/scholar/url_finder/translators/individual/asce.py +23 -0
  1586. scitex/scholar/url_finder/translators/individual/asco_meeting_library.py +23 -0
  1587. scitex/scholar/url_finder/translators/individual/astis.py +23 -0
  1588. scitex/scholar/url_finder/translators/individual/atlanta_journal_constitution.py +145 -0
  1589. scitex/scholar/url_finder/translators/individual/ats_international_journal.py +23 -0
  1590. scitex/scholar/url_finder/translators/individual/atypon.py +23 -0
  1591. scitex/scholar/url_finder/translators/individual/atypon_journals.py +253 -0
  1592. scitex/scholar/url_finder/translators/individual/austlii_and_nzlii.py +251 -0
  1593. scitex/scholar/url_finder/translators/individual/australian_dictionary_of_biography.py +124 -0
  1594. scitex/scholar/url_finder/translators/individual/bailii.py +122 -0
  1595. scitex/scholar/url_finder/translators/individual/bbc.py +229 -0
  1596. scitex/scholar/url_finder/translators/individual/bbc_genome.py +180 -0
  1597. scitex/scholar/url_finder/translators/individual/beck_online.py +23 -0
  1598. scitex/scholar/url_finder/translators/individual/biblioteca_nacional_de_maestros.py +144 -0
  1599. scitex/scholar/url_finder/translators/individual/bibliotheque_archives_nationale_quebec_pistard.py +134 -0
  1600. scitex/scholar/url_finder/translators/individual/bibliotheque_archives_nationales_quebec.py +187 -0
  1601. scitex/scholar/url_finder/translators/individual/bibliotheque_et_archives_nationale_du_quebec__pistard_.py +23 -0
  1602. scitex/scholar/url_finder/translators/individual/bibliotheque_et_archives_nationales_du_quebec.py +23 -0
  1603. scitex/scholar/url_finder/translators/individual/bibliotheque_nationale_de_france.py +23 -0
  1604. scitex/scholar/url_finder/translators/individual/bibliotheque_nationale_france.py +163 -0
  1605. scitex/scholar/url_finder/translators/individual/bibsys.py +162 -0
  1606. scitex/scholar/url_finder/translators/individual/bioconductor.py +184 -0
  1607. scitex/scholar/url_finder/translators/individual/biomed_central.py +151 -0
  1608. scitex/scholar/url_finder/translators/individual/bioone.py +23 -0
  1609. scitex/scholar/url_finder/translators/individual/biorxiv.py +146 -0
  1610. scitex/scholar/url_finder/translators/individual/blaetter.py +23 -0
  1611. scitex/scholar/url_finder/translators/individual/blogger.py +148 -0
  1612. scitex/scholar/url_finder/translators/individual/bloomberg.py +168 -0
  1613. scitex/scholar/url_finder/translators/individual/bloomsbury_food_library.py +135 -0
  1614. scitex/scholar/url_finder/translators/individual/bluesky.py +129 -0
  1615. scitex/scholar/url_finder/translators/individual/bnf_isbn.py +118 -0
  1616. scitex/scholar/url_finder/translators/individual/bocc.py +247 -0
  1617. scitex/scholar/url_finder/translators/individual/boe.py +196 -0
  1618. scitex/scholar/url_finder/translators/individual/bookmarks.py +23 -0
  1619. scitex/scholar/url_finder/translators/individual/bookshop_org.py +23 -0
  1620. scitex/scholar/url_finder/translators/individual/boston_review.py +23 -0
  1621. scitex/scholar/url_finder/translators/individual/bosworth_toller_anglo_saxon_dictionary.py +23 -0
  1622. scitex/scholar/url_finder/translators/individual/bosworth_toller_s_anglo_saxon_dictionary_online.py +23 -0
  1623. scitex/scholar/url_finder/translators/individual/bracero_history_archive.py +23 -0
  1624. scitex/scholar/url_finder/translators/individual/brill.py +25 -0
  1625. scitex/scholar/url_finder/translators/individual/brukerhandboken.py +23 -0
  1626. scitex/scholar/url_finder/translators/individual/bryn_mawr_classical_review.py +23 -0
  1627. scitex/scholar/url_finder/translators/individual/bundesgesetzblatt.py +23 -0
  1628. scitex/scholar/url_finder/translators/individual/business_standard.py +178 -0
  1629. scitex/scholar/url_finder/translators/individual/cabi___cab_abstracts.py +23 -0
  1630. scitex/scholar/url_finder/translators/individual/cabi_cab_abstracts.py +176 -0
  1631. scitex/scholar/url_finder/translators/individual/cairn.py +23 -0
  1632. scitex/scholar/url_finder/translators/individual/cairn_info.py +23 -0
  1633. scitex/scholar/url_finder/translators/individual/cambridge.py +25 -0
  1634. scitex/scholar/url_finder/translators/individual/cambridge_core.py +178 -0
  1635. scitex/scholar/url_finder/translators/individual/cambridge_engage_preprints.py +23 -0
  1636. scitex/scholar/url_finder/translators/individual/caod.py +224 -0
  1637. scitex/scholar/url_finder/translators/individual/cbc.py +281 -0
  1638. scitex/scholar/url_finder/translators/individual/ccfr__bnf_.py +23 -0
  1639. scitex/scholar/url_finder/translators/individual/ccfr_bnf.py +223 -0
  1640. scitex/scholar/url_finder/translators/individual/cell_press.py +150 -0
  1641. scitex/scholar/url_finder/translators/individual/cern_document_server.py +23 -0
  1642. scitex/scholar/url_finder/translators/individual/ceur_workshop_proceedings.py +23 -0
  1643. scitex/scholar/url_finder/translators/individual/cff.py +23 -0
  1644. scitex/scholar/url_finder/translators/individual/cff_references.py +23 -0
  1645. scitex/scholar/url_finder/translators/individual/champlain_society___collection.py +23 -0
  1646. scitex/scholar/url_finder/translators/individual/chronicling_america.py +23 -0
  1647. scitex/scholar/url_finder/translators/individual/cia_world_factbook.py +163 -0
  1648. scitex/scholar/url_finder/translators/individual/cinii_research.py +23 -0
  1649. scitex/scholar/url_finder/translators/individual/citavi_5_xml.py +23 -0
  1650. scitex/scholar/url_finder/translators/individual/citeseer.py +23 -0
  1651. scitex/scholar/url_finder/translators/individual/citizen_lab.py +23 -0
  1652. scitex/scholar/url_finder/translators/individual/civilization_ca.py +23 -0
  1653. scitex/scholar/url_finder/translators/individual/clacso.py +23 -0
  1654. scitex/scholar/url_finder/translators/individual/climate_change_and_human_health_literature_portal.py +23 -0
  1655. scitex/scholar/url_finder/translators/individual/climate_change_human_health.py +23 -0
  1656. scitex/scholar/url_finder/translators/individual/clinical_key.py +23 -0
  1657. scitex/scholar/url_finder/translators/individual/clinicaltrials_gov.py +23 -0
  1658. scitex/scholar/url_finder/translators/individual/cnki.py +23 -0
  1659. scitex/scholar/url_finder/translators/individual/code4lib_journal.py +23 -0
  1660. scitex/scholar/url_finder/translators/individual/colorado_state_legislature.py +23 -0
  1661. scitex/scholar/url_finder/translators/individual/computer_history_museum_archive.py +23 -0
  1662. scitex/scholar/url_finder/translators/individual/copernicus.py +23 -0
  1663. scitex/scholar/url_finder/translators/individual/crossref_rest.py +618 -0
  1664. scitex/scholar/url_finder/translators/individual/crossref_unixref_xml.py +23 -0
  1665. scitex/scholar/url_finder/translators/individual/csiro_publishing.py +23 -0
  1666. scitex/scholar/url_finder/translators/individual/csiro_publishing_dup.py +23 -0
  1667. scitex/scholar/url_finder/translators/individual/current_affairs.py +112 -0
  1668. scitex/scholar/url_finder/translators/individual/dabi.py +205 -0
  1669. scitex/scholar/url_finder/translators/individual/dagens_nyheter.py +25 -0
  1670. scitex/scholar/url_finder/translators/individual/dagstuhl.py +147 -0
  1671. scitex/scholar/url_finder/translators/individual/dagstuhl_research_online_publication_server.py +23 -0
  1672. scitex/scholar/url_finder/translators/individual/dar_almandumah.py +160 -0
  1673. scitex/scholar/url_finder/translators/individual/dart_europe.py +172 -0
  1674. scitex/scholar/url_finder/translators/individual/data_gov.py +145 -0
  1675. scitex/scholar/url_finder/translators/individual/databrary.py +144 -0
  1676. scitex/scholar/url_finder/translators/individual/datacite_json.py +278 -0
  1677. scitex/scholar/url_finder/translators/individual/dataverse.py +190 -0
  1678. scitex/scholar/url_finder/translators/individual/daum_news.py +133 -0
  1679. scitex/scholar/url_finder/translators/individual/dblp.py +151 -0
  1680. scitex/scholar/url_finder/translators/individual/dblp_computer_science_bibliography.py +104 -0
  1681. scitex/scholar/url_finder/translators/individual/dbpia.py +102 -0
  1682. scitex/scholar/url_finder/translators/individual/de_gruyter_brill.py +23 -0
  1683. scitex/scholar/url_finder/translators/individual/defense_technical_information_center.py +114 -0
  1684. scitex/scholar/url_finder/translators/individual/dejure_org.py +23 -0
  1685. scitex/scholar/url_finder/translators/individual/delpher.py +224 -0
  1686. scitex/scholar/url_finder/translators/individual/demographic_research.py +160 -0
  1687. scitex/scholar/url_finder/translators/individual/denik_cz.py +155 -0
  1688. scitex/scholar/url_finder/translators/individual/depatisnet.py +145 -0
  1689. scitex/scholar/url_finder/translators/individual/der_freitag.py +214 -0
  1690. scitex/scholar/url_finder/translators/individual/der_spiegel.py +189 -0
  1691. scitex/scholar/url_finder/translators/individual/desiring_god.py +23 -0
  1692. scitex/scholar/url_finder/translators/individual/deutsche_fotothek.py +23 -0
  1693. scitex/scholar/url_finder/translators/individual/deutsche_nationalbibliothek.py +23 -0
  1694. scitex/scholar/url_finder/translators/individual/dhistory.py +23 -0
  1695. scitex/scholar/url_finder/translators/individual/dialnet.py +23 -0
  1696. scitex/scholar/url_finder/translators/individual/die_zeit.py +23 -0
  1697. scitex/scholar/url_finder/translators/individual/digibib_net.py +25 -0
  1698. scitex/scholar/url_finder/translators/individual/digital_humanities_quarterly.py +23 -0
  1699. scitex/scholar/url_finder/translators/individual/digital_spy.py +23 -0
  1700. scitex/scholar/url_finder/translators/individual/digizeitschriften.py +25 -0
  1701. scitex/scholar/url_finder/translators/individual/dimensions.py +23 -0
  1702. scitex/scholar/url_finder/translators/individual/dlibra.py +23 -0
  1703. scitex/scholar/url_finder/translators/individual/doaj.py +23 -0
  1704. scitex/scholar/url_finder/translators/individual/doi.py +23 -0
  1705. scitex/scholar/url_finder/translators/individual/doi_content_negotiation.py +23 -0
  1706. scitex/scholar/url_finder/translators/individual/douban.py +23 -0
  1707. scitex/scholar/url_finder/translators/individual/dpla.py +200 -0
  1708. scitex/scholar/url_finder/translators/individual/dreier_neuerscheinungsdienst.py +23 -0
  1709. scitex/scholar/url_finder/translators/individual/drugbank_ca.py +23 -0
  1710. scitex/scholar/url_finder/translators/individual/dryad.py +23 -0
  1711. scitex/scholar/url_finder/translators/individual/dryad_digital_repository.py +23 -0
  1712. scitex/scholar/url_finder/translators/individual/dspace.py +109 -0
  1713. scitex/scholar/url_finder/translators/individual/dspace_intermediate_metadata.py +23 -0
  1714. scitex/scholar/url_finder/translators/individual/duke_university_press_books.py +23 -0
  1715. scitex/scholar/url_finder/translators/individual/e_periodica_switzerland.py +23 -0
  1716. scitex/scholar/url_finder/translators/individual/eastview.py +23 -0
  1717. scitex/scholar/url_finder/translators/individual/ebrary.py +25 -0
  1718. scitex/scholar/url_finder/translators/individual/ebsco_discovery_layer.py +23 -0
  1719. scitex/scholar/url_finder/translators/individual/ebscohost.py +25 -0
  1720. scitex/scholar/url_finder/translators/individual/edinburgh_university_press_journals.py +23 -0
  1721. scitex/scholar/url_finder/translators/individual/education_week.py +23 -0
  1722. scitex/scholar/url_finder/translators/individual/eidr.py +23 -0
  1723. scitex/scholar/url_finder/translators/individual/el_comercio__peru_.py +23 -0
  1724. scitex/scholar/url_finder/translators/individual/el_pais.py +23 -0
  1725. scitex/scholar/url_finder/translators/individual/electronic_colloquium_on_computational_complexity.py +25 -0
  1726. scitex/scholar/url_finder/translators/individual/elibrary_ru.py +23 -0
  1727. scitex/scholar/url_finder/translators/individual/elicit.py +23 -0
  1728. scitex/scholar/url_finder/translators/individual/elife.py +25 -0
  1729. scitex/scholar/url_finder/translators/individual/elsevier_health.py +23 -0
  1730. scitex/scholar/url_finder/translators/individual/elsevier_health_journals.py +25 -0
  1731. scitex/scholar/url_finder/translators/individual/elsevier_pure.py +23 -0
  1732. scitex/scholar/url_finder/translators/individual/embedded_metadata.py +23 -0
  1733. scitex/scholar/url_finder/translators/individual/emedicine.py +23 -0
  1734. scitex/scholar/url_finder/translators/individual/emerald.py +25 -0
  1735. scitex/scholar/url_finder/translators/individual/emerald_insight.py +25 -0
  1736. scitex/scholar/url_finder/translators/individual/emja.py +23 -0
  1737. scitex/scholar/url_finder/translators/individual/encyclopedia_of_chicago.py +23 -0
  1738. scitex/scholar/url_finder/translators/individual/encyclopedia_of_korean_culture.py +23 -0
  1739. scitex/scholar/url_finder/translators/individual/endnote_xml.py +23 -0
  1740. scitex/scholar/url_finder/translators/individual/engineering_village.py +23 -0
  1741. scitex/scholar/url_finder/translators/individual/envidat.py +23 -0
  1742. scitex/scholar/url_finder/translators/individual/epa_national_library_catalog.py +23 -0
  1743. scitex/scholar/url_finder/translators/individual/epicurious.py +25 -0
  1744. scitex/scholar/url_finder/translators/individual/eprint_iacr.py +23 -0
  1745. scitex/scholar/url_finder/translators/individual/eric.py +23 -0
  1746. scitex/scholar/url_finder/translators/individual/erudit.py +23 -0
  1747. scitex/scholar/url_finder/translators/individual/espacenet.py +23 -0
  1748. scitex/scholar/url_finder/translators/individual/etatar_ru.py +23 -0
  1749. scitex/scholar/url_finder/translators/individual/euclid.py +23 -0
  1750. scitex/scholar/url_finder/translators/individual/eur_lex.py +23 -0
  1751. scitex/scholar/url_finder/translators/individual/eurasianet.py +23 -0
  1752. scitex/scholar/url_finder/translators/individual/eurogamerusgamer.py +25 -0
  1753. scitex/scholar/url_finder/translators/individual/europe_pmc.py +23 -0
  1754. scitex/scholar/url_finder/translators/individual/evernote.py +23 -0
  1755. scitex/scholar/url_finder/translators/individual/f1000_research.py +23 -0
  1756. scitex/scholar/url_finder/translators/individual/fachportal_padagogik.py +25 -0
  1757. scitex/scholar/url_finder/translators/individual/factiva.py +23 -0
  1758. scitex/scholar/url_finder/translators/individual/failed_architecture.py +23 -0
  1759. scitex/scholar/url_finder/translators/individual/fairfax_australia.py +23 -0
  1760. scitex/scholar/url_finder/translators/individual/fao_publications.py +23 -0
  1761. scitex/scholar/url_finder/translators/individual/fatcat.py +23 -0
  1762. scitex/scholar/url_finder/translators/individual/faz_net.py +23 -0
  1763. scitex/scholar/url_finder/translators/individual/feb_web_ru.py +23 -0
  1764. scitex/scholar/url_finder/translators/individual/figshare.py +23 -0
  1765. scitex/scholar/url_finder/translators/individual/financial_times.py +23 -0
  1766. scitex/scholar/url_finder/translators/individual/finna.py +23 -0
  1767. scitex/scholar/url_finder/translators/individual/fishpond_co_nz.py +23 -0
  1768. scitex/scholar/url_finder/translators/individual/flickr.py +23 -0
  1769. scitex/scholar/url_finder/translators/individual/foreign_affairs.py +23 -0
  1770. scitex/scholar/url_finder/translators/individual/foreign_policy.py +23 -0
  1771. scitex/scholar/url_finder/translators/individual/fr_online_de.py +23 -0
  1772. scitex/scholar/url_finder/translators/individual/freecite.py +23 -0
  1773. scitex/scholar/url_finder/translators/individual/freepatentsonline.py +23 -0
  1774. scitex/scholar/url_finder/translators/individual/frieze.py +23 -0
  1775. scitex/scholar/url_finder/translators/individual/frontiers.py +84 -0
  1776. scitex/scholar/url_finder/translators/individual/gale_databases.py +25 -0
  1777. scitex/scholar/url_finder/translators/individual/galegdc.py +23 -0
  1778. scitex/scholar/url_finder/translators/individual/galegroup.py +23 -0
  1779. scitex/scholar/url_finder/translators/individual/gallica.py +23 -0
  1780. scitex/scholar/url_finder/translators/individual/game_studies.py +23 -0
  1781. scitex/scholar/url_finder/translators/individual/gamespot.py +23 -0
  1782. scitex/scholar/url_finder/translators/individual/gamestar_gamepro.py +23 -0
  1783. scitex/scholar/url_finder/translators/individual/gasyrlar_awazy.py +23 -0
  1784. scitex/scholar/url_finder/translators/individual/gemeinsamer_bibliotheksverbund_isbn.py +23 -0
  1785. scitex/scholar/url_finder/translators/individual/gene_ontology.py +23 -0
  1786. scitex/scholar/url_finder/translators/individual/github.py +23 -0
  1787. scitex/scholar/url_finder/translators/individual/globes.py +23 -0
  1788. scitex/scholar/url_finder/translators/individual/gmail.py +23 -0
  1789. scitex/scholar/url_finder/translators/individual/gms_german_medical_science.py +111 -0
  1790. scitex/scholar/url_finder/translators/individual/goodreads.py +23 -0
  1791. scitex/scholar/url_finder/translators/individual/google_books.py +23 -0
  1792. scitex/scholar/url_finder/translators/individual/google_patents.py +23 -0
  1793. scitex/scholar/url_finder/translators/individual/google_play.py +23 -0
  1794. scitex/scholar/url_finder/translators/individual/google_presentation.py +23 -0
  1795. scitex/scholar/url_finder/translators/individual/google_research.py +23 -0
  1796. scitex/scholar/url_finder/translators/individual/google_scholar.py +23 -0
  1797. scitex/scholar/url_finder/translators/individual/govinfo.py +23 -0
  1798. scitex/scholar/url_finder/translators/individual/gpo_access_e_cfr.py +23 -0
  1799. scitex/scholar/url_finder/translators/individual/gulag_many_days__many_lives.py +23 -0
  1800. scitex/scholar/url_finder/translators/individual/haaretz.py +23 -0
  1801. scitex/scholar/url_finder/translators/individual/habr.py +23 -0
  1802. scitex/scholar/url_finder/translators/individual/hal.py +23 -0
  1803. scitex/scholar/url_finder/translators/individual/hal_archives_ouvertes.py +23 -0
  1804. scitex/scholar/url_finder/translators/individual/handelszeitung.py +23 -0
  1805. scitex/scholar/url_finder/translators/individual/hanrei_watch.py +23 -0
  1806. scitex/scholar/url_finder/translators/individual/harper_s_magazine.py +23 -0
  1807. scitex/scholar/url_finder/translators/individual/harvard_business_review.py +23 -0
  1808. scitex/scholar/url_finder/translators/individual/harvard_caselaw_access_project.py +23 -0
  1809. scitex/scholar/url_finder/translators/individual/harvard_university_press_books.py +23 -0
  1810. scitex/scholar/url_finder/translators/individual/hathitrust.py +23 -0
  1811. scitex/scholar/url_finder/translators/individual/hcsp.py +23 -0
  1812. scitex/scholar/url_finder/translators/individual/heinonline.py +23 -0
  1813. scitex/scholar/url_finder/translators/individual/heise.py +23 -0
  1814. scitex/scholar/url_finder/translators/individual/herder.py +23 -0
  1815. scitex/scholar/url_finder/translators/individual/highbeam.py +23 -0
  1816. scitex/scholar/url_finder/translators/individual/highwire.py +23 -0
  1817. scitex/scholar/url_finder/translators/individual/highwire2.py +23 -0
  1818. scitex/scholar/url_finder/translators/individual/highwire_2_0.py +23 -0
  1819. scitex/scholar/url_finder/translators/individual/hindawi.py +23 -0
  1820. scitex/scholar/url_finder/translators/individual/hindawi_publishers.py +23 -0
  1821. scitex/scholar/url_finder/translators/individual/hispanic_american_periodical_index.py +23 -0
  1822. scitex/scholar/url_finder/translators/individual/homeland_security_digital_library.py +23 -0
  1823. scitex/scholar/url_finder/translators/individual/hudoc.py +23 -0
  1824. scitex/scholar/url_finder/translators/individual/huff_post.py +23 -0
  1825. scitex/scholar/url_finder/translators/individual/human_rights_watch.py +23 -0
  1826. scitex/scholar/url_finder/translators/individual/ibisworld.py +23 -0
  1827. scitex/scholar/url_finder/translators/individual/idea_alm.py +23 -0
  1828. scitex/scholar/url_finder/translators/individual/idref.py +23 -0
  1829. scitex/scholar/url_finder/translators/individual/ieee_computer_society.py +118 -0
  1830. scitex/scholar/url_finder/translators/individual/ieee_xplore.py +265 -0
  1831. scitex/scholar/url_finder/translators/individual/ietf.py +23 -0
  1832. scitex/scholar/url_finder/translators/individual/ign.py +23 -0
  1833. scitex/scholar/url_finder/translators/individual/imdb.py +23 -0
  1834. scitex/scholar/url_finder/translators/individual/imf.py +23 -0
  1835. scitex/scholar/url_finder/translators/individual/in_these_times.py +23 -0
  1836. scitex/scholar/url_finder/translators/individual/informationssystem_medienpaedagogik.py +23 -0
  1837. scitex/scholar/url_finder/translators/individual/informit_database.py +23 -0
  1838. scitex/scholar/url_finder/translators/individual/infotrac.py +23 -0
  1839. scitex/scholar/url_finder/translators/individual/ingenta_connect.py +23 -0
  1840. scitex/scholar/url_finder/translators/individual/ingentaconnect.py +23 -0
  1841. scitex/scholar/url_finder/translators/individual/inside_higher_ed.py +23 -0
  1842. scitex/scholar/url_finder/translators/individual/insignia_opac.py +23 -0
  1843. scitex/scholar/url_finder/translators/individual/inspire.py +23 -0
  1844. scitex/scholar/url_finder/translators/individual/institute_of_contemporary_art.py +23 -0
  1845. scitex/scholar/url_finder/translators/individual/institute_of_physics.py +23 -0
  1846. scitex/scholar/url_finder/translators/individual/integrum.py +23 -0
  1847. scitex/scholar/url_finder/translators/individual/intellixir.py +23 -0
  1848. scitex/scholar/url_finder/translators/individual/inter_research_science_center.py +116 -0
  1849. scitex/scholar/url_finder/translators/individual/international_nuclear_information_system.py +23 -0
  1850. scitex/scholar/url_finder/translators/individual/internet_archive.py +23 -0
  1851. scitex/scholar/url_finder/translators/individual/internet_archive_scholar.py +23 -0
  1852. scitex/scholar/url_finder/translators/individual/internet_archive_wayback.py +23 -0
  1853. scitex/scholar/url_finder/translators/individual/internet_archive_wayback_machine.py +23 -0
  1854. scitex/scholar/url_finder/translators/individual/invenio_rdm.py +23 -0
  1855. scitex/scholar/url_finder/translators/individual/inveniordm.py +23 -0
  1856. scitex/scholar/url_finder/translators/individual/io_port.py +23 -0
  1857. scitex/scholar/url_finder/translators/individual/iop.py +23 -0
  1858. scitex/scholar/url_finder/translators/individual/ipcc.py +23 -0
  1859. scitex/scholar/url_finder/translators/individual/isidore.py +23 -0
  1860. scitex/scholar/url_finder/translators/individual/istc.py +23 -0
  1861. scitex/scholar/url_finder/translators/individual/j_stage.py +23 -0
  1862. scitex/scholar/url_finder/translators/individual/jahrbuch.py +23 -0
  1863. scitex/scholar/url_finder/translators/individual/japan_times_online.py +23 -0
  1864. scitex/scholar/url_finder/translators/individual/jets.py +23 -0
  1865. scitex/scholar/url_finder/translators/individual/jisc_historical_texts.py +25 -0
  1866. scitex/scholar/url_finder/translators/individual/journal_of_electronic_publishing.py +23 -0
  1867. scitex/scholar/url_finder/translators/individual/journal_of_extension.py +23 -0
  1868. scitex/scholar/url_finder/translators/individual/journal_of_machine_learning_research.py +23 -0
  1869. scitex/scholar/url_finder/translators/individual/journal_of_religion_and_society.py +23 -0
  1870. scitex/scholar/url_finder/translators/individual/jrc_publications_repository.py +23 -0
  1871. scitex/scholar/url_finder/translators/individual/jstage.py +23 -0
  1872. scitex/scholar/url_finder/translators/individual/jstor.py +211 -0
  1873. scitex/scholar/url_finder/translators/individual/juricaf.py +23 -0
  1874. scitex/scholar/url_finder/translators/individual/jurion.py +23 -0
  1875. scitex/scholar/url_finder/translators/individual/juris.py +23 -0
  1876. scitex/scholar/url_finder/translators/individual/jurpc.py +23 -0
  1877. scitex/scholar/url_finder/translators/individual/k10plus_isbn.py +23 -0
  1878. scitex/scholar/url_finder/translators/individual/kanopy.py +23 -0
  1879. scitex/scholar/url_finder/translators/individual/karger.py +23 -0
  1880. scitex/scholar/url_finder/translators/individual/khaama_press.py +23 -0
  1881. scitex/scholar/url_finder/translators/individual/kitapyurdu_com.py +23 -0
  1882. scitex/scholar/url_finder/translators/individual/kommersant.py +23 -0
  1883. scitex/scholar/url_finder/translators/individual/korean_national_library.py +25 -0
  1884. scitex/scholar/url_finder/translators/individual/kstudy.py +23 -0
  1885. scitex/scholar/url_finder/translators/individual/l_annee_philologique.py +23 -0
  1886. scitex/scholar/url_finder/translators/individual/la_croix.py +23 -0
  1887. scitex/scholar/url_finder/translators/individual/la_nacion__argentina_.py +23 -0
  1888. scitex/scholar/url_finder/translators/individual/la_presse.py +23 -0
  1889. scitex/scholar/url_finder/translators/individual/la_republica__peru_.py +23 -0
  1890. scitex/scholar/url_finder/translators/individual/la_times.py +25 -0
  1891. scitex/scholar/url_finder/translators/individual/lagen_nu.py +23 -0
  1892. scitex/scholar/url_finder/translators/individual/landesbibliographie_baden_wurttemberg.py +25 -0
  1893. scitex/scholar/url_finder/translators/individual/lapham_s_quarterly.py +23 -0
  1894. scitex/scholar/url_finder/translators/individual/le_devoir.py +23 -0
  1895. scitex/scholar/url_finder/translators/individual/le_figaro.py +23 -0
  1896. scitex/scholar/url_finder/translators/individual/le_maitron.py +23 -0
  1897. scitex/scholar/url_finder/translators/individual/le_monde.py +23 -0
  1898. scitex/scholar/url_finder/translators/individual/le_monde_diplomatique.py +23 -0
  1899. scitex/scholar/url_finder/translators/individual/legifrance.py +23 -0
  1900. scitex/scholar/url_finder/translators/individual/legislative_insight.py +25 -0
  1901. scitex/scholar/url_finder/translators/individual/lexis_.py +23 -0
  1902. scitex/scholar/url_finder/translators/individual/lexisnexis.py +23 -0
  1903. scitex/scholar/url_finder/translators/individual/libraries_tasmania.py +25 -0
  1904. scitex/scholar/url_finder/translators/individual/library_catalog__aleph_.py +23 -0
  1905. scitex/scholar/url_finder/translators/individual/library_catalog__amicus_.py +23 -0
  1906. scitex/scholar/url_finder/translators/individual/library_catalog__aquabrowser_.py +23 -0
  1907. scitex/scholar/url_finder/translators/individual/library_catalog__bibliocommons_.py +23 -0
  1908. scitex/scholar/url_finder/translators/individual/library_catalog__blacklight_.py +23 -0
  1909. scitex/scholar/url_finder/translators/individual/library_catalog__capita_prism_.py +23 -0
  1910. scitex/scholar/url_finder/translators/individual/library_catalog__dra_.py +23 -0
  1911. scitex/scholar/url_finder/translators/individual/library_catalog__dynix_.py +23 -0
  1912. scitex/scholar/url_finder/translators/individual/library_catalog__encore_.py +23 -0
  1913. scitex/scholar/url_finder/translators/individual/library_catalog__innopac_.py +23 -0
  1914. scitex/scholar/url_finder/translators/individual/library_catalog__koha_.py +25 -0
  1915. scitex/scholar/url_finder/translators/individual/library_catalog__mango_.py +23 -0
  1916. scitex/scholar/url_finder/translators/individual/library_catalog__opals_.py +23 -0
  1917. scitex/scholar/url_finder/translators/individual/library_catalog__pica2_.py +23 -0
  1918. scitex/scholar/url_finder/translators/individual/library_catalog__pica_.py +23 -0
  1919. scitex/scholar/url_finder/translators/individual/library_catalog__pika_.py +23 -0
  1920. scitex/scholar/url_finder/translators/individual/library_catalog__polaris_.py +23 -0
  1921. scitex/scholar/url_finder/translators/individual/library_catalog__quolto_.py +23 -0
  1922. scitex/scholar/url_finder/translators/individual/library_catalog__rero_ils_.py +23 -0
  1923. scitex/scholar/url_finder/translators/individual/library_catalog__sirsi_.py +23 -0
  1924. scitex/scholar/url_finder/translators/individual/library_catalog__sirsi_elibrary_.py +23 -0
  1925. scitex/scholar/url_finder/translators/individual/library_catalog__slims_.py +23 -0
  1926. scitex/scholar/url_finder/translators/individual/library_catalog__tind_ils_.py +23 -0
  1927. scitex/scholar/url_finder/translators/individual/library_catalog__tinread_.py +23 -0
  1928. scitex/scholar/url_finder/translators/individual/library_catalog__tlcyouseemore_.py +23 -0
  1929. scitex/scholar/url_finder/translators/individual/library_catalog__visual_library_2021_.py +23 -0
  1930. scitex/scholar/url_finder/translators/individual/library_catalog__voyager_.py +23 -0
  1931. scitex/scholar/url_finder/translators/individual/library_catalog__voyager_7_.py +23 -0
  1932. scitex/scholar/url_finder/translators/individual/library_catalog__vtls_.py +23 -0
  1933. scitex/scholar/url_finder/translators/individual/library_genesis.py +23 -0
  1934. scitex/scholar/url_finder/translators/individual/library_hub_discover.py +23 -0
  1935. scitex/scholar/url_finder/translators/individual/library_of_congress_digital_collections.py +23 -0
  1936. scitex/scholar/url_finder/translators/individual/library_of_congress_isbn.py +23 -0
  1937. scitex/scholar/url_finder/translators/individual/libris_isbn.py +23 -0
  1938. scitex/scholar/url_finder/translators/individual/lingbuzz.py +180 -0
  1939. scitex/scholar/url_finder/translators/individual/lippincott_williams_and_wilkins.py +23 -0
  1940. scitex/scholar/url_finder/translators/individual/literary_hub.py +23 -0
  1941. scitex/scholar/url_finder/translators/individual/litres.py +23 -0
  1942. scitex/scholar/url_finder/translators/individual/livejournal.py +23 -0
  1943. scitex/scholar/url_finder/translators/individual/livivo.py +23 -0
  1944. scitex/scholar/url_finder/translators/individual/london_review_of_books.py +23 -0
  1945. scitex/scholar/url_finder/translators/individual/lookus.py +23 -0
  1946. scitex/scholar/url_finder/translators/individual/lulu.py +23 -0
  1947. scitex/scholar/url_finder/translators/individual/lwn_net.py +23 -0
  1948. scitex/scholar/url_finder/translators/individual/lww.py +23 -0
  1949. scitex/scholar/url_finder/translators/individual/mab2.py +23 -0
  1950. scitex/scholar/url_finder/translators/individual/magazines_russ_ru.py +23 -0
  1951. scitex/scholar/url_finder/translators/individual/mailman.py +23 -0
  1952. scitex/scholar/url_finder/translators/individual/mainichi_daily_news.py +23 -0
  1953. scitex/scholar/url_finder/translators/individual/marc.py +23 -0
  1954. scitex/scholar/url_finder/translators/individual/marcxml.py +23 -0
  1955. scitex/scholar/url_finder/translators/individual/mastodon.py +23 -0
  1956. scitex/scholar/url_finder/translators/individual/matbugat_ru.py +23 -0
  1957. scitex/scholar/url_finder/translators/individual/max_planck_institute_for_the_history_of_science_virtual_laboratory_library.py +25 -0
  1958. scitex/scholar/url_finder/translators/individual/mcv.py +23 -0
  1959. scitex/scholar/url_finder/translators/individual/mdpi.py +76 -0
  1960. scitex/scholar/url_finder/translators/individual/mdpi_journals.py +23 -0
  1961. scitex/scholar/url_finder/translators/individual/medes.py +23 -0
  1962. scitex/scholar/url_finder/translators/individual/medium.py +23 -0
  1963. scitex/scholar/url_finder/translators/individual/medline_nbib.py +23 -0
  1964. scitex/scholar/url_finder/translators/individual/medlinenbib.py +23 -0
  1965. scitex/scholar/url_finder/translators/individual/medra.py +23 -0
  1966. scitex/scholar/url_finder/translators/individual/metalib.py +23 -0
  1967. scitex/scholar/url_finder/translators/individual/mets.py +23 -0
  1968. scitex/scholar/url_finder/translators/individual/microbiology_society_journals.py +25 -0
  1969. scitex/scholar/url_finder/translators/individual/microsoft_academic.py +23 -0
  1970. scitex/scholar/url_finder/translators/individual/midas_journals.py +25 -0
  1971. scitex/scholar/url_finder/translators/individual/mikromarc.py +23 -0
  1972. scitex/scholar/url_finder/translators/individual/milli_kutuphane.py +23 -0
  1973. scitex/scholar/url_finder/translators/individual/mit_press_books.py +23 -0
  1974. scitex/scholar/url_finder/translators/individual/mods.py +23 -0
  1975. scitex/scholar/url_finder/translators/individual/mpg_pure.py +23 -0
  1976. scitex/scholar/url_finder/translators/individual/musee_du_louvre.py +23 -0
  1977. scitex/scholar/url_finder/translators/individual/nagoya_university_opac.py +25 -0
  1978. scitex/scholar/url_finder/translators/individual/nasa_ads.py +23 -0
  1979. scitex/scholar/url_finder/translators/individual/nasa_ntrs.py +23 -0
  1980. scitex/scholar/url_finder/translators/individual/national_academies_press.py +23 -0
  1981. scitex/scholar/url_finder/translators/individual/national_agriculture_library.py +23 -0
  1982. scitex/scholar/url_finder/translators/individual/national_archives_of_australia.py +23 -0
  1983. scitex/scholar/url_finder/translators/individual/national_archives_of_south_africa.py +23 -0
  1984. scitex/scholar/url_finder/translators/individual/national_bureau_of_economic_research.py +23 -0
  1985. scitex/scholar/url_finder/translators/individual/national_diet_library_catalogue.py +23 -0
  1986. scitex/scholar/url_finder/translators/individual/national_gallery_of_art___usa.py +23 -0
  1987. scitex/scholar/url_finder/translators/individual/national_gallery_of_australia.py +23 -0
  1988. scitex/scholar/url_finder/translators/individual/national_library_of_australia__new_catalog_.py +23 -0
  1989. scitex/scholar/url_finder/translators/individual/national_library_of_belarus.py +23 -0
  1990. scitex/scholar/url_finder/translators/individual/national_library_of_norway.py +23 -0
  1991. scitex/scholar/url_finder/translators/individual/national_library_of_poland_isbn.py +23 -0
  1992. scitex/scholar/url_finder/translators/individual/national_post.py +23 -0
  1993. scitex/scholar/url_finder/translators/individual/national_technical_reports_library.py +23 -0
  1994. scitex/scholar/url_finder/translators/individual/national_transportation_library_rosa_p.py +23 -0
  1995. scitex/scholar/url_finder/translators/individual/nature.py +23 -0
  1996. scitex/scholar/url_finder/translators/individual/nature_publishing_group.py +193 -0
  1997. scitex/scholar/url_finder/translators/individual/nber.py +23 -0
  1998. scitex/scholar/url_finder/translators/individual/ncbi_nucleotide.py +23 -0
  1999. scitex/scholar/url_finder/translators/individual/neural_information_processing_systems.py +23 -0
  2000. scitex/scholar/url_finder/translators/individual/new_left_review.py +23 -0
  2001. scitex/scholar/url_finder/translators/individual/new_zealand_herald.py +23 -0
  2002. scitex/scholar/url_finder/translators/individual/newlines_magazine.py +23 -0
  2003. scitex/scholar/url_finder/translators/individual/news_corp_australia.py +23 -0
  2004. scitex/scholar/url_finder/translators/individual/newsbank.py +23 -0
  2005. scitex/scholar/url_finder/translators/individual/newshub_co_nz.py +23 -0
  2006. scitex/scholar/url_finder/translators/individual/newsnettamedia.py +23 -0
  2007. scitex/scholar/url_finder/translators/individual/newspapers_com.py +23 -0
  2008. scitex/scholar/url_finder/translators/individual/noor_digital_library.py +23 -0
  2009. scitex/scholar/url_finder/translators/individual/note_html.py +23 -0
  2010. scitex/scholar/url_finder/translators/individual/note_markdown.py +23 -0
  2011. scitex/scholar/url_finder/translators/individual/notre_dame_philosophical_reviews.py +23 -0
  2012. scitex/scholar/url_finder/translators/individual/npr.py +23 -0
  2013. scitex/scholar/url_finder/translators/individual/nrc_nl.py +23 -0
  2014. scitex/scholar/url_finder/translators/individual/nrc_research_press.py +23 -0
  2015. scitex/scholar/url_finder/translators/individual/ntsb_accident_reports.py +25 -0
  2016. scitex/scholar/url_finder/translators/individual/nypl_menus.py +23 -0
  2017. scitex/scholar/url_finder/translators/individual/nypl_research_catalog.py +23 -0
  2018. scitex/scholar/url_finder/translators/individual/nytimes_com.py +23 -0
  2019. scitex/scholar/url_finder/translators/individual/nzz_ch.py +23 -0
  2020. scitex/scholar/url_finder/translators/individual/oapen.py +23 -0
  2021. scitex/scholar/url_finder/translators/individual/oclc_worldcat_firstsearch.py +23 -0
  2022. scitex/scholar/url_finder/translators/individual/oecd.py +23 -0
  2023. scitex/scholar/url_finder/translators/individual/oecd_ilibrary.py +23 -0
  2024. scitex/scholar/url_finder/translators/individual/ohiolink.py +23 -0
  2025. scitex/scholar/url_finder/translators/individual/open_conf.py +23 -0
  2026. scitex/scholar/url_finder/translators/individual/open_knowledge_repository.py +23 -0
  2027. scitex/scholar/url_finder/translators/individual/open_worldcat.py +23 -0
  2028. scitex/scholar/url_finder/translators/individual/openalex.py +23 -0
  2029. scitex/scholar/url_finder/translators/individual/openalex_json.py +23 -0
  2030. scitex/scholar/url_finder/translators/individual/openedition_books.py +23 -0
  2031. scitex/scholar/url_finder/translators/individual/openedition_journals.py +161 -0
  2032. scitex/scholar/url_finder/translators/individual/openjur.py +23 -0
  2033. scitex/scholar/url_finder/translators/individual/optical_society_of_america.py +23 -0
  2034. scitex/scholar/url_finder/translators/individual/optimization_online.py +23 -0
  2035. scitex/scholar/url_finder/translators/individual/orcid.py +286 -0
  2036. scitex/scholar/url_finder/translators/individual/osa.py +23 -0
  2037. scitex/scholar/url_finder/translators/individual/osf_preprints.py +23 -0
  2038. scitex/scholar/url_finder/translators/individual/osti_energy_citations.py +23 -0
  2039. scitex/scholar/url_finder/translators/individual/ovid.py +23 -0
  2040. scitex/scholar/url_finder/translators/individual/ovid_tagged.py +23 -0
  2041. scitex/scholar/url_finder/translators/individual/oxford.py +148 -0
  2042. scitex/scholar/url_finder/translators/individual/oxford_dictionaries_premium.py +25 -0
  2043. scitex/scholar/url_finder/translators/individual/oxford_english_dictionary.py +23 -0
  2044. scitex/scholar/url_finder/translators/individual/oxford_music_and_art_online.py +23 -0
  2045. scitex/scholar/url_finder/translators/individual/oxford_reference.py +23 -0
  2046. scitex/scholar/url_finder/translators/individual/oxford_university_press.py +23 -0
  2047. scitex/scholar/url_finder/translators/individual/ozon_ru.py +25 -0
  2048. scitex/scholar/url_finder/translators/individual/pajhwok_afghan_news.py +23 -0
  2049. scitex/scholar/url_finder/translators/individual/papers_past.py +23 -0
  2050. scitex/scholar/url_finder/translators/individual/paris_review.py +23 -0
  2051. scitex/scholar/url_finder/translators/individual/pastebin.py +23 -0
  2052. scitex/scholar/url_finder/translators/individual/patents___uspto.py +23 -0
  2053. scitex/scholar/url_finder/translators/individual/pc_gamer.py +23 -0
  2054. scitex/scholar/url_finder/translators/individual/pc_games.py +23 -0
  2055. scitex/scholar/url_finder/translators/individual/peeters.py +23 -0
  2056. scitex/scholar/url_finder/translators/individual/pei_archival_information_network.py +23 -0
  2057. scitex/scholar/url_finder/translators/individual/pep_web.py +23 -0
  2058. scitex/scholar/url_finder/translators/individual/perceiving_systems.py +23 -0
  2059. scitex/scholar/url_finder/translators/individual/perlego.py +23 -0
  2060. scitex/scholar/url_finder/translators/individual/philosopher_s_imprint.py +23 -0
  2061. scitex/scholar/url_finder/translators/individual/philpapers.py +23 -0
  2062. scitex/scholar/url_finder/translators/individual/pkp_catalog_systems.py +23 -0
  2063. scitex/scholar/url_finder/translators/individual/pleade.py +23 -0
  2064. scitex/scholar/url_finder/translators/individual/plos.py +111 -0
  2065. scitex/scholar/url_finder/translators/individual/plos_journals.py +23 -0
  2066. scitex/scholar/url_finder/translators/individual/pnas.py +23 -0
  2067. scitex/scholar/url_finder/translators/individual/polygon.py +25 -0
  2068. scitex/scholar/url_finder/translators/individual/potsdamer_neueste_nachrichten.py +23 -0
  2069. scitex/scholar/url_finder/translators/individual/prc_history_review.py +23 -0
  2070. scitex/scholar/url_finder/translators/individual/preprints_org.py +23 -0
  2071. scitex/scholar/url_finder/translators/individual/primo.py +25 -0
  2072. scitex/scholar/url_finder/translators/individual/primo_2018.py +23 -0
  2073. scitex/scholar/url_finder/translators/individual/primo_normalized_xml.py +23 -0
  2074. scitex/scholar/url_finder/translators/individual/probing_the_past.py +23 -0
  2075. scitex/scholar/url_finder/translators/individual/project_gutenberg.py +23 -0
  2076. scitex/scholar/url_finder/translators/individual/project_muse.py +25 -0
  2077. scitex/scholar/url_finder/translators/individual/promed.py +23 -0
  2078. scitex/scholar/url_finder/translators/individual/proquest.py +23 -0
  2079. scitex/scholar/url_finder/translators/individual/proquest_ebook_central.py +23 -0
  2080. scitex/scholar/url_finder/translators/individual/proquest_policyfile.py +23 -0
  2081. scitex/scholar/url_finder/translators/individual/protein_data_bank.py +23 -0
  2082. scitex/scholar/url_finder/translators/individual/pubfactory_journals.py +25 -0
  2083. scitex/scholar/url_finder/translators/individual/public_record_office_victoria.py +23 -0
  2084. scitex/scholar/url_finder/translators/individual/publications_du_quebec.py +23 -0
  2085. scitex/scholar/url_finder/translators/individual/publications_office_of_the_european_union.py +23 -0
  2086. scitex/scholar/url_finder/translators/individual/pubmed.py +330 -0
  2087. scitex/scholar/url_finder/translators/individual/pubmed_central.py +71 -0
  2088. scitex/scholar/url_finder/translators/individual/pubmed_xml.py +23 -0
  2089. scitex/scholar/url_finder/translators/individual/pubpub.py +23 -0
  2090. scitex/scholar/url_finder/translators/individual/pypi.py +23 -0
  2091. scitex/scholar/url_finder/translators/individual/qatar_digital_library.py +23 -0
  2092. scitex/scholar/url_finder/translators/individual/queensland_state_archives.py +23 -0
  2093. scitex/scholar/url_finder/translators/individual/r_packages.py +23 -0
  2094. scitex/scholar/url_finder/translators/individual/radio_free_europe__radio_liberty.py +23 -0
  2095. scitex/scholar/url_finder/translators/individual/rand.py +23 -0
  2096. scitex/scholar/url_finder/translators/individual/rdf.py +23 -0
  2097. scitex/scholar/url_finder/translators/individual/rechtspraak_nl.py +25 -0
  2098. scitex/scholar/url_finder/translators/individual/redalyc.py +23 -0
  2099. scitex/scholar/url_finder/translators/individual/reddit.py +23 -0
  2100. scitex/scholar/url_finder/translators/individual/referbibix.py +23 -0
  2101. scitex/scholar/url_finder/translators/individual/refworks_tagged.py +23 -0
  2102. scitex/scholar/url_finder/translators/individual/regeringskansliet.py +23 -0
  2103. scitex/scholar/url_finder/translators/individual/repec___econpapers.py +23 -0
  2104. scitex/scholar/url_finder/translators/individual/repec___ideas.py +23 -0
  2105. scitex/scholar/url_finder/translators/individual/repec_ideas.py +23 -0
  2106. scitex/scholar/url_finder/translators/individual/research_square.py +23 -0
  2107. scitex/scholar/url_finder/translators/individual/researchgate.py +23 -0
  2108. scitex/scholar/url_finder/translators/individual/retsinformation.py +23 -0
  2109. scitex/scholar/url_finder/translators/individual/reuters.py +23 -0
  2110. scitex/scholar/url_finder/translators/individual/ris.py +23 -0
  2111. scitex/scholar/url_finder/translators/individual/rock__paper__shotgun.py +23 -0
  2112. scitex/scholar/url_finder/translators/individual/roll_call.py +23 -0
  2113. scitex/scholar/url_finder/translators/individual/rsc.py +23 -0
  2114. scitex/scholar/url_finder/translators/individual/rsc_publishing.py +23 -0
  2115. scitex/scholar/url_finder/translators/individual/russian_state_library.py +23 -0
  2116. scitex/scholar/url_finder/translators/individual/sacramento_bee.py +23 -0
  2117. scitex/scholar/url_finder/translators/individual/sae_papers.py +23 -0
  2118. scitex/scholar/url_finder/translators/individual/safari_books_online.py +23 -0
  2119. scitex/scholar/url_finder/translators/individual/sage.py +23 -0
  2120. scitex/scholar/url_finder/translators/individual/sage_journals.py +183 -0
  2121. scitex/scholar/url_finder/translators/individual/sage_knowledge.py +23 -0
  2122. scitex/scholar/url_finder/translators/individual/saildart.py +23 -0
  2123. scitex/scholar/url_finder/translators/individual/salt_research_archives.py +23 -0
  2124. scitex/scholar/url_finder/translators/individual/sbn_it.py +23 -0
  2125. scitex/scholar/url_finder/translators/individual/scholars_portal_journals.py +23 -0
  2126. scitex/scholar/url_finder/translators/individual/scholia.py +23 -0
  2127. scitex/scholar/url_finder/translators/individual/schweizer_radio_und_fernsehen_srf.py +23 -0
  2128. scitex/scholar/url_finder/translators/individual/scielo.py +23 -0
  2129. scitex/scholar/url_finder/translators/individual/sciencedirect.py +132 -0
  2130. scitex/scholar/url_finder/translators/individual/scinapse.py +110 -0
  2131. scitex/scholar/url_finder/translators/individual/scopus.py +23 -0
  2132. scitex/scholar/url_finder/translators/individual/semantic_scholar.py +23 -0
  2133. scitex/scholar/url_finder/translators/individual/semantics_visual_library.py +25 -0
  2134. scitex/scholar/url_finder/translators/individual/sfu_ipinch.py +23 -0
  2135. scitex/scholar/url_finder/translators/individual/silverchair.py +301 -0
  2136. scitex/scholar/url_finder/translators/individual/sipri.py +23 -0
  2137. scitex/scholar/url_finder/translators/individual/sirs_knowledge_source.py +23 -0
  2138. scitex/scholar/url_finder/translators/individual/slate.py +23 -0
  2139. scitex/scholar/url_finder/translators/individual/slideshare.py +23 -0
  2140. scitex/scholar/url_finder/translators/individual/slub_dresden.py +23 -0
  2141. scitex/scholar/url_finder/translators/individual/sora.py +25 -0
  2142. scitex/scholar/url_finder/translators/individual/springer.py +121 -0
  2143. scitex/scholar/url_finder/translators/individual/springer_link.py +23 -0
  2144. scitex/scholar/url_finder/translators/individual/ssoar.py +23 -0
  2145. scitex/scholar/url_finder/translators/individual/ssrn.py +63 -0
  2146. scitex/scholar/url_finder/translators/individual/stack_exchange.py +23 -0
  2147. scitex/scholar/url_finder/translators/individual/standard_ebooks.py +23 -0
  2148. scitex/scholar/url_finder/translators/individual/stanford_encyclopedia_of_philosophy.py +25 -0
  2149. scitex/scholar/url_finder/translators/individual/stanford_university_press.py +23 -0
  2150. scitex/scholar/url_finder/translators/individual/state_records_office_of_western_australia.py +23 -0
  2151. scitex/scholar/url_finder/translators/individual/state_records_office_wa.py +23 -0
  2152. scitex/scholar/url_finder/translators/individual/stitcher.py +23 -0
  2153. scitex/scholar/url_finder/translators/individual/store_norske_leksikon.py +23 -0
  2154. scitex/scholar/url_finder/translators/individual/stuff_co_nz.py +23 -0
  2155. scitex/scholar/url_finder/translators/individual/substack.py +23 -0
  2156. scitex/scholar/url_finder/translators/individual/sud_ouest.py +23 -0
  2157. scitex/scholar/url_finder/translators/individual/sueddeutsche_de.py +23 -0
  2158. scitex/scholar/url_finder/translators/individual/summon_2.py +23 -0
  2159. scitex/scholar/url_finder/translators/individual/superlib.py +25 -0
  2160. scitex/scholar/url_finder/translators/individual/svenska_dagbladet.py +23 -0
  2161. scitex/scholar/url_finder/translators/individual/sveriges_radio.py +23 -0
  2162. scitex/scholar/url_finder/translators/individual/svt_nyheter.py +23 -0
  2163. scitex/scholar/url_finder/translators/individual/tagesspiegel.py +23 -0
  2164. scitex/scholar/url_finder/translators/individual/talis_aspire.py +23 -0
  2165. scitex/scholar/url_finder/translators/individual/talisprism.py +23 -0
  2166. scitex/scholar/url_finder/translators/individual/tatknigafund.py +23 -0
  2167. scitex/scholar/url_finder/translators/individual/tatpressa_ru.py +23 -0
  2168. scitex/scholar/url_finder/translators/individual/taylor___francis_ebooks.py +23 -0
  2169. scitex/scholar/url_finder/translators/individual/taylor_and_francis_nejm.py +23 -0
  2170. scitex/scholar/url_finder/translators/individual/taylor_francis.py +23 -0
  2171. scitex/scholar/url_finder/translators/individual/taylor_francis_nejm.py +23 -0
  2172. scitex/scholar/url_finder/translators/individual/taz_de.py +23 -0
  2173. scitex/scholar/url_finder/translators/individual/tei.py +23 -0
  2174. scitex/scholar/url_finder/translators/individual/tesis_doctorals_en_xarxa.py +23 -0
  2175. scitex/scholar/url_finder/translators/individual/the_art_newspaper.py +23 -0
  2176. scitex/scholar/url_finder/translators/individual/the_atlantic.py +23 -0
  2177. scitex/scholar/url_finder/translators/individual/the_boston_globe.py +23 -0
  2178. scitex/scholar/url_finder/translators/individual/the_chronicle_of_higher_education.py +23 -0
  2179. scitex/scholar/url_finder/translators/individual/the_daily_beast.py +23 -0
  2180. scitex/scholar/url_finder/translators/individual/the_economic_times___the_times_of_india.py +23 -0
  2181. scitex/scholar/url_finder/translators/individual/the_economist.py +23 -0
  2182. scitex/scholar/url_finder/translators/individual/the_free_dictionary.py +23 -0
  2183. scitex/scholar/url_finder/translators/individual/the_globe_and_mail.py +23 -0
  2184. scitex/scholar/url_finder/translators/individual/the_guardian.py +23 -0
  2185. scitex/scholar/url_finder/translators/individual/the_hamilton_spectator.py +23 -0
  2186. scitex/scholar/url_finder/translators/individual/the_hindu.py +23 -0
  2187. scitex/scholar/url_finder/translators/individual/the_independent.py +23 -0
  2188. scitex/scholar/url_finder/translators/individual/the_intercept.py +23 -0
  2189. scitex/scholar/url_finder/translators/individual/the_met.py +23 -0
  2190. scitex/scholar/url_finder/translators/individual/the_microfinance_gateway.py +23 -0
  2191. scitex/scholar/url_finder/translators/individual/the_nation.py +23 -0
  2192. scitex/scholar/url_finder/translators/individual/the_national_archives__uk_.py +23 -0
  2193. scitex/scholar/url_finder/translators/individual/the_new_republic.py +23 -0
  2194. scitex/scholar/url_finder/translators/individual/the_new_york_review_of_books.py +23 -0
  2195. scitex/scholar/url_finder/translators/individual/the_new_yorker.py +23 -0
  2196. scitex/scholar/url_finder/translators/individual/the_open_library.py +23 -0
  2197. scitex/scholar/url_finder/translators/individual/the_straits_times.py +23 -0
  2198. scitex/scholar/url_finder/translators/individual/the_telegraph.py +23 -0
  2199. scitex/scholar/url_finder/translators/individual/the_times_and_sunday_times.py +23 -0
  2200. scitex/scholar/url_finder/translators/individual/the_times_of_israel.py +23 -0
  2201. scitex/scholar/url_finder/translators/individual/themarker.py +23 -0
  2202. scitex/scholar/url_finder/translators/individual/theory_of_computing.py +23 -0
  2203. scitex/scholar/url_finder/translators/individual/thesesfr.py +23 -0
  2204. scitex/scholar/url_finder/translators/individual/thieme.py +23 -0
  2205. scitex/scholar/url_finder/translators/individual/time_com.py +23 -0
  2206. scitex/scholar/url_finder/translators/individual/timesmachine.py +23 -0
  2207. scitex/scholar/url_finder/translators/individual/tony_blair_institute.py +23 -0
  2208. scitex/scholar/url_finder/translators/individual/tony_blair_institute_for_global_change.py +23 -0
  2209. scitex/scholar/url_finder/translators/individual/toronto_star.py +23 -0
  2210. scitex/scholar/url_finder/translators/individual/transportation_research_board.py +23 -0
  2211. scitex/scholar/url_finder/translators/individual/treesearch.py +25 -0
  2212. scitex/scholar/url_finder/translators/individual/trove.py +23 -0
  2213. scitex/scholar/url_finder/translators/individual/tumblr.py +23 -0
  2214. scitex/scholar/url_finder/translators/individual/tv_by_the_numbers.py +23 -0
  2215. scitex/scholar/url_finder/translators/individual/tvnz.py +23 -0
  2216. scitex/scholar/url_finder/translators/individual/twitter.py +23 -0
  2217. scitex/scholar/url_finder/translators/individual/ubiquity_journals.py +23 -0
  2218. scitex/scholar/url_finder/translators/individual/uchicago_vufind.py +23 -0
  2219. scitex/scholar/url_finder/translators/individual/unapi.py +23 -0
  2220. scitex/scholar/url_finder/translators/individual/unesco.py +23 -0
  2221. scitex/scholar/url_finder/translators/individual/university_of_california_press_books.py +23 -0
  2222. scitex/scholar/url_finder/translators/individual/university_of_chicago_press_books.py +25 -0
  2223. scitex/scholar/url_finder/translators/individual/university_of_wisconsin_madison_libraries_catalog.py +23 -0
  2224. scitex/scholar/url_finder/translators/individual/university_press_scholarship.py +23 -0
  2225. scitex/scholar/url_finder/translators/individual/unqualified_dublin_core_rdf.py +23 -0
  2226. scitex/scholar/url_finder/translators/individual/unz_print_archive.py +23 -0
  2227. scitex/scholar/url_finder/translators/individual/upcommons.py +23 -0
  2228. scitex/scholar/url_finder/translators/individual/uptodate_references.py +23 -0
  2229. scitex/scholar/url_finder/translators/individual/us_national_archives_research_catalog.py +23 -0
  2230. scitex/scholar/url_finder/translators/individual/usenix.py +23 -0
  2231. scitex/scholar/url_finder/translators/individual/vanity_fair.py +23 -0
  2232. scitex/scholar/url_finder/translators/individual/verniana.py +23 -0
  2233. scitex/scholar/url_finder/translators/individual/verniana_jules_verne_studies.py +23 -0
  2234. scitex/scholar/url_finder/translators/individual/verso_books.py +23 -0
  2235. scitex/scholar/url_finder/translators/individual/vice.py +23 -0
  2236. scitex/scholar/url_finder/translators/individual/victoria___albert_museum.py +23 -0
  2237. scitex/scholar/url_finder/translators/individual/vimeo.py +23 -0
  2238. scitex/scholar/url_finder/translators/individual/vlex.py +25 -0
  2239. scitex/scholar/url_finder/translators/individual/voxeu.py +23 -0
  2240. scitex/scholar/url_finder/translators/individual/wall_street_journal.py +23 -0
  2241. scitex/scholar/url_finder/translators/individual/wanfang_data.py +23 -0
  2242. scitex/scholar/url_finder/translators/individual/washington_monthly.py +23 -0
  2243. scitex/scholar/url_finder/translators/individual/washington_post.py +23 -0
  2244. scitex/scholar/url_finder/translators/individual/web_of_science.py +25 -0
  2245. scitex/scholar/url_finder/translators/individual/web_of_science_nextgen.py +25 -0
  2246. scitex/scholar/url_finder/translators/individual/web_of_science_tagged.py +23 -0
  2247. scitex/scholar/url_finder/translators/individual/welt_online.py +23 -0
  2248. scitex/scholar/url_finder/translators/individual/westlaw_uk.py +23 -0
  2249. scitex/scholar/url_finder/translators/individual/who.py +23 -0
  2250. scitex/scholar/url_finder/translators/individual/wikidata.py +23 -0
  2251. scitex/scholar/url_finder/translators/individual/wikidata_quickstatements.py +23 -0
  2252. scitex/scholar/url_finder/translators/individual/wikileaks_plusd.py +23 -0
  2253. scitex/scholar/url_finder/translators/individual/wikimedia_commons.py +23 -0
  2254. scitex/scholar/url_finder/translators/individual/wikipedia.py +23 -0
  2255. scitex/scholar/url_finder/translators/individual/wikipedia_citation_templates.py +23 -0
  2256. scitex/scholar/url_finder/translators/individual/wikisource.py +23 -0
  2257. scitex/scholar/url_finder/translators/individual/wikiwand.py +23 -0
  2258. scitex/scholar/url_finder/translators/individual/wiktionary.py +23 -0
  2259. scitex/scholar/url_finder/translators/individual/wildlife_biology_in_practice.py +23 -0
  2260. scitex/scholar/url_finder/translators/individual/wiley.py +187 -0
  2261. scitex/scholar/url_finder/translators/individual/wiley_online_library.py +23 -0
  2262. scitex/scholar/url_finder/translators/individual/wilson_center_digital_archive.py +25 -0
  2263. scitex/scholar/url_finder/translators/individual/winnipeg_free_press.py +23 -0
  2264. scitex/scholar/url_finder/translators/individual/wipo.py +23 -0
  2265. scitex/scholar/url_finder/translators/individual/wired.py +23 -0
  2266. scitex/scholar/url_finder/translators/individual/wiso.py +23 -0
  2267. scitex/scholar/url_finder/translators/individual/womennews.py +23 -0
  2268. scitex/scholar/url_finder/translators/individual/world_bank.py +23 -0
  2269. scitex/scholar/url_finder/translators/individual/world_digital_library.py +23 -0
  2270. scitex/scholar/url_finder/translators/individual/world_history_connected.py +23 -0
  2271. scitex/scholar/url_finder/translators/individual/world_shakespeare_bibliography_online.py +23 -0
  2272. scitex/scholar/url_finder/translators/individual/worldcat_discovery_service.py +23 -0
  2273. scitex/scholar/url_finder/translators/individual/xml_contextobject.py +23 -0
  2274. scitex/scholar/url_finder/translators/individual/yandex_books.py +23 -0
  2275. scitex/scholar/url_finder/translators/individual/ynet.py +23 -0
  2276. scitex/scholar/url_finder/translators/individual/youtube.py +23 -0
  2277. scitex/scholar/url_finder/translators/individual/ypfs.py +23 -0
  2278. scitex/scholar/url_finder/translators/individual/ypsf.py +23 -0
  2279. scitex/scholar/url_finder/translators/individual/zbmath.py +102 -0
  2280. scitex/scholar/url_finder/translators/individual/zenodo.py +23 -0
  2281. scitex/scholar/url_finder/translators/individual/ziponline.py +23 -0
  2282. scitex/scholar/url_finder/translators/individual/zobodat.py +23 -0
  2283. scitex/scholar/url_finder/translators/individual/zotero_org.py +23 -0
  2284. scitex/scholar/url_finder/translators/individual/zotero_rdf.py +23 -0
  2285. scitex/scholar/url_finder/translators/individual/zoterobib.py +23 -0
  2286. scitex/scholar/utils/__init__.py +25 -0
  2287. scitex/scholar/utils/bibtex/__init__.py +9 -0
  2288. scitex/scholar/utils/bibtex/_parse_bibtex.py +71 -0
  2289. scitex/scholar/utils/cleanup/__init__.py +8 -0
  2290. scitex/scholar/utils/cleanup/_cleanup_scholar_processes.py +92 -0
  2291. scitex/scholar/utils/create_demo_movie.sh +49 -0
  2292. scitex/scholar/utils/text/_TextNormalizer.py +480 -0
  2293. scitex/scholar/utils/text/__init__.py +9 -0
  2294. scitex/scholar/utils/validation/DOIValidator.py +292 -0
  2295. scitex/scholar/utils/validation/README.md +169 -0
  2296. scitex/scholar/utils/validation/__init__.py +13 -0
  2297. scitex/scholar/utils/validation/validate_library_dois.py +225 -0
  2298. scitex/scholar/zotero/__init__.py +38 -0
  2299. scitex/security/README.md +255 -0
  2300. scitex/security/__init__.py +34 -0
  2301. scitex/security/cli.py +127 -0
  2302. scitex/security/github.py +371 -0
  2303. scitex/session/README.md +252 -0
  2304. scitex/session/__init__.py +55 -0
  2305. scitex/session/_decorator.py +631 -0
  2306. scitex/session/_lifecycle.py +827 -0
  2307. scitex/session/_manager.py +106 -0
  2308. scitex/session/template.py +29 -0
  2309. scitex/sh/README.md +58 -0
  2310. scitex/sh/__init__.py +93 -0
  2311. scitex/sh/_execute.py +224 -0
  2312. scitex/sh/_security.py +67 -0
  2313. scitex/sh/_types.py +28 -0
  2314. scitex/sh/test_sh.py +72 -0
  2315. scitex/sh/test_sh_simple.py +61 -0
  2316. scitex/stats/README.md +1085 -0
  2317. scitex/stats/__init__.py +350 -0
  2318. scitex/stats/__main__.py +281 -0
  2319. scitex/stats/_figrecipe_integration.py +116 -0
  2320. scitex/stats/_mcp/__init__.py +4 -0
  2321. scitex/stats/_mcp/handlers.py +1191 -0
  2322. scitex/stats/_mcp/tool_schemas.py +384 -0
  2323. scitex/stats/_schema.py +50 -0
  2324. scitex/stats/auto/__init__.py +187 -0
  2325. scitex/stats/auto/_context.py +331 -0
  2326. scitex/stats/auto/_formatting.py +679 -0
  2327. scitex/stats/auto/_rules.py +901 -0
  2328. scitex/stats/auto/_selector.py +554 -0
  2329. scitex/stats/auto/_styles.py +721 -0
  2330. scitex/stats/correct/__init__.py +21 -0
  2331. scitex/stats/correct/_correct_bonferroni.py +560 -0
  2332. scitex/stats/correct/_correct_fdr.py +608 -0
  2333. scitex/stats/correct/_correct_fdr_.py +627 -0
  2334. scitex/stats/correct/_correct_holm.py +530 -0
  2335. scitex/stats/correct/_correct_sidak.py +514 -0
  2336. scitex/stats/descriptive/__init__.py +85 -0
  2337. scitex/stats/descriptive/_circular.py +400 -0
  2338. scitex/stats/descriptive/_describe.py +182 -0
  2339. scitex/stats/descriptive/_nan.py +288 -0
  2340. scitex/stats/descriptive/_real.py +176 -0
  2341. scitex/stats/effect_sizes/__init__.py +40 -0
  2342. scitex/stats/effect_sizes/_cliffs_delta.py +328 -0
  2343. scitex/stats/effect_sizes/_cohens_d.py +341 -0
  2344. scitex/stats/effect_sizes/_epsilon_squared.py +312 -0
  2345. scitex/stats/effect_sizes/_eta_squared.py +298 -0
  2346. scitex/stats/effect_sizes/_prob_superiority.py +293 -0
  2347. scitex/stats/io/__init__.py +28 -0
  2348. scitex/stats/io/_bundle.py +156 -0
  2349. scitex/stats/mcp_server.py +405 -0
  2350. scitex/stats/posthoc/__init__.py +19 -0
  2351. scitex/stats/posthoc/_dunnett.py +483 -0
  2352. scitex/stats/posthoc/_games_howell.py +401 -0
  2353. scitex/stats/posthoc/_tukey_hsd.py +375 -0
  2354. scitex/stats/power/__init__.py +19 -0
  2355. scitex/stats/power/_power.py +433 -0
  2356. scitex/stats/run_all.sh +46 -0
  2357. scitex/stats/tests/__init__.py +13 -0
  2358. scitex/stats/tests/correlation/__init__.py +13 -0
  2359. scitex/stats/tests/correlation/_test_pearson.py +252 -0
  2360. scitex/stats/utils/__init__.py +59 -0
  2361. scitex/stats/utils/_effect_size.py +992 -0
  2362. scitex/stats/utils/_formatters.py +276 -0
  2363. scitex/stats/utils/_normalizers.py +923 -0
  2364. scitex/stats/utils/_power.py +433 -0
  2365. scitex/str/__init__.py +112 -0
  2366. scitex/str/_clean_path.py +79 -0
  2367. scitex/str/_color_text.py +52 -0
  2368. scitex/str/_decapitalize.py +58 -0
  2369. scitex/str/_factor_out_digits.py +309 -0
  2370. scitex/str/_format_plot_text.py +567 -0
  2371. scitex/str/_grep.py +48 -0
  2372. scitex/str/_latex.py +97 -0
  2373. scitex/str/_latex_fallback.py +583 -0
  2374. scitex/str/_mask_api.py +39 -0
  2375. scitex/str/_mask_api_key.py +8 -0
  2376. scitex/str/_parse.py +163 -0
  2377. scitex/str/_print_block.py +47 -0
  2378. scitex/str/_print_debug.py +68 -0
  2379. scitex/str/_printc.py +62 -0
  2380. scitex/str/_readable_bytes.py +38 -0
  2381. scitex/str/_remove_ansi.py +23 -0
  2382. scitex/str/_replace.py +134 -0
  2383. scitex/str/_search.py +127 -0
  2384. scitex/str/_squeeze_space.py +36 -0
  2385. scitex/template/README.md +154 -0
  2386. scitex/template/__init__.py +115 -0
  2387. scitex/template/_clone_project.py +167 -0
  2388. scitex/template/_copy.py +105 -0
  2389. scitex/template/_customize.py +116 -0
  2390. scitex/template/_git_strategy.py +122 -0
  2391. scitex/template/_logging_helpers.py +122 -0
  2392. scitex/template/_mcp/__init__.py +4 -0
  2393. scitex/template/_mcp/handlers.py +259 -0
  2394. scitex/template/_mcp/tool_schemas.py +112 -0
  2395. scitex/template/_rename.py +65 -0
  2396. scitex/template/clone_pip_project.py +106 -0
  2397. scitex/template/clone_research.py +106 -0
  2398. scitex/template/clone_singularity.py +106 -0
  2399. scitex/template/clone_writer_directory.py +106 -0
  2400. scitex/template/mcp_server.py +186 -0
  2401. scitex/tex/__init__.py +14 -0
  2402. scitex/tex/_export.py +890 -0
  2403. scitex/tex/_preview.py +118 -0
  2404. scitex/tex/_to_vec.py +119 -0
  2405. scitex/torch/__init__.py +28 -0
  2406. scitex/torch/_apply_to.py +34 -0
  2407. scitex/torch/_nan_funcs.py +77 -0
  2408. scitex/types/_ArrayLike.py +66 -0
  2409. scitex/types/_ColorLike.py +21 -0
  2410. scitex/types/__init__.py +14 -0
  2411. scitex/types/_is_listed_X.py +70 -0
  2412. scitex/ui/__init__.py +173 -0
  2413. scitex/ui/_backends/__init__.py +66 -0
  2414. scitex/ui/_backends/_audio.py +86 -0
  2415. scitex/ui/_backends/_config.py +250 -0
  2416. scitex/ui/_backends/_desktop.py +184 -0
  2417. scitex/ui/_backends/_emacs.py +210 -0
  2418. scitex/ui/_backends/_email.py +78 -0
  2419. scitex/ui/_backends/_matplotlib.py +118 -0
  2420. scitex/ui/_backends/_playwright.py +109 -0
  2421. scitex/ui/_backends/_types.py +58 -0
  2422. scitex/ui/_backends/_webhook.py +77 -0
  2423. scitex/ui/_backends.py +37 -0
  2424. scitex/ui/_mcp/__init__.py +23 -0
  2425. scitex/ui/_mcp/handlers.py +260 -0
  2426. scitex/ui/_mcp/tool_schemas.py +107 -0
  2427. scitex/ui/mcp_server.py +151 -0
  2428. scitex/units.py +324 -0
  2429. scitex/utils/__init__.py +22 -0
  2430. scitex/utils/_compress_hdf5.py +127 -0
  2431. scitex/utils/_email.py +221 -0
  2432. scitex/utils/_grid.py +150 -0
  2433. scitex/utils/_notify.py +267 -0
  2434. scitex/utils/_search.py +123 -0
  2435. scitex/utils/_verify_scitex_format.py +564 -0
  2436. scitex/utils/_verify_scitex_format_v01.py +348 -0
  2437. scitex/utils/template.py +123 -0
  2438. scitex/web/__init__.py +49 -0
  2439. scitex/web/_scraping.py +150 -0
  2440. scitex/web/_search_pubmed.py +501 -0
  2441. scitex/web/_summarize_url.py +158 -0
  2442. scitex/web/download_images.py +316 -0
  2443. scitex/writer/README.md +435 -0
  2444. scitex/writer/Writer.py +487 -0
  2445. scitex/writer/__init__.py +46 -0
  2446. scitex/writer/_clone_writer_project.py +160 -0
  2447. scitex/writer/_compile/__init__.py +41 -0
  2448. scitex/writer/_compile/_compile_async.py +130 -0
  2449. scitex/writer/_compile/_compile_unified.py +148 -0
  2450. scitex/writer/_compile/_parser.py +63 -0
  2451. scitex/writer/_compile/_runner.py +457 -0
  2452. scitex/writer/_compile/_validator.py +46 -0
  2453. scitex/writer/_compile/manuscript.py +110 -0
  2454. scitex/writer/_compile/revision.py +82 -0
  2455. scitex/writer/_compile/supplementary.py +100 -0
  2456. scitex/writer/_dataclasses/__init__.py +44 -0
  2457. scitex/writer/_dataclasses/config/_CONSTANTS.py +46 -0
  2458. scitex/writer/_dataclasses/config/_WriterConfig.py +175 -0
  2459. scitex/writer/_dataclasses/config/__init__.py +9 -0
  2460. scitex/writer/_dataclasses/contents/_ManuscriptContents.py +236 -0
  2461. scitex/writer/_dataclasses/contents/_RevisionContents.py +136 -0
  2462. scitex/writer/_dataclasses/contents/_SupplementaryContents.py +114 -0
  2463. scitex/writer/_dataclasses/contents/__init__.py +9 -0
  2464. scitex/writer/_dataclasses/core/_Document.py +146 -0
  2465. scitex/writer/_dataclasses/core/_DocumentSection.py +546 -0
  2466. scitex/writer/_dataclasses/core/__init__.py +7 -0
  2467. scitex/writer/_dataclasses/results/_CompilationResult.py +165 -0
  2468. scitex/writer/_dataclasses/results/_LaTeXIssue.py +102 -0
  2469. scitex/writer/_dataclasses/results/_SaveSectionsResponse.py +118 -0
  2470. scitex/writer/_dataclasses/results/_SectionReadResponse.py +131 -0
  2471. scitex/writer/_dataclasses/results/__init__.py +11 -0
  2472. scitex/writer/_dataclasses/tree/MINIMUM_FILES.md +121 -0
  2473. scitex/writer/_dataclasses/tree/_ConfigTree.py +86 -0
  2474. scitex/writer/_dataclasses/tree/_ManuscriptTree.py +84 -0
  2475. scitex/writer/_dataclasses/tree/_RevisionTree.py +97 -0
  2476. scitex/writer/_dataclasses/tree/_ScriptsTree.py +118 -0
  2477. scitex/writer/_dataclasses/tree/_SharedTree.py +100 -0
  2478. scitex/writer/_dataclasses/tree/_SupplementaryTree.py +101 -0
  2479. scitex/writer/_dataclasses/tree/__init__.py +23 -0
  2480. scitex/writer/_mcp/__init__.py +4 -0
  2481. scitex/writer/_mcp/handlers.py +765 -0
  2482. scitex/writer/_mcp/tool_schemas.py +363 -0
  2483. scitex/writer/_project/__init__.py +29 -0
  2484. scitex/writer/_project/_create.py +89 -0
  2485. scitex/writer/_project/_trees.py +63 -0
  2486. scitex/writer/_project/_validate.py +61 -0
  2487. scitex/writer/utils/.legacy_git_retry.py +164 -0
  2488. scitex/writer/utils/__init__.py +24 -0
  2489. scitex/writer/utils/_converters.py +635 -0
  2490. scitex/writer/utils/_parse_latex_logs.py +138 -0
  2491. scitex/writer/utils/_parse_script_args.py +156 -0
  2492. scitex/writer/utils/_verify_tree_structure.py +205 -0
  2493. scitex/writer/utils/_watch.py +96 -0
  2494. scitex-2.14.0.dist-info/METADATA +1238 -0
  2495. scitex-2.14.0.dist-info/RECORD +2498 -0
  2496. scitex-2.14.0.dist-info/WHEEL +4 -0
  2497. scitex-2.14.0.dist-info/entry_points.txt +12 -0
  2498. scitex-2.14.0.dist-info/licenses/LICENSE +661 -0
@@ -0,0 +1,3384 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # Timestamp: "2025-11-19 13:00:00 (ywatanabe)"
4
+ # File: /home/ywatanabe/proj/scitex-code/src/scitex/plt/utils/_collect_figure_metadata.py
5
+
6
+ """
7
+ Collect metadata from matplotlib figures for embedding in saved images.
8
+
9
+ This module provides utilities to automatically extract dimension, styling,
10
+ and configuration information from matplotlib figures and axes, making saved
11
+ figures self-documenting and reproducible.
12
+ """
13
+
14
+ __FILE__ = __file__
15
+
16
+ from typing import Dict, Optional, Union, List
17
+
18
+ from scitex import logging
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+ # Precision settings for JSON output
23
+ PRECISION = {
24
+ "mm": 2, # Millimeters: 0.01mm precision (10 microns)
25
+ "inch": 3, # Inches: 0.001 inch precision
26
+ "position": 3, # Normalized position: 0.001 precision
27
+ "lim": 2, # Axis limits: 2 decimal places
28
+ "linewidth": 2, # Line widths: 0.01 precision
29
+ }
30
+
31
+
32
+ class FixedFloat:
33
+ """
34
+ A float wrapper that preserves fixed decimal places in JSON output.
35
+
36
+ Example: FixedFloat(0.25, 3) -> "0.250" in JSON
37
+ """
38
+ def __init__(self, value: float, precision: int):
39
+ self.value = round(value, precision)
40
+ self.precision = precision
41
+
42
+ def __repr__(self):
43
+ return f"{self.value:.{self.precision}f}"
44
+
45
+ def __float__(self):
46
+ return self.value
47
+
48
+
49
+ def _round_value(value: Union[float, int], precision: int, fixed: bool = False) -> Union[float, int, "FixedFloat"]:
50
+ """
51
+ Round a single value to specified precision.
52
+
53
+ Parameters
54
+ ----------
55
+ value : float or int
56
+ Value to round
57
+ precision : int
58
+ Number of decimal places
59
+ fixed : bool
60
+ If True, return FixedFloat with fixed decimal places (e.g., 0.250)
61
+ If False, return float (e.g., 0.25)
62
+ """
63
+ if isinstance(value, int):
64
+ if fixed:
65
+ return FixedFloat(float(value), precision)
66
+ return value
67
+ if isinstance(value, float):
68
+ if fixed:
69
+ return FixedFloat(value, precision)
70
+ return round(value, precision)
71
+ return value
72
+
73
+
74
+ def _round_list(values: List, precision: int, fixed: bool = False) -> List:
75
+ """Round all values in a list."""
76
+ return [_round_value(v, precision, fixed) for v in values]
77
+
78
+
79
+ def _round_dict(d: dict, precision_map: dict = None) -> dict:
80
+ """
81
+ Round all float values in a dict based on key-specific precision.
82
+
83
+ Parameters
84
+ ----------
85
+ d : dict
86
+ Dictionary to process
87
+ precision_map : dict, optional
88
+ Mapping of key patterns to precision values.
89
+ Default uses PRECISION settings based on key names.
90
+ """
91
+ if precision_map is None:
92
+ precision_map = {}
93
+
94
+ result = {}
95
+ for key, value in d.items():
96
+ # Determine precision based on key name
97
+ if "mm" in key.lower():
98
+ prec = PRECISION["mm"]
99
+ elif "inch" in key.lower():
100
+ prec = PRECISION["inch"]
101
+ elif "position" in key.lower() or key in ("left", "bottom", "right", "top"):
102
+ prec = PRECISION["position"]
103
+ elif "lim" in key.lower():
104
+ prec = PRECISION["lim"]
105
+ elif "width" in key.lower() and "line" in key.lower():
106
+ prec = PRECISION["linewidth"]
107
+ else:
108
+ prec = precision_map.get(key, 3) # Default 3 decimals
109
+
110
+ if isinstance(value, dict):
111
+ result[key] = _round_dict(value, precision_map)
112
+ elif isinstance(value, list):
113
+ result[key] = _round_list(value, prec)
114
+ elif isinstance(value, float):
115
+ result[key] = _round_value(value, prec)
116
+ else:
117
+ result[key] = value
118
+
119
+ return result
120
+
121
+
122
+ def _collect_single_axes_metadata(fig, ax, ax_index: int) -> dict:
123
+ """
124
+ Collect metadata for a single axes object.
125
+
126
+ Parameters
127
+ ----------
128
+ fig : matplotlib.figure.Figure
129
+ The parent figure
130
+ ax : matplotlib.axes.Axes
131
+ The axes to collect metadata from
132
+ ax_index : int
133
+ Index of this axes in the figure (for position tracking)
134
+
135
+ Returns
136
+ -------
137
+ dict
138
+ Metadata dictionary for this axes containing:
139
+ - size_mm, size_inch, size_px
140
+ - position_ratio
141
+ - position_in_grid
142
+ - margins_mm, margins_inch
143
+ - bbox_mm, bbox_inch, bbox_px
144
+ - x_axis_bottom, y_axis_left (axis info)
145
+ """
146
+ ax_metadata = {}
147
+
148
+ try:
149
+ from ._figure_from_axes_mm import get_dimension_info
150
+
151
+ dim_info = get_dimension_info(fig, ax)
152
+
153
+ # Size in multiple units
154
+ ax_metadata["size_mm"] = dim_info.get("axes_size_mm", [])
155
+ if "axes_size_inch" in dim_info:
156
+ ax_metadata["size_inch"] = dim_info["axes_size_inch"]
157
+ if "axes_size_px" in dim_info:
158
+ ax_metadata["size_px"] = dim_info["axes_size_px"]
159
+
160
+ # Position in figure coordinates (normalized 0-1 values)
161
+ # Uses matplotlib terminology: bounds_figure_fraction
162
+ if "axes_position" in dim_info:
163
+ ax_metadata["bounds_figure_fraction"] = list(dim_info["axes_position"])
164
+
165
+ # Position in grid (row, col)
166
+ if hasattr(ax, "_scitex_metadata") and "position_in_grid" in ax._scitex_metadata:
167
+ ax_metadata["position_in_grid"] = ax._scitex_metadata["position_in_grid"]
168
+ else:
169
+ # Calculate from ax_index if we have grid info
170
+ ax_metadata["position_in_grid"] = [ax_index, 0] # Default single column
171
+
172
+ # Margins in mm and inch
173
+ if "margins_mm" in dim_info:
174
+ ax_metadata["margins_mm"] = dim_info["margins_mm"]
175
+ if "margins_inch" in dim_info:
176
+ ax_metadata["margins_inch"] = dim_info["margins_inch"]
177
+
178
+ # Bounding box with intuitive keys
179
+ if "axes_bbox_px" in dim_info:
180
+ bbox = dim_info["axes_bbox_px"]
181
+ # Convert from x0/y0/x1/y1 to x_left/y_bottom/x_right/y_top
182
+ ax_metadata["bbox_px"] = {
183
+ "x_left": bbox.get("x0", bbox.get("x_left", 0)),
184
+ "x_right": bbox.get("x1", bbox.get("x_right", 0)),
185
+ "y_top": bbox.get("y0", bbox.get("y_top", 0)),
186
+ "y_bottom": bbox.get("y1", bbox.get("y_bottom", 0)),
187
+ "width": bbox.get("width", 0),
188
+ "height": bbox.get("height", 0),
189
+ }
190
+ if "axes_bbox_mm" in dim_info:
191
+ bbox = dim_info["axes_bbox_mm"]
192
+ ax_metadata["bbox_mm"] = {
193
+ "x_left": bbox.get("x0", bbox.get("x_left", 0)),
194
+ "x_right": bbox.get("x1", bbox.get("x_right", 0)),
195
+ "y_top": bbox.get("y0", bbox.get("y_top", 0)),
196
+ "y_bottom": bbox.get("y1", bbox.get("y_bottom", 0)),
197
+ "width": bbox.get("width", 0),
198
+ "height": bbox.get("height", 0),
199
+ }
200
+ if "axes_bbox_inch" in dim_info:
201
+ bbox = dim_info["axes_bbox_inch"]
202
+ ax_metadata["bbox_inch"] = {
203
+ "x_left": bbox.get("x0", bbox.get("x_left", 0)),
204
+ "x_right": bbox.get("x1", bbox.get("x_right", 0)),
205
+ "y_top": bbox.get("y0", bbox.get("y_top", 0)),
206
+ "y_bottom": bbox.get("y1", bbox.get("y_bottom", 0)),
207
+ "width": bbox.get("width", 0),
208
+ "height": bbox.get("height", 0),
209
+ }
210
+
211
+ except Exception as e:
212
+ logger.warning(f"Could not extract dimension info for axes {ax_index}: {e}")
213
+
214
+ # Extract axes labels and units
215
+ # X-axis - using matplotlib terminology (xaxis)
216
+ xlabel = ax.get_xlabel()
217
+ x_label, x_unit = _parse_label_unit(xlabel)
218
+ ax_metadata["xaxis"] = {
219
+ "label": x_label,
220
+ "unit": x_unit,
221
+ "scale": ax.get_xscale(),
222
+ "lim": list(ax.get_xlim()),
223
+ }
224
+
225
+ # Y-axis - using matplotlib terminology (yaxis)
226
+ ylabel = ax.get_ylabel()
227
+ y_label, y_unit = _parse_label_unit(ylabel)
228
+ ax_metadata["yaxis"] = {
229
+ "label": y_label,
230
+ "unit": y_unit,
231
+ "scale": ax.get_yscale(),
232
+ "lim": list(ax.get_ylim()),
233
+ }
234
+
235
+ return ax_metadata
236
+
237
+
238
+ def _restructure_style(flat_style: dict) -> dict:
239
+ """
240
+ Restructure flat style_mm dict into hierarchical structure with explicit scopes.
241
+
242
+ Converts:
243
+ {"axis_thickness_mm": 0.2, "tick_length_mm": 0.8, ...}
244
+ To:
245
+ {
246
+ "global": {"fonts": {...}, "padding": {...}},
247
+ "axes_default": {"axes": {...}, "ticks": {...}},
248
+ "artist_default": {"lines": {...}, "markers": {...}}
249
+ }
250
+
251
+ Style scopes:
252
+ - global: rcParams-like settings (fonts, padding) applied to entire figure
253
+ - axes_default: default axes appearance (can be overridden per-axes)
254
+ - artist_default: default artist appearance (can be overridden per-artist)
255
+ """
256
+ result = {
257
+ "global": {
258
+ "fonts": {},
259
+ "padding": {},
260
+ },
261
+ "axes_default": {
262
+ "axes": {},
263
+ "ticks": {},
264
+ },
265
+ "artist_default": {
266
+ "lines": {},
267
+ "markers": {},
268
+ },
269
+ }
270
+
271
+ # Mapping from flat keys to hierarchical structure (scope, category, key)
272
+ key_mapping = {
273
+ # Axes-level defaults
274
+ "axis_thickness_mm": ("axes_default", "axes", "thickness_mm"),
275
+ "axes_thickness_mm": ("axes_default", "axes", "thickness_mm"),
276
+ "tick_length_mm": ("axes_default", "ticks", "length_mm"),
277
+ "tick_thickness_mm": ("axes_default", "ticks", "thickness_mm"),
278
+ "n_ticks": ("axes_default", "ticks", "n_ticks"),
279
+ # Artist-level defaults (Line2D, markers)
280
+ "trace_thickness_mm": ("artist_default", "lines", "thickness_mm"),
281
+ "line_thickness_mm": ("artist_default", "lines", "thickness_mm"),
282
+ "marker_size_mm": ("artist_default", "markers", "size_mm"),
283
+ "scatter_size_mm": ("artist_default", "markers", "scatter_size_mm"),
284
+ # Global defaults (rcParams-like)
285
+ "font_family": ("global", "fonts", "family"),
286
+ "font_family_requested": ("global", "fonts", "family_requested"),
287
+ "font_family_actual": ("global", "fonts", "family_actual"),
288
+ "axis_font_size_pt": ("global", "fonts", "axis_size_pt"),
289
+ "tick_font_size_pt": ("global", "fonts", "tick_size_pt"),
290
+ "title_font_size_pt": ("global", "fonts", "title_size_pt"),
291
+ "legend_font_size_pt": ("global", "fonts", "legend_size_pt"),
292
+ "suptitle_font_size_pt": ("global", "fonts", "suptitle_size_pt"),
293
+ "annotation_font_size_pt": ("global", "fonts", "annotation_size_pt"),
294
+ "label_pad_pt": ("global", "padding", "label_pt"),
295
+ "tick_pad_pt": ("global", "padding", "tick_pt"),
296
+ "title_pad_pt": ("global", "padding", "title_pt"),
297
+ }
298
+
299
+ for key, value in flat_style.items():
300
+ if key in key_mapping:
301
+ scope, category, new_key = key_mapping[key]
302
+ result[scope][category][new_key] = value
303
+ else:
304
+ # Unknown keys go to a misc section or are kept at top level
305
+ # For now, skip unknown keys to keep structure clean
306
+ pass
307
+
308
+ # Remove empty categories within each scope
309
+ for scope in list(result.keys()):
310
+ result[scope] = {k: v for k, v in result[scope].items() if v}
311
+ # Remove empty scopes
312
+ if not result[scope]:
313
+ del result[scope]
314
+
315
+ return result
316
+
317
+
318
+ def collect_figure_metadata(fig, ax=None) -> Dict:
319
+ """
320
+ Collect all metadata from figure and axes for embedding in saved images.
321
+
322
+ This function automatically extracts:
323
+ - Software versions (scitex, matplotlib)
324
+ - Timestamp
325
+ - Figure UUID (unique identifier)
326
+ - Figure/axes dimensions (mm, inch, px)
327
+ - DPI settings
328
+ - Margins
329
+ - Styling parameters (if available)
330
+ - Mode (display/publication)
331
+ - Creation method
332
+ - Plot type and axes information
333
+
334
+ Parameters
335
+ ----------
336
+ fig : matplotlib.figure.Figure
337
+ Figure to collect metadata from
338
+ ax : matplotlib.axes.Axes, optional
339
+ Primary axes to collect dimension info from.
340
+ If not provided, uses first axes in figure.
341
+
342
+ Returns
343
+ -------
344
+ dict
345
+ Complete metadata dictionary ready for embedding via scitex.io.embed_metadata()
346
+
347
+ Examples
348
+ --------
349
+ >>> from scitex.plt.utils import create_axes_with_size_mm, collect_figure_metadata
350
+ >>> fig, ax = create_axes_with_size_mm(30, 21, mode='publication')
351
+ >>> ax.plot(x, y)
352
+ >>> metadata = collect_figure_metadata(fig, ax)
353
+ >>> print(metadata['dimensions']['axes_size_mm'])
354
+ (30.0, 21.0)
355
+
356
+ Notes
357
+ -----
358
+ This function is automatically called by FigWrapper.savefig() when
359
+ embed_metadata=True (the default). You typically don't need to call it manually.
360
+
361
+ The collected metadata enables:
362
+ - Reproducing exact figure dimensions later
363
+ - Matching styling across multiple figures
364
+ - Documenting figure provenance
365
+ - Debugging dimension/DPI issues
366
+ """
367
+ import datetime
368
+ import uuid
369
+
370
+ import matplotlib
371
+ import scitex
372
+
373
+ # Base metadata with cleaner structure:
374
+ # - runtime: software/creation info
375
+ # - figure: figure-level properties
376
+ # - axes: axes-level properties
377
+ # - style: styling parameters
378
+ # - plot: plot content (title, type, traces, legend)
379
+ # - data: CSV linkage (path, hash, columns)
380
+ metadata = {
381
+ "scitex_schema": "scitex.plt.figure",
382
+ "scitex_schema_version": "0.1.0",
383
+ "figure_uuid": str(uuid.uuid4()),
384
+ "runtime": {
385
+ "scitex_version": scitex.__version__,
386
+ "matplotlib_version": matplotlib.__version__,
387
+ "created_at": datetime.datetime.now().isoformat(),
388
+ },
389
+ }
390
+
391
+ # Collect all axes from figure
392
+ # Keep AxisWrappers for metadata access, but also track grid shape
393
+ all_axes = [] # List of (ax_wrapper_or_mpl, row, col) tuples
394
+ grid_shape = (1, 1) # Default single axes
395
+
396
+ if ax is not None:
397
+ # Handle AxesWrapper (multi-axes) - extract individual AxisWrappers with positions
398
+ if hasattr(ax, "_axes_scitex"):
399
+ import numpy as np
400
+ axes_array = ax._axes_scitex
401
+ if isinstance(axes_array, np.ndarray):
402
+ grid_shape = axes_array.shape
403
+ for idx, ax_item in enumerate(axes_array.flat):
404
+ row = idx // grid_shape[1]
405
+ col = idx % grid_shape[1]
406
+ all_axes.append((ax_item, row, col))
407
+ else:
408
+ all_axes = [(axes_array, 0, 0)]
409
+ # Handle AxisWrapper (single axes)
410
+ elif hasattr(ax, "_axis_mpl"):
411
+ all_axes = [(ax, 0, 0)]
412
+ else:
413
+ # Assume it's a matplotlib axes
414
+ all_axes = [(ax, 0, 0)]
415
+ elif hasattr(fig, "axes") and len(fig.axes) > 0:
416
+ # Fallback to figure axes (linear indexing)
417
+ for idx, ax_item in enumerate(fig.axes):
418
+ all_axes.append((ax_item, 0, idx))
419
+
420
+ # Helper to unwrap AxisWrapper to matplotlib axes
421
+ def _unwrap_ax(ax_item):
422
+ if hasattr(ax_item, "_axis_mpl"):
423
+ return ax_item._axis_mpl
424
+ return ax_item
425
+
426
+ # Add figure-level properties (extracted from first axes for figure dimensions)
427
+ if all_axes:
428
+ try:
429
+ from ._figure_from_axes_mm import get_dimension_info
430
+
431
+ first_ax_tuple = all_axes[0]
432
+ first_ax_mpl = _unwrap_ax(first_ax_tuple[0])
433
+ dim_info = get_dimension_info(fig, first_ax_mpl)
434
+
435
+ metadata["figure"] = {
436
+ "size_mm": dim_info["figure_size_mm"],
437
+ "size_inch": dim_info["figure_size_inch"],
438
+ "size_px": dim_info["figure_size_px"],
439
+ "dpi": dim_info["dpi"],
440
+ }
441
+
442
+ # Add top-level axes_bbox_px for easy access by canvas/web editors
443
+ # Uses x0/y0/x1/y1 format (origin at top-left for web compatibility)
444
+ # x0: left edge (Y-axis position), y1: bottom edge (X-axis position)
445
+ if "axes_bbox_px" in dim_info:
446
+ metadata["axes_bbox_px"] = dim_info["axes_bbox_px"]
447
+ if "axes_bbox_mm" in dim_info:
448
+ metadata["axes_bbox_mm"] = dim_info["axes_bbox_mm"]
449
+ except Exception as e:
450
+ logger.warning(f"Could not extract figure dimension info: {e}")
451
+
452
+ # Collect per-axes metadata
453
+ if all_axes:
454
+ metadata["axes"] = {}
455
+ for ax_item, row, col in all_axes:
456
+ # Use row-col format: ax_00, ax_01, ax_10, ax_11 for 2x2 grid
457
+ ax_key = f"ax_{row}{col}"
458
+ try:
459
+ ax_mpl = _unwrap_ax(ax_item)
460
+ ax_metadata = _collect_single_axes_metadata(fig, ax_mpl, row * grid_shape[1] + col)
461
+ if ax_metadata:
462
+ # Add grid position info
463
+ ax_metadata["grid_position"] = {"row": row, "col": col}
464
+ metadata["axes"][ax_key] = ax_metadata
465
+ except Exception as e:
466
+ logger.warning(f"Could not extract metadata for {ax_key}: {e}")
467
+
468
+ # Add scitex-specific metadata if axes was tagged
469
+ scitex_meta = None
470
+ if ax is not None and hasattr(ax, "_scitex_metadata"):
471
+ scitex_meta = ax._scitex_metadata
472
+ elif hasattr(fig, "_scitex_metadata"):
473
+ scitex_meta = fig._scitex_metadata
474
+
475
+ if scitex_meta:
476
+ # Extract stats separately for top-level access
477
+ if "stats" in scitex_meta:
478
+ stats_list = scitex_meta["stats"]
479
+ # Determine first_ax_key from axes metadata
480
+ first_ax_key = None
481
+ if "axes" in metadata and metadata["axes"]:
482
+ first_ax_key = next(iter(metadata["axes"].keys()), None)
483
+ # Add plot_id and ax_id to each stats entry if not present
484
+ for stat in stats_list:
485
+ if isinstance(stat, dict):
486
+ # Try to get plot info from metadata
487
+ if stat.get("plot_id") is None:
488
+ if "plot" in metadata and "ax_id" in metadata["plot"]:
489
+ stat["plot_id"] = metadata["plot"]["ax_id"]
490
+ elif first_ax_key:
491
+ stat["plot_id"] = first_ax_key
492
+ if "ax_id" not in stat and first_ax_key:
493
+ stat["ax_id"] = first_ax_key
494
+ metadata["stats"] = stats_list
495
+
496
+ # Extract style_mm to dedicated "style" section with hierarchical structure
497
+ if "style_mm" in scitex_meta:
498
+ metadata["style"] = _restructure_style(scitex_meta["style_mm"])
499
+
500
+ # Extract mode to figure section
501
+ if "mode" in scitex_meta:
502
+ if "figure" not in metadata:
503
+ metadata["figure"] = {}
504
+ metadata["figure"]["mode"] = scitex_meta["mode"]
505
+
506
+ # Extract created_with to runtime section
507
+ if "created_with" in scitex_meta:
508
+ metadata["runtime"]["created_with"] = scitex_meta["created_with"]
509
+
510
+ # Note: axes_size_mm and position_in_grid are now handled per-axes
511
+ # in _collect_single_axes_metadata() and stored under axes.ax_00, axes.ax_01, etc.
512
+
513
+ # Add actual font information
514
+ try:
515
+ from ._get_actual_font import get_actual_font_name
516
+
517
+ actual_font = get_actual_font_name()
518
+
519
+ # Store both requested and actual font in style.global.fonts section
520
+ if "style" in metadata:
521
+ # Ensure global.fonts section exists
522
+ if "global" not in metadata["style"]:
523
+ metadata["style"]["global"] = {}
524
+ if "fonts" not in metadata["style"]["global"]:
525
+ metadata["style"]["global"]["fonts"] = {}
526
+
527
+ # Get requested font from global.fonts.family or default to Arial
528
+ requested_font = metadata["style"]["global"]["fonts"].get("family", "Arial")
529
+ # Remove redundant family - keep only family_requested and family_actual
530
+ if "family" in metadata["style"]["global"]["fonts"]:
531
+ del metadata["style"]["global"]["fonts"]["family"]
532
+ metadata["style"]["global"]["fonts"]["family_requested"] = requested_font
533
+ metadata["style"]["global"]["fonts"]["family_actual"] = actual_font
534
+
535
+ # Warn if requested and actual fonts differ
536
+ if requested_font != actual_font:
537
+ try:
538
+ from scitex.logging import getLogger
539
+
540
+ logger = getLogger(__name__)
541
+ logger.warning(
542
+ f"Font mismatch: Requested '{requested_font}' but using '{actual_font}'. "
543
+ f"For {requested_font}: sudo apt-get install ttf-mscorefonts-installer && fc-cache -fv"
544
+ )
545
+ except ImportError:
546
+ logger.warning(
547
+ f"Font mismatch: Requested '{requested_font}' but using '{actual_font}'"
548
+ )
549
+ else:
550
+ # If no style section, add font info to runtime section
551
+ metadata["runtime"]["font_family_actual"] = actual_font
552
+ except Exception:
553
+ # If font detection fails, continue without it
554
+ pass
555
+
556
+ # Extract plot content and axes labels
557
+ # For multi-axes figures, we need to handle AxesWrapper specially
558
+ primary_ax = ax
559
+ if ax is not None:
560
+ # Handle AxesWrapper (multi-axes) - use first axis for primary plot info
561
+ if hasattr(ax, "_axes_scitex"):
562
+ import numpy as np
563
+ axes_array = ax._axes_scitex
564
+ if isinstance(axes_array, np.ndarray) and axes_array.size > 0:
565
+ primary_ax = axes_array.flat[0]
566
+ else:
567
+ primary_ax = axes_array
568
+
569
+ if primary_ax is not None:
570
+ try:
571
+ # Try to get scitex AxisWrapper for history access
572
+ # This is needed because matplotlib axes don't have the tracking history
573
+ ax_for_history = primary_ax
574
+
575
+ # If ax is a raw matplotlib axes, try to find the scitex wrapper
576
+ if not hasattr(primary_ax, 'history'):
577
+ # Check if primary_ax has a scitex wrapper stored on it
578
+ if hasattr(primary_ax, '_scitex_wrapper'):
579
+ ax_for_history = primary_ax._scitex_wrapper
580
+ # Check if figure has scitex axes reference
581
+ elif hasattr(fig, 'axes') and hasattr(fig.axes, 'history'):
582
+ ax_for_history = fig.axes
583
+ # Check for FigWrapper's axes attribute
584
+ elif hasattr(fig, '_fig_scitex') and hasattr(fig._fig_scitex, 'axes'):
585
+ ax_for_history = fig._fig_scitex.axes
586
+ # Check if the figure object itself has scitex_axes
587
+ elif hasattr(fig, '_scitex_axes'):
588
+ ax_for_history = fig._scitex_axes
589
+
590
+ # Add n_ticks to axes metadata if available from style
591
+ if "style" in metadata and "ticks" in metadata["style"] and "n_ticks" in metadata["style"]["ticks"]:
592
+ n_ticks = metadata["style"]["ticks"]["n_ticks"]
593
+ # Add n_ticks to each axes' axis info
594
+ if "axes" in metadata:
595
+ for ax_key in metadata["axes"]:
596
+ ax_data = metadata["axes"][ax_key]
597
+ if "xaxis" in ax_data:
598
+ ax_data["xaxis"]["n_ticks"] = n_ticks
599
+ if "yaxis" in ax_data:
600
+ ax_data["yaxis"]["n_ticks"] = n_ticks
601
+
602
+ # Initialize plot section for plot content
603
+ plot_info = {}
604
+
605
+ # Add ax_id to match the axes key in metadata["axes"]
606
+ # This links plot info to the corresponding axes entry
607
+ ax_row, ax_col = 0, 0 # Default for single axes
608
+ if hasattr(primary_ax, "_scitex_metadata") and "position_in_grid" in primary_ax._scitex_metadata:
609
+ pos = primary_ax._scitex_metadata["position_in_grid"]
610
+ ax_row, ax_col = pos[0], pos[1]
611
+ # Use same format as axes keys: ax_00, ax_01, etc.
612
+ plot_info["ax_id"] = f"ax_{ax_row:02d}" if ax_row == ax_col == 0 else f"ax_{ax_row * 10 + ax_col:02d}"
613
+
614
+ # Extract title - use underlying matplotlib axes if needed
615
+ ax_mpl = primary_ax._axis_mpl if hasattr(primary_ax, '_axis_mpl') else primary_ax
616
+ title = ax_mpl.get_title()
617
+ if title:
618
+ plot_info["title"] = title
619
+
620
+ # Detect plot type and method from axes history or lines
621
+ # Use ax_for_history which has the scitex history if available
622
+ plot_type, method = _detect_plot_type(ax_for_history)
623
+ if plot_type:
624
+ plot_info["type"] = plot_type
625
+ if method:
626
+ plot_info["method"] = method
627
+
628
+ # Extract style preset if available
629
+ if (
630
+ hasattr(primary_ax, "_scitex_metadata")
631
+ and "style_preset" in primary_ax._scitex_metadata
632
+ ):
633
+ if "style" not in metadata:
634
+ metadata["style"] = {}
635
+ metadata["style"]["preset"] = primary_ax._scitex_metadata["style_preset"]
636
+ elif (
637
+ hasattr(fig, "_scitex_metadata")
638
+ and "style_preset" in fig._scitex_metadata
639
+ ):
640
+ if "style" not in metadata:
641
+ metadata["style"] = {}
642
+ metadata["style"]["preset"] = fig._scitex_metadata["style_preset"]
643
+
644
+ # Extract artists and legend - add to axes section (matplotlib terminology)
645
+ # Artists and legend belong to axes, not a separate plot section
646
+ ax_row, ax_col = 0, 0
647
+ if hasattr(primary_ax, "_scitex_metadata") and "position_in_grid" in primary_ax._scitex_metadata:
648
+ pos = primary_ax._scitex_metadata["position_in_grid"]
649
+ ax_row, ax_col = pos[0], pos[1]
650
+ ax_key = f"ax_{ax_row:02d}" if ax_row == ax_col == 0 else f"ax_{ax_row * 10 + ax_col:02d}"
651
+
652
+ if "axes" in metadata and ax_key in metadata["axes"]:
653
+ # Add artists to axes
654
+ artists = _extract_artists(primary_ax)
655
+ if artists:
656
+ metadata["axes"][ax_key]["artists"] = artists
657
+
658
+ # Add legend to axes
659
+ legend_info = _extract_legend_info(primary_ax)
660
+ if legend_info:
661
+ metadata["axes"][ax_key]["legend"] = legend_info
662
+
663
+ # Add plot section if we have content
664
+ if plot_info:
665
+ metadata["plot"] = plot_info
666
+
667
+ # Data section for CSV linkage
668
+ # Note: Per-trace column mappings are in plot.traces[i].csv_columns
669
+ # This section provides:
670
+ # - csv_hash: for verifying data integrity
671
+ # - csv_path: path to CSV file (added by _save.py)
672
+ # - columns_actual: actual column names in CSV (added by _save.py after export)
673
+ data_info = {}
674
+
675
+ # Compute CSV data hash for reproducibility verification
676
+ csv_hash = _compute_csv_hash(ax_for_history)
677
+ if csv_hash:
678
+ data_info["csv_hash"] = csv_hash
679
+
680
+ # csv_path and columns_actual will be added by _save.py after actual CSV export
681
+ # This ensures single source of truth - actual columns, not predictions
682
+
683
+ # Add data section if we have content
684
+ if data_info:
685
+ metadata["data"] = data_info
686
+
687
+ except Exception as e:
688
+ # If Phase 1 extraction fails, continue without it
689
+ logger.warning(f"Could not extract Phase 1 metadata: {e}")
690
+
691
+ # Apply precision rounding to all numeric values
692
+ metadata = _round_metadata(metadata)
693
+
694
+ return metadata
695
+
696
+
697
+ def _round_metadata(metadata: dict) -> dict:
698
+ """
699
+ Apply appropriate precision rounding to all numeric values in metadata.
700
+
701
+ Precision rules:
702
+ - mm values: 2 decimal places (0.01mm = 10 microns)
703
+ - inch values: 3 decimal places
704
+ - position values: 3 decimal places
705
+ - axis limits: 2 decimal places
706
+ - linewidth: 2 decimal places
707
+ - px values: integers (no decimals)
708
+ """
709
+ result = {}
710
+
711
+ for key, value in metadata.items():
712
+ if key in ("scitex_schema", "scitex_schema_version", "figure_uuid"):
713
+ # String fields - no rounding
714
+ result[key] = value
715
+ elif key == "runtime":
716
+ # Runtime section - no numeric values to round
717
+ result[key] = value
718
+ elif key == "figure":
719
+ result[key] = _round_figure_section(value)
720
+ elif key == "axes":
721
+ result[key] = _round_axes_section(value)
722
+ elif key == "style":
723
+ result[key] = _round_style_section(value)
724
+ elif key == "plot":
725
+ result[key] = _round_plot_section(value)
726
+ elif key == "data":
727
+ # Data section - no numeric values to round (hashes, paths, column names)
728
+ result[key] = value
729
+ elif key == "stats":
730
+ # Stats section - preserve precision for statistical values
731
+ result[key] = value
732
+ else:
733
+ result[key] = value
734
+
735
+ return result
736
+
737
+
738
+ def _round_figure_section(fig_data: dict) -> dict:
739
+ """Round values in figure section."""
740
+ result = {}
741
+ for key, value in fig_data.items():
742
+ if key == "size_mm":
743
+ # Fixed 2 decimals for mm: [80.00, 68.00]
744
+ result[key] = _round_list(value, PRECISION["mm"], fixed=True)
745
+ elif key == "size_inch":
746
+ # Fixed 3 decimals for inch: [3.150, 2.677]
747
+ result[key] = _round_list(value, PRECISION["inch"], fixed=True)
748
+ elif key == "size_px":
749
+ result[key] = [int(v) for v in value] # Pixels are integers
750
+ elif key == "dpi":
751
+ result[key] = int(value)
752
+ else:
753
+ result[key] = value
754
+ return result
755
+
756
+
757
+ def _round_axes_section(axes_data: dict) -> dict:
758
+ """Round values in axes section.
759
+
760
+ Handles both flat structure (legacy) and nested structure (ax_00, ax_01, ...).
761
+ """
762
+ result = {}
763
+ for key, value in axes_data.items():
764
+ # Check if this is a nested axes key (ax_00, ax_01, etc.)
765
+ if key.startswith("ax_") and isinstance(value, dict):
766
+ # Recursively round the nested axes data
767
+ result[key] = _round_single_axes_data(value)
768
+ else:
769
+ # Handle flat structure (legacy) or non-axes keys
770
+ result[key] = _round_single_axes_data({key: value}).get(key, value)
771
+ return result
772
+
773
+
774
+ def _round_single_axes_data(ax_data: dict) -> dict:
775
+ """Round values for a single axes' data."""
776
+ result = {}
777
+ for key, value in ax_data.items():
778
+ if key == "size_mm":
779
+ # Fixed 2 decimals: [40.00, 28.00]
780
+ result[key] = _round_list(value, PRECISION["mm"], fixed=True)
781
+ elif key == "size_inch":
782
+ # Fixed 3 decimals: [1.575, 1.102]
783
+ result[key] = _round_list(value, PRECISION["inch"], fixed=True)
784
+ elif key == "size_px":
785
+ result[key] = [int(v) for v in value]
786
+ elif key in ("position", "position_ratio", "bounds_figure_fraction"):
787
+ # Fixed 3 decimals: [0.250, 0.294, 0.500, 0.412]
788
+ result[key] = _round_list(value, PRECISION["position"], fixed=True)
789
+ elif key == "position_in_grid":
790
+ result[key] = [int(v) for v in value]
791
+ elif key == "margins_mm":
792
+ # Fixed 2 decimals: {"left": 20.00, ...}
793
+ result[key] = {k: _round_value(v, PRECISION["mm"], fixed=True) for k, v in value.items()}
794
+ elif key == "margins_inch":
795
+ # Fixed 3 decimals: {"left": 0.787, ...}
796
+ result[key] = {k: _round_value(v, PRECISION["inch"], fixed=True) for k, v in value.items()}
797
+ elif key == "bbox_mm":
798
+ # Fixed 2 decimals
799
+ result[key] = {k: _round_value(v, PRECISION["mm"], fixed=True) for k, v in value.items()}
800
+ elif key == "bbox_inch":
801
+ # Fixed 3 decimals
802
+ result[key] = {k: _round_value(v, PRECISION["inch"], fixed=True) for k, v in value.items()}
803
+ elif key == "bbox_px":
804
+ result[key] = {k: int(v) for k, v in value.items()}
805
+ elif key in ("xaxis", "yaxis", "xaxis_top", "yaxis_right"):
806
+ # Axis info (label, unit, scale, lim, n_ticks) - using matplotlib terminology
807
+ axis_result = {}
808
+ for ak, av in value.items():
809
+ if ak == "lim":
810
+ # Fixed 2 decimals for limits: [-0.31, 6.60]
811
+ axis_result[ak] = _round_list(av, PRECISION["lim"], fixed=True)
812
+ elif ak == "n_ticks":
813
+ axis_result[ak] = int(av)
814
+ else:
815
+ axis_result[ak] = av
816
+ result[key] = axis_result
817
+ elif key == "legend":
818
+ # Legend has no floats to round, pass through
819
+ result[key] = value
820
+ elif key == "artists":
821
+ # Round artist values
822
+ result[key] = [_round_artist(a) for a in value]
823
+ else:
824
+ result[key] = value
825
+ return result
826
+
827
+
828
+ def _round_style_section(style_data: dict) -> dict:
829
+ """Round values in hierarchical style section with scopes.
830
+
831
+ Handles structure like:
832
+ {
833
+ "global": {"fonts": {...}, "padding": {...}},
834
+ "axes_default": {"axes": {...}, "ticks": {...}},
835
+ "artist_default": {"lines": {...}, "markers": {...}}
836
+ }
837
+ """
838
+ result = {}
839
+ for scope, scope_data in style_data.items():
840
+ if scope in ("global", "axes_default", "artist_default"):
841
+ # Handle scope-level dict
842
+ result[scope] = {}
843
+ for category, category_data in scope_data.items():
844
+ if isinstance(category_data, dict):
845
+ result[scope][category] = _round_style_subsection(category, category_data)
846
+ else:
847
+ result[scope][category] = category_data
848
+ elif isinstance(scope_data, dict):
849
+ # Fallback for flat structure (backward compatibility)
850
+ result[scope] = _round_style_subsection(scope, scope_data)
851
+ elif isinstance(scope_data, float):
852
+ if "_mm" in scope:
853
+ result[scope] = _round_value(scope_data, PRECISION["mm"], fixed=True)
854
+ elif "_pt" in scope:
855
+ result[scope] = _round_value(scope_data, 1, fixed=True)
856
+ else:
857
+ result[scope] = _round_value(scope_data, 2)
858
+ elif isinstance(scope_data, int):
859
+ result[scope] = scope_data
860
+ else:
861
+ result[scope] = scope_data
862
+ return result
863
+
864
+
865
+ def _round_style_subsection(category: str, data: dict) -> dict:
866
+ """Round values in a style subsection based on category."""
867
+ result = {}
868
+ for key, value in data.items():
869
+ if isinstance(value, float):
870
+ if "_mm" in key or category in ("axes", "ticks", "lines", "markers"):
871
+ # mm values: 2 decimals
872
+ result[key] = _round_value(value, PRECISION["mm"], fixed=True)
873
+ elif "_pt" in key or category in ("fonts", "padding"):
874
+ # pt values: 1 decimal
875
+ result[key] = _round_value(value, 1, fixed=True)
876
+ else:
877
+ result[key] = _round_value(value, 2)
878
+ elif isinstance(value, int):
879
+ result[key] = value
880
+ else:
881
+ result[key] = value
882
+ return result
883
+
884
+
885
+ def _round_plot_section(plot_data: dict) -> dict:
886
+ """Round values in plot section."""
887
+ result = {}
888
+ for key, value in plot_data.items():
889
+ if key == "artists":
890
+ result[key] = [_round_artist(a) for a in value]
891
+ elif key == "legend":
892
+ result[key] = value # Legend has no floats to round
893
+ else:
894
+ result[key] = value
895
+ return result
896
+
897
+
898
+ def _round_artist(artist: dict) -> dict:
899
+ """Round values in a single artist."""
900
+ result = {}
901
+ for key, value in artist.items():
902
+ if key == "style" and isinstance(value, dict):
903
+ # Legacy: Round values in style dict (for backward compatibility)
904
+ style_result = {}
905
+ for sk, sv in value.items():
906
+ if sk in ("linewidth_pt", "markersize_pt"):
907
+ # Fixed 2 decimals: 0.57
908
+ style_result[sk] = _round_value(sv, PRECISION["linewidth"], fixed=True)
909
+ else:
910
+ style_result[sk] = sv
911
+ result[key] = style_result
912
+ elif key == "backend" and isinstance(value, dict):
913
+ # New two-layer structure: round values in backend.props
914
+ backend_result = {"name": value.get("name", "matplotlib")}
915
+ if "artist_class" in value:
916
+ backend_result["artist_class"] = value["artist_class"]
917
+ if "props" in value and isinstance(value["props"], dict):
918
+ props_result = {}
919
+ for pk, pv in value["props"].items():
920
+ if pk in ("linewidth_pt", "markersize_pt"):
921
+ # Fixed 2 decimals: 0.57
922
+ props_result[pk] = _round_value(pv, PRECISION["linewidth"], fixed=True)
923
+ elif pk == "size":
924
+ # Scatter size: 1 decimal
925
+ props_result[pk] = _round_value(pv, 1, fixed=True)
926
+ else:
927
+ props_result[pk] = pv
928
+ backend_result["props"] = props_result
929
+ result[key] = backend_result
930
+ elif key == "geometry" and isinstance(value, dict):
931
+ # Round geometry values (for bar charts)
932
+ geom_result = {}
933
+ for gk, gv in value.items():
934
+ if isinstance(gv, float):
935
+ geom_result[gk] = _round_value(gv, 4, fixed=False)
936
+ else:
937
+ geom_result[gk] = gv
938
+ result[key] = geom_result
939
+ elif key == "zorder":
940
+ result[key] = int(value) if isinstance(value, (int, float)) else value
941
+ else:
942
+ result[key] = value
943
+ return result
944
+
945
+
946
+ # Backward compatibility alias
947
+ _round_trace = _round_artist
948
+
949
+
950
+ def _parse_label_unit(label_text: str) -> tuple:
951
+ """
952
+ Parse label text to extract label and unit.
953
+
954
+ Handles formats like:
955
+ - "Time [s]" -> ("Time", "s")
956
+ - "Amplitude (a.u.)" -> ("Amplitude", "a.u.")
957
+ - "Value" -> ("Value", "")
958
+
959
+ Parameters
960
+ ----------
961
+ label_text : str
962
+ The full label text from axes
963
+
964
+ Returns
965
+ -------
966
+ tuple
967
+ (label, unit) where unit is empty string if not found
968
+ """
969
+ import re
970
+
971
+ if not label_text:
972
+ return "", ""
973
+
974
+ # Try to match [...] pattern first (preferred format)
975
+ match = re.match(r"^(.+?)\s*\[([^\]]+)\]$", label_text)
976
+ if match:
977
+ return match.group(1).strip(), match.group(2).strip()
978
+
979
+ # Try to match (...) pattern
980
+ match = re.match(r"^(.+?)\s*\(([^\)]+)\)$", label_text)
981
+ if match:
982
+ return match.group(1).strip(), match.group(2).strip()
983
+
984
+ # No unit found
985
+ return label_text.strip(), ""
986
+
987
+
988
+ def _get_csv_column_names(trace_id: str, ax_row: int = 0, ax_col: int = 0, variables: list = None) -> dict:
989
+ """
990
+ Get CSV column names using the single source of truth naming convention.
991
+
992
+ Format: ax-row-{row}-col-{col}_trace-id-{id}_variable-{var}
993
+
994
+ Parameters
995
+ ----------
996
+ trace_id : str
997
+ The trace identifier (e.g., "sine", "step")
998
+ ax_row : int
999
+ Row position of axes in grid (default: 0)
1000
+ ax_col : int
1001
+ Column position of axes in grid (default: 0)
1002
+ variables : list, optional
1003
+ List of variable names (default: ["x", "y"])
1004
+
1005
+ Returns
1006
+ -------
1007
+ dict
1008
+ Dictionary mapping variable names to CSV column names
1009
+ """
1010
+ from ._csv_column_naming import get_csv_column_name
1011
+
1012
+ if variables is None:
1013
+ variables = ["x", "y"]
1014
+
1015
+ data_ref = {}
1016
+ for var in variables:
1017
+ data_ref[var] = get_csv_column_name(var, ax_row, ax_col, trace_id=trace_id)
1018
+
1019
+ return data_ref
1020
+
1021
+
1022
+ def _extract_artists(ax) -> list:
1023
+ """
1024
+ Extract artist information including properties and CSV column mapping.
1025
+
1026
+ Uses matplotlib terminology: each drawable element is an Artist.
1027
+ Only includes artists that were explicitly created via scitex tracking (top-level calls),
1028
+ not internal artists created by matplotlib functions like boxplot() which internally
1029
+ call plot() multiple times.
1030
+
1031
+ Parameters
1032
+ ----------
1033
+ ax : matplotlib.axes.Axes
1034
+ The axes to extract artists from
1035
+
1036
+ Returns
1037
+ -------
1038
+ list
1039
+ List of artist dictionaries with:
1040
+ - id: unique identifier
1041
+ - artist_class: matplotlib class name (Line2D, PathCollection, etc.)
1042
+ - label: legend label
1043
+ - style: color, linestyle, linewidth, etc.
1044
+ - data_ref: CSV column mapping (matches columns_actual exactly)
1045
+ """
1046
+ import matplotlib.colors as mcolors
1047
+
1048
+ artists = []
1049
+
1050
+ # Get axes position for CSV column naming
1051
+ ax_row, ax_col = 0, 0 # Default for single axes
1052
+ if hasattr(ax, "_scitex_metadata") and "position_in_grid" in ax._scitex_metadata:
1053
+ pos = ax._scitex_metadata["position_in_grid"]
1054
+ ax_row, ax_col = pos[0], pos[1]
1055
+
1056
+ # Get the raw matplotlib axes for accessing lines
1057
+ mpl_ax = ax._axis_mpl if hasattr(ax, "_axis_mpl") else ax
1058
+
1059
+ # Try to find scitex wrapper for plot type detection and history access
1060
+ ax_for_detection = ax
1061
+ if not hasattr(ax, 'history') and hasattr(mpl_ax, '_scitex_wrapper'):
1062
+ ax_for_detection = mpl_ax._scitex_wrapper
1063
+
1064
+ # Check if we should filter to only tracked artists
1065
+ # For plot types that internally call plot (boxplot, errorbar, etc.),
1066
+ # we don't export the internal artists EXCEPT explicitly tracked ones
1067
+ plot_type, method = _detect_plot_type(ax_for_detection)
1068
+
1069
+ # Plot types where internal line artists should be hidden
1070
+ # But we still export artists that have explicit _scitex_id set
1071
+ # These plot types create Line2D objects internally that don't have
1072
+ # corresponding data in the CSV export
1073
+ # NOTE: scatter is NOT included here because scatter plots often have
1074
+ # regression lines that should be exported
1075
+ internal_plot_types = {
1076
+ "boxplot", "violin", "hist", "bar", "image", "heatmap", "kde", "ecdf",
1077
+ "errorbar", "fill", "stem", "contour", "pie", "quiver", "stream"
1078
+ }
1079
+
1080
+ skip_unlabeled = plot_type in internal_plot_types
1081
+
1082
+ # Build a map from scitex_id to full record from history
1083
+ # Record format: (tracking_id, method, tracked_dict, kwargs)
1084
+ id_to_history = {}
1085
+ if hasattr(ax_for_detection, "history"):
1086
+ for record_id, record in ax_for_detection.history.items():
1087
+ if isinstance(record, tuple) and len(record) >= 2:
1088
+ tracking_id = record[0] # The id used in tracking
1089
+ id_to_history[tracking_id] = record # Store full record
1090
+
1091
+ # Special handling for boxplot and violin - extract semantic components
1092
+ # Boxplot creates lines in a specific pattern: for n boxes, there are
1093
+ # typically: whiskers (2*n), caps (2*n), median (n), fliers (n)
1094
+ is_boxplot = plot_type == "boxplot"
1095
+ is_violin = plot_type == "violin"
1096
+ is_stem = plot_type == "stem"
1097
+
1098
+ # For boxplot, try to determine the number of boxes and compute stats from history
1099
+ num_boxes = 0
1100
+ boxplot_stats = [] # Will hold stats for each box
1101
+ boxplot_data = None
1102
+ if is_boxplot and hasattr(ax_for_detection, "history"):
1103
+ for record in ax_for_detection.history.values():
1104
+ if isinstance(record, tuple) and len(record) >= 3:
1105
+ method_name = record[1]
1106
+ if method_name == "boxplot":
1107
+ tracked_dict = record[2]
1108
+ args = tracked_dict.get("args", [])
1109
+ if args and len(args) > 0:
1110
+ data = args[0]
1111
+ if hasattr(data, '__len__') and not isinstance(data, str):
1112
+ # Check if it's list of arrays or single array
1113
+ if hasattr(data[0], '__len__') and not isinstance(data[0], str):
1114
+ num_boxes = len(data)
1115
+ boxplot_data = data
1116
+ else:
1117
+ num_boxes = 1
1118
+ boxplot_data = [data]
1119
+ break
1120
+
1121
+ # Compute boxplot statistics
1122
+ if boxplot_data is not None:
1123
+ import numpy as np
1124
+ for box_idx, box_data in enumerate(boxplot_data):
1125
+ try:
1126
+ arr = np.asarray(box_data)
1127
+ arr = arr[~np.isnan(arr)] # Remove NaN values
1128
+ if len(arr) > 0:
1129
+ q1 = float(np.percentile(arr, 25))
1130
+ median = float(np.median(arr))
1131
+ q3 = float(np.percentile(arr, 75))
1132
+ iqr = q3 - q1
1133
+ whisker_low = float(max(arr.min(), q1 - 1.5 * iqr))
1134
+ whisker_high = float(min(arr.max(), q3 + 1.5 * iqr))
1135
+ # Fliers are points outside whiskers
1136
+ fliers = arr[(arr < whisker_low) | (arr > whisker_high)]
1137
+ boxplot_stats.append({
1138
+ "box_index": box_idx,
1139
+ "median": median,
1140
+ "q1": q1,
1141
+ "q3": q3,
1142
+ "whisker_low": whisker_low,
1143
+ "whisker_high": whisker_high,
1144
+ "n_fliers": int(len(fliers)),
1145
+ "n_samples": int(len(arr)),
1146
+ })
1147
+ except (ValueError, TypeError):
1148
+ pass
1149
+
1150
+ for i, line in enumerate(mpl_ax.lines):
1151
+ # Get ID from _scitex_id attribute (set by scitex plotting functions)
1152
+ # This matches the id= kwarg passed to ax.plot()
1153
+ scitex_id = getattr(line, "_scitex_id", None)
1154
+
1155
+ # Get label for legend
1156
+ label = line.get_label()
1157
+
1158
+ # For internal plot types (boxplot, violin, etc.), skip Line2D artists
1159
+ # that were created internally by matplotlib (not explicitly tracked).
1160
+ # These internal artists don't have corresponding data in the CSV.
1161
+ # BUT: for boxplot/violin/stem, we want to export with semantic labels
1162
+ semantic_type = None
1163
+ semantic_id = None
1164
+ has_boxplot_stats = False
1165
+ box_idx = None
1166
+
1167
+ # For stem, always detect semantic type (even with scitex_id)
1168
+ if is_stem:
1169
+ marker = line.get_marker()
1170
+ linestyle = line.get_linestyle()
1171
+ if marker and marker != "None" and linestyle == "None":
1172
+ # This is the marker line (markers only, no connecting line)
1173
+ semantic_type = "stem_marker"
1174
+ semantic_id = "stem_markers"
1175
+ elif linestyle and linestyle != "None":
1176
+ # This is either stemlines or baseline
1177
+ # Check if it looks like a baseline (horizontal line at y=0)
1178
+ ydata = line.get_ydata()
1179
+ if len(ydata) >= 2 and len(set(ydata)) == 1:
1180
+ semantic_type = "stem_baseline"
1181
+ semantic_id = "stem_baseline"
1182
+ else:
1183
+ semantic_type = "stem_stem"
1184
+ semantic_id = "stem_lines"
1185
+ else:
1186
+ semantic_type = "stem_component"
1187
+ semantic_id = f"stem_{i}"
1188
+
1189
+ if skip_unlabeled and not scitex_id and label.startswith("_"):
1190
+ # For boxplot, assign semantic roles based on position in lines list
1191
+ if is_boxplot and num_boxes > 0:
1192
+ # Boxplot line order: whiskers (2*n), caps (2*n), medians (n), fliers (n)
1193
+ total_whiskers = 2 * num_boxes
1194
+ total_caps = 2 * num_boxes
1195
+ total_medians = num_boxes
1196
+
1197
+ if i < total_whiskers:
1198
+ box_idx = i // 2
1199
+ whisker_idx = i % 2
1200
+ semantic_type = "boxplot_whisker"
1201
+ semantic_id = f"box_{box_idx}_whisker_{whisker_idx}"
1202
+ elif i < total_whiskers + total_caps:
1203
+ cap_i = i - total_whiskers
1204
+ box_idx = cap_i // 2
1205
+ cap_idx = cap_i % 2
1206
+ semantic_type = "boxplot_cap"
1207
+ semantic_id = f"box_{box_idx}_cap_{cap_idx}"
1208
+ elif i < total_whiskers + total_caps + total_medians:
1209
+ box_idx = i - total_whiskers - total_caps
1210
+ semantic_type = "boxplot_median"
1211
+ semantic_id = f"box_{box_idx}_median"
1212
+ # Mark this as the primary element to hold stats
1213
+ has_boxplot_stats = True
1214
+ else:
1215
+ flier_idx = i - total_whiskers - total_caps - total_medians
1216
+ # Distribute fliers across boxes if we have fewer flier lines than boxes
1217
+ box_idx = flier_idx if flier_idx < num_boxes else num_boxes - 1
1218
+ semantic_type = "boxplot_flier"
1219
+ semantic_id = f"box_{box_idx}_flier"
1220
+ elif is_violin:
1221
+ # Violin typically has: bodies (patches), then optional lines
1222
+ semantic_type = "violin_component"
1223
+ semantic_id = f"violin_line_{i}"
1224
+ elif is_stem:
1225
+ # Already handled above
1226
+ pass
1227
+ else:
1228
+ continue # Skip for other internal plot types
1229
+
1230
+ artist = {}
1231
+
1232
+ # For scatter plots, check if this Line2D is a regression line
1233
+ is_regression_line = False
1234
+ if plot_type == "scatter" and label.startswith("_"):
1235
+ # Check if this looks like a regression line (straight line with few points)
1236
+ xdata = line.get_xdata()
1237
+ ydata = line.get_ydata()
1238
+ if len(xdata) == 2: # Regression line typically has 2 points
1239
+ is_regression_line = True
1240
+
1241
+ # Store display id/label
1242
+ # For stem, use semantic_id as the primary ID to ensure uniqueness
1243
+ if semantic_id and is_stem:
1244
+ artist["id"] = semantic_id
1245
+ if scitex_id:
1246
+ artist["group_id"] = scitex_id # Store original trace id as group
1247
+ elif scitex_id:
1248
+ artist["id"] = scitex_id
1249
+ elif semantic_id:
1250
+ artist["id"] = semantic_id
1251
+ elif is_regression_line:
1252
+ artist["id"] = f"regression_{i}"
1253
+ elif not label.startswith("_"):
1254
+ artist["id"] = label
1255
+ else:
1256
+ artist["id"] = f"line_{i}"
1257
+
1258
+ # Semantic layer: mark (plot type) and role (component role)
1259
+ # mark: line, scatter, bar, boxplot, violin, heatmap, etc.
1260
+ # role: specific component like boxplot_median, violin_body, etc.
1261
+ artist["mark"] = "line" # Line2D is always a line mark
1262
+ if semantic_type:
1263
+ artist["role"] = semantic_type
1264
+ elif is_regression_line:
1265
+ artist["role"] = "regression_line"
1266
+
1267
+ # Label (for legend) - use label if not internal
1268
+ # legend_included indicates if this artist appears in legend
1269
+ if not label.startswith("_"):
1270
+ artist["label"] = label
1271
+ artist["legend_included"] = True
1272
+ else:
1273
+ artist["legend_included"] = False
1274
+
1275
+ # zorder for layering
1276
+ artist["zorder"] = line.get_zorder()
1277
+
1278
+ # Backend layer: matplotlib-specific properties
1279
+ backend = {
1280
+ "name": "matplotlib",
1281
+ "artist_class": type(line).__name__, # e.g., "Line2D"
1282
+ "props": {}
1283
+ }
1284
+
1285
+ # Color - always convert to hex for consistent JSON storage
1286
+ color = line.get_color()
1287
+ try:
1288
+ # mcolors.to_hex handles strings, RGB tuples, RGBA tuples
1289
+ color_hex = mcolors.to_hex(color, keep_alpha=False)
1290
+ backend["props"]["color"] = color_hex
1291
+ except (ValueError, TypeError):
1292
+ # Fallback: store as-is
1293
+ backend["props"]["color"] = color
1294
+
1295
+ # Line style
1296
+ backend["props"]["linestyle"] = line.get_linestyle()
1297
+
1298
+ # Line width
1299
+ backend["props"]["linewidth_pt"] = line.get_linewidth()
1300
+
1301
+ # Marker - always include (null if no marker)
1302
+ marker = line.get_marker()
1303
+ if marker and marker != "None" and marker != "none":
1304
+ backend["props"]["marker"] = marker
1305
+ backend["props"]["markersize_pt"] = line.get_markersize()
1306
+ else:
1307
+ backend["props"]["marker"] = None
1308
+
1309
+ artist["backend"] = backend
1310
+
1311
+ # data_ref - CSV column mapping using single source of truth naming
1312
+ # Format: ax-row-{row}-col-{col}_trace-id-{id}_variable-{var}
1313
+ # Only add data_ref if this is NOT a boxplot/violin internal element
1314
+ # (those have semantic_type set but no corresponding CSV data)
1315
+ if not semantic_type:
1316
+ # Try to find the correct trace_id for data_ref
1317
+ # Priority: 1) _scitex_id, 2) History record trace_id, 3) Artist ID
1318
+ trace_id_for_ref = None
1319
+
1320
+ if scitex_id:
1321
+ # Artist has explicit _scitex_id set
1322
+ trace_id_for_ref = scitex_id
1323
+ else:
1324
+ # Try to find matching history record for this Line2D
1325
+ # Look for "plot" method records and match by index
1326
+ if hasattr(ax_for_detection, "history"):
1327
+ plot_records = []
1328
+ for record_id, record in ax_for_detection.history.items():
1329
+ if isinstance(record, tuple) and len(record) >= 2:
1330
+ if record[1] == "plot":
1331
+ # Extract trace_id from tracking_id (e.g., "ax_00_plot_0" -> "0")
1332
+ tracking_id = record[0]
1333
+ if tracking_id.startswith("ax_"):
1334
+ parts = tracking_id.split("_")
1335
+ if len(parts) >= 4:
1336
+ trace_id_for_ref = "_".join(parts[3:])
1337
+ elif len(parts) == 4:
1338
+ trace_id_for_ref = parts[3]
1339
+ elif tracking_id.startswith("plot_"):
1340
+ trace_id_for_ref = tracking_id[5:] if len(tracking_id) > 5 else str(i)
1341
+ else:
1342
+ # User-provided ID like "sine"
1343
+ trace_id_for_ref = tracking_id
1344
+ plot_records.append(trace_id_for_ref)
1345
+
1346
+ # Match by line index if we have plot records
1347
+ if plot_records:
1348
+ # Find the index of this line among all non-semantic lines
1349
+ non_semantic_line_idx = 0
1350
+ for j, l in enumerate(mpl_ax.lines[:i]):
1351
+ l_label = l.get_label()
1352
+ l_scitex_id = getattr(l, "_scitex_id", None)
1353
+ l_semantic_id = getattr(l, "_scitex_semantic_id", None)
1354
+ # Count only lines that would get data_ref (non-semantic)
1355
+ if not l_semantic_id and not l_label.startswith("_"):
1356
+ non_semantic_line_idx += 1
1357
+ elif l_scitex_id:
1358
+ non_semantic_line_idx += 1
1359
+
1360
+ if non_semantic_line_idx < len(plot_records):
1361
+ trace_id_for_ref = plot_records[non_semantic_line_idx]
1362
+
1363
+ # Fallback to artist ID
1364
+ if not trace_id_for_ref:
1365
+ trace_id_for_ref = artist.get("id", str(i))
1366
+
1367
+ artist["data_ref"] = _get_csv_column_names(trace_id_for_ref, ax_row, ax_col)
1368
+ elif is_stem and scitex_id:
1369
+ # For stem artists, add data_ref pointing to the original trace's columns
1370
+ artist["data_ref"] = _get_csv_column_names(scitex_id, ax_row, ax_col)
1371
+ # For baseline, mark it as derived (not directly from CSV)
1372
+ if semantic_type == "stem_baseline":
1373
+ artist["derived"] = True
1374
+ artist["data_ref"]["derived_from"] = "y=0"
1375
+
1376
+ # Add boxplot statistics to the median artist
1377
+ if has_boxplot_stats and box_idx is not None and box_idx < len(boxplot_stats):
1378
+ artist["stats"] = boxplot_stats[box_idx]
1379
+
1380
+ artists.append(artist)
1381
+
1382
+ # Also extract PathCollection artists (scatter points)
1383
+ for i, coll in enumerate(mpl_ax.collections):
1384
+ if "PathCollection" not in type(coll).__name__:
1385
+ continue
1386
+
1387
+ artist = {}
1388
+
1389
+ # Get ID from _scitex_id attribute
1390
+ scitex_id = getattr(coll, "_scitex_id", None)
1391
+ label = coll.get_label()
1392
+
1393
+ if scitex_id:
1394
+ artist["id"] = scitex_id
1395
+ elif label and not label.startswith("_"):
1396
+ artist["id"] = label
1397
+ else:
1398
+ artist["id"] = f"scatter_{i}"
1399
+
1400
+ # Semantic layer
1401
+ artist["mark"] = "scatter"
1402
+
1403
+ # Legend inclusion
1404
+ if label and not label.startswith("_"):
1405
+ artist["label"] = label
1406
+ artist["legend_included"] = True
1407
+ else:
1408
+ artist["legend_included"] = False
1409
+
1410
+ artist["zorder"] = coll.get_zorder()
1411
+
1412
+ # Backend layer: matplotlib-specific properties
1413
+ backend = {
1414
+ "name": "matplotlib",
1415
+ "artist_class": type(coll).__name__, # "PathCollection"
1416
+ "props": {}
1417
+ }
1418
+
1419
+ try:
1420
+ facecolors = coll.get_facecolor()
1421
+ if len(facecolors) > 0:
1422
+ backend["props"]["facecolor"] = mcolors.to_hex(facecolors[0], keep_alpha=False)
1423
+ except (ValueError, TypeError, IndexError):
1424
+ pass
1425
+
1426
+ try:
1427
+ edgecolors = coll.get_edgecolor()
1428
+ if len(edgecolors) > 0:
1429
+ backend["props"]["edgecolor"] = mcolors.to_hex(edgecolors[0], keep_alpha=False)
1430
+ except (ValueError, TypeError, IndexError):
1431
+ pass
1432
+
1433
+ try:
1434
+ sizes = coll.get_sizes()
1435
+ if len(sizes) > 0:
1436
+ backend["props"]["size"] = float(sizes[0])
1437
+ except (ValueError, TypeError, IndexError):
1438
+ pass
1439
+
1440
+ artist["backend"] = backend
1441
+
1442
+ # data_ref - CSV column mapping using single source of truth naming
1443
+ # Format: ax-row-{row}-col-{col}_trace-id-{id}_variable-{var}
1444
+ artist_id = artist.get("id", str(i))
1445
+ artist["data_ref"] = _get_csv_column_names(artist_id, ax_row, ax_col)
1446
+
1447
+ artists.append(artist)
1448
+
1449
+ # Extract Rectangle patches (bar/barh/hist charts)
1450
+ # First, collect all rectangles to determine group info
1451
+ rectangles = []
1452
+ for i, patch in enumerate(mpl_ax.patches):
1453
+ patch_type = type(patch).__name__
1454
+ if patch_type == "Rectangle":
1455
+ rectangles.append((i, patch))
1456
+
1457
+ # Determine if this is bar, barh, or hist based on plot_type
1458
+ is_bar = plot_type in ("bar", "barh")
1459
+ is_hist = plot_type == "hist"
1460
+
1461
+ # Get trace_id from history for data_ref
1462
+ trace_id_for_bars = None
1463
+ if hasattr(ax_for_detection, "history"):
1464
+ for record in ax_for_detection.history.values():
1465
+ if isinstance(record, tuple) and len(record) >= 2:
1466
+ method_name = record[1]
1467
+ if method_name in ("bar", "barh", "hist"):
1468
+ trace_id_for_bars = record[0]
1469
+ break
1470
+
1471
+ bar_count = 0
1472
+ for rect_idx, (i, patch) in enumerate(rectangles):
1473
+ patch_type = type(patch).__name__
1474
+
1475
+ # Skip internal unlabeled patches for non-bar/hist types
1476
+ scitex_id = getattr(patch, "_scitex_id", None)
1477
+ label = patch.get_label() if hasattr(patch, "get_label") else ""
1478
+
1479
+ # For bar/hist, we want ALL rectangles even if unlabeled
1480
+ if not (is_bar or is_hist):
1481
+ if skip_unlabeled and not scitex_id and (not label or label.startswith("_")):
1482
+ continue
1483
+
1484
+ artist = {}
1485
+
1486
+ # Generate unique ID with index
1487
+ base_id = scitex_id or (label if label and not label.startswith("_") else trace_id_for_bars or "bar")
1488
+ artist["id"] = f"{base_id}_{bar_count}"
1489
+
1490
+ # Add group_id for referencing the whole group
1491
+ artist["group_id"] = base_id
1492
+
1493
+ # Semantic layer
1494
+ artist["mark"] = "bar"
1495
+ if is_hist:
1496
+ artist["role"] = "hist_bin"
1497
+ else:
1498
+ artist["role"] = "bar_body"
1499
+
1500
+ # Legend inclusion - only first bar of a group should be in legend
1501
+ if label and not label.startswith("_") and bar_count == 0:
1502
+ artist["label"] = label
1503
+ artist["legend_included"] = True
1504
+ else:
1505
+ artist["legend_included"] = False
1506
+
1507
+ artist["zorder"] = patch.get_zorder()
1508
+
1509
+ # Backend layer: matplotlib-specific properties
1510
+ backend = {
1511
+ "name": "matplotlib",
1512
+ "artist_class": patch_type,
1513
+ "props": {}
1514
+ }
1515
+
1516
+ try:
1517
+ backend["props"]["facecolor"] = mcolors.to_hex(patch.get_facecolor(), keep_alpha=False)
1518
+ except (ValueError, TypeError):
1519
+ pass
1520
+ try:
1521
+ backend["props"]["edgecolor"] = mcolors.to_hex(patch.get_edgecolor(), keep_alpha=False)
1522
+ except (ValueError, TypeError):
1523
+ pass
1524
+ try:
1525
+ backend["props"]["linewidth_pt"] = patch.get_linewidth()
1526
+ except (ValueError, TypeError):
1527
+ pass
1528
+
1529
+ artist["backend"] = backend
1530
+
1531
+ # Bar geometry
1532
+ try:
1533
+ artist["geometry"] = {
1534
+ "x": patch.get_x(),
1535
+ "y": patch.get_y(),
1536
+ "width": patch.get_width(),
1537
+ "height": patch.get_height(),
1538
+ }
1539
+ except (ValueError, TypeError):
1540
+ pass
1541
+
1542
+ # data_ref with row_index for individual bars
1543
+ if trace_id_for_bars:
1544
+ if is_hist:
1545
+ # Histogram uses specific column names: bin-centers (x), bin-counts (y)
1546
+ prefix = f"ax-row-{ax_row}-col-{ax_col}_trace-id-{trace_id_for_bars}_variable-"
1547
+ artist["data_ref"] = {
1548
+ "x": f"{prefix}bin-centers",
1549
+ "y": f"{prefix}bin-counts",
1550
+ "row_index": bar_count,
1551
+ "bin_index": bar_count,
1552
+ }
1553
+ else:
1554
+ artist["data_ref"] = _get_csv_column_names(trace_id_for_bars, ax_row, ax_col)
1555
+ artist["data_ref"]["row_index"] = bar_count
1556
+
1557
+ bar_count += 1
1558
+ artists.append(artist)
1559
+
1560
+ # Extract Wedge patches (pie charts)
1561
+ wedge_count = 0
1562
+ for i, patch in enumerate(mpl_ax.patches):
1563
+ patch_type = type(patch).__name__
1564
+
1565
+ if patch_type != "Wedge":
1566
+ continue
1567
+
1568
+ artist = {}
1569
+
1570
+ scitex_id = getattr(patch, "_scitex_id", None)
1571
+ label = patch.get_label() if hasattr(patch, "get_label") else ""
1572
+
1573
+ if scitex_id:
1574
+ artist["id"] = scitex_id
1575
+ elif label and not label.startswith("_"):
1576
+ artist["id"] = label
1577
+ else:
1578
+ artist["id"] = f"wedge_{wedge_count}"
1579
+ wedge_count += 1
1580
+
1581
+ # Semantic layer
1582
+ artist["mark"] = "pie"
1583
+ artist["role"] = "pie_wedge"
1584
+
1585
+ if label and not label.startswith("_"):
1586
+ artist["label"] = label
1587
+ artist["legend_included"] = True
1588
+ else:
1589
+ artist["legend_included"] = False
1590
+
1591
+ artist["zorder"] = patch.get_zorder()
1592
+
1593
+ # Backend layer
1594
+ backend = {
1595
+ "name": "matplotlib",
1596
+ "artist_class": patch_type,
1597
+ "props": {}
1598
+ }
1599
+ try:
1600
+ backend["props"]["facecolor"] = mcolors.to_hex(patch.get_facecolor(), keep_alpha=False)
1601
+ except (ValueError, TypeError):
1602
+ pass
1603
+
1604
+ artist["backend"] = backend
1605
+ artists.append(artist)
1606
+
1607
+ # Extract QuadMesh (hist2d) and PolyCollection (hexbin/violin) with colormap info
1608
+ # Try to get hist2d result data from history
1609
+ hist2d_result = None
1610
+ hexbin_result = None
1611
+ if hasattr(ax_for_detection, "history"):
1612
+ for record in ax_for_detection.history.values():
1613
+ if isinstance(record, tuple) and len(record) >= 3:
1614
+ method_name = record[1]
1615
+ tracked_dict = record[2]
1616
+ if method_name == "hist2d" and "result" in tracked_dict:
1617
+ hist2d_result = tracked_dict["result"]
1618
+ elif method_name == "hexbin" and "result" in tracked_dict:
1619
+ hexbin_result = tracked_dict["result"]
1620
+
1621
+ for i, coll in enumerate(mpl_ax.collections):
1622
+ coll_type = type(coll).__name__
1623
+
1624
+ if coll_type == "QuadMesh":
1625
+ artist = {}
1626
+ artist["id"] = f"hist2d_{i}"
1627
+
1628
+ # Semantic layer
1629
+ artist["mark"] = "heatmap"
1630
+ artist["role"] = "hist2d"
1631
+
1632
+ artist["legend_included"] = False
1633
+ artist["zorder"] = coll.get_zorder()
1634
+
1635
+ # Backend layer
1636
+ backend = {
1637
+ "name": "matplotlib",
1638
+ "artist_class": coll_type,
1639
+ "props": {}
1640
+ }
1641
+ try:
1642
+ cmap = coll.get_cmap()
1643
+ if cmap:
1644
+ backend["props"]["cmap"] = cmap.name
1645
+ except (ValueError, TypeError, AttributeError):
1646
+ pass
1647
+ try:
1648
+ backend["props"]["vmin"] = float(coll.norm.vmin) if coll.norm else None
1649
+ backend["props"]["vmax"] = float(coll.norm.vmax) if coll.norm else None
1650
+ except (ValueError, TypeError, AttributeError):
1651
+ pass
1652
+
1653
+ artist["backend"] = backend
1654
+
1655
+ # Extract hist2d result data directly from QuadMesh
1656
+ try:
1657
+ # Get the count array from the QuadMesh
1658
+ arr = coll.get_array()
1659
+ if arr is not None and len(arr) > 0:
1660
+ import numpy as np
1661
+ # QuadMesh from hist2d has counts as flattened array
1662
+ # Try to get coordinates from the mesh
1663
+ coords = coll.get_coordinates()
1664
+ if coords is not None and len(coords) > 0:
1665
+ # coords shape is (n_rows+1, n_cols+1, 2) for 2D hist
1666
+ n_ybins = coords.shape[0] - 1
1667
+ n_xbins = coords.shape[1] - 1
1668
+
1669
+ # Get edges from coordinates
1670
+ xedges = coords[0, :, 0] # First row, all cols, x-coord
1671
+ yedges = coords[:, 0, 1] # All rows, first col, y-coord
1672
+
1673
+ artist["result"] = {
1674
+ "H_shape": [n_ybins, n_xbins],
1675
+ "n_xbins": int(n_xbins),
1676
+ "n_ybins": int(n_ybins),
1677
+ "xedges_range": [float(xedges[0]), float(xedges[-1])],
1678
+ "yedges_range": [float(yedges[0]), float(yedges[-1])],
1679
+ "count_range": [float(arr.min()), float(arr.max())],
1680
+ "total_count": int(arr.sum()),
1681
+ }
1682
+ except (IndexError, TypeError, AttributeError, ValueError):
1683
+ pass
1684
+
1685
+ artists.append(artist)
1686
+
1687
+ elif coll_type == "PolyCollection" or (coll_type == "FillBetweenPolyCollection" and plot_type == "violin"):
1688
+ arr = coll.get_array() if hasattr(coll, "get_array") else None
1689
+
1690
+ # Check if this is hexbin (has array data for counts) or violin body
1691
+ if arr is not None and len(arr) > 0 and plot_type == "hexbin":
1692
+ artist = {}
1693
+ artist["id"] = f"hexbin_{i}"
1694
+
1695
+ # Semantic layer
1696
+ artist["mark"] = "heatmap"
1697
+ artist["role"] = "hexbin"
1698
+
1699
+ artist["legend_included"] = False
1700
+ artist["zorder"] = coll.get_zorder()
1701
+
1702
+ # Backend layer
1703
+ backend = {
1704
+ "name": "matplotlib",
1705
+ "artist_class": coll_type,
1706
+ "props": {}
1707
+ }
1708
+ try:
1709
+ cmap = coll.get_cmap()
1710
+ if cmap:
1711
+ backend["props"]["cmap"] = cmap.name
1712
+ except (ValueError, TypeError, AttributeError):
1713
+ pass
1714
+ try:
1715
+ backend["props"]["vmin"] = float(coll.norm.vmin) if coll.norm else None
1716
+ backend["props"]["vmax"] = float(coll.norm.vmax) if coll.norm else None
1717
+ except (ValueError, TypeError, AttributeError):
1718
+ pass
1719
+
1720
+ artist["backend"] = backend
1721
+
1722
+ # Add hexbin result info directly from the PolyCollection
1723
+ try:
1724
+ artist["result"] = {
1725
+ "n_hexagons": int(len(arr)),
1726
+ "count_range": [float(arr.min()), float(arr.max())] if len(arr) > 0 else None,
1727
+ "total_count": int(arr.sum()),
1728
+ }
1729
+ except (TypeError, AttributeError, ValueError):
1730
+ pass
1731
+
1732
+ artists.append(artist)
1733
+
1734
+ elif plot_type == "violin":
1735
+ # This is a violin body (PolyCollection for violin shape)
1736
+ artist = {}
1737
+ scitex_id = getattr(coll, "_scitex_id", None)
1738
+ label = coll.get_label() if hasattr(coll, "get_label") else ""
1739
+
1740
+ if scitex_id:
1741
+ artist["id"] = f"{scitex_id}_body_{i}"
1742
+ artist["group_id"] = scitex_id
1743
+ else:
1744
+ artist["id"] = f"violin_body_{i}"
1745
+
1746
+ # Semantic layer
1747
+ artist["mark"] = "polygon"
1748
+ artist["role"] = "violin_body"
1749
+
1750
+ artist["legend_included"] = False
1751
+ artist["zorder"] = coll.get_zorder()
1752
+
1753
+ # Backend layer
1754
+ backend = {
1755
+ "name": "matplotlib",
1756
+ "artist_class": coll_type,
1757
+ "props": {}
1758
+ }
1759
+ try:
1760
+ facecolors = coll.get_facecolor()
1761
+ if len(facecolors) > 0:
1762
+ backend["props"]["facecolor"] = mcolors.to_hex(facecolors[0], keep_alpha=False)
1763
+ except (ValueError, TypeError, IndexError):
1764
+ pass
1765
+ try:
1766
+ edgecolors = coll.get_edgecolor()
1767
+ if len(edgecolors) > 0:
1768
+ backend["props"]["edgecolor"] = mcolors.to_hex(edgecolors[0], keep_alpha=False)
1769
+ except (ValueError, TypeError, IndexError):
1770
+ pass
1771
+
1772
+ artist["backend"] = backend
1773
+ artists.append(artist)
1774
+
1775
+ # Extract AxesImage (imshow)
1776
+ for i, img in enumerate(mpl_ax.images):
1777
+ img_type = type(img).__name__
1778
+
1779
+ artist = {}
1780
+
1781
+ scitex_id = getattr(img, "_scitex_id", None)
1782
+ label = img.get_label() if hasattr(img, "get_label") else ""
1783
+
1784
+ if scitex_id:
1785
+ artist["id"] = scitex_id
1786
+ elif label and not label.startswith("_"):
1787
+ artist["id"] = label
1788
+ else:
1789
+ artist["id"] = f"image_{i}"
1790
+
1791
+ # Semantic layer
1792
+ artist["mark"] = "image"
1793
+ artist["role"] = "image"
1794
+
1795
+ artist["legend_included"] = False
1796
+ artist["zorder"] = img.get_zorder()
1797
+
1798
+ # Backend layer
1799
+ backend = {
1800
+ "name": "matplotlib",
1801
+ "artist_class": img_type,
1802
+ "props": {}
1803
+ }
1804
+ try:
1805
+ cmap = img.get_cmap()
1806
+ if cmap:
1807
+ backend["props"]["cmap"] = cmap.name
1808
+ except (ValueError, TypeError, AttributeError):
1809
+ pass
1810
+ try:
1811
+ backend["props"]["vmin"] = float(img.norm.vmin) if img.norm else None
1812
+ backend["props"]["vmax"] = float(img.norm.vmax) if img.norm else None
1813
+ except (ValueError, TypeError, AttributeError):
1814
+ pass
1815
+ try:
1816
+ backend["props"]["interpolation"] = img.get_interpolation()
1817
+ except (ValueError, TypeError, AttributeError):
1818
+ pass
1819
+
1820
+ artist["backend"] = backend
1821
+ artists.append(artist)
1822
+
1823
+ # Extract Text artists (annotations, stats text, etc.)
1824
+ text_count = 0
1825
+ for i, text_obj in enumerate(mpl_ax.texts):
1826
+ text_content = text_obj.get_text()
1827
+ if not text_content or text_content.strip() == "":
1828
+ continue
1829
+
1830
+ artist = {}
1831
+
1832
+ scitex_id = getattr(text_obj, "_scitex_id", None)
1833
+
1834
+ if scitex_id:
1835
+ artist["id"] = scitex_id
1836
+ else:
1837
+ artist["id"] = f"text_{text_count}"
1838
+
1839
+ # Semantic layer
1840
+ artist["mark"] = "text"
1841
+
1842
+ # Try to determine role from content or position
1843
+ pos = text_obj.get_position()
1844
+ # Check if this looks like stats annotation (contains r=, p=, etc.)
1845
+ if any(kw in text_content.lower() for kw in ['r=', 'p=', 'r²=', 'n=']):
1846
+ artist["role"] = "stats_annotation"
1847
+ else:
1848
+ artist["role"] = "annotation"
1849
+
1850
+ artist["legend_included"] = False
1851
+ artist["zorder"] = text_obj.get_zorder()
1852
+
1853
+ # Geometry - text position
1854
+ artist["geometry"] = {
1855
+ "x": pos[0],
1856
+ "y": pos[1],
1857
+ }
1858
+
1859
+ # Text content
1860
+ artist["text"] = text_content
1861
+
1862
+ # Backend layer
1863
+ backend = {
1864
+ "name": "matplotlib",
1865
+ "artist_class": type(text_obj).__name__,
1866
+ "props": {}
1867
+ }
1868
+
1869
+ try:
1870
+ color = text_obj.get_color()
1871
+ backend["props"]["color"] = mcolors.to_hex(color, keep_alpha=False)
1872
+ except (ValueError, TypeError):
1873
+ pass
1874
+
1875
+ try:
1876
+ backend["props"]["fontsize_pt"] = text_obj.get_fontsize()
1877
+ except (ValueError, TypeError):
1878
+ pass
1879
+
1880
+ try:
1881
+ backend["props"]["ha"] = text_obj.get_ha()
1882
+ backend["props"]["va"] = text_obj.get_va()
1883
+ except (ValueError, TypeError):
1884
+ pass
1885
+
1886
+ artist["backend"] = backend
1887
+
1888
+ # data_ref for text position - only if text was explicitly tracked (has _scitex_id)
1889
+ # Auto-generated text (like contour clabels, pie labels) doesn't have CSV data
1890
+ if scitex_id:
1891
+ artist["data_ref"] = {
1892
+ "x": f"text_{text_count}_x",
1893
+ "y": f"text_{text_count}_y",
1894
+ "content": f"text_{text_count}_content"
1895
+ }
1896
+
1897
+ text_count += 1
1898
+ artists.append(artist)
1899
+
1900
+ # Extract LineCollection artists (errorbar lines, etc.)
1901
+ for i, coll in enumerate(mpl_ax.collections):
1902
+ coll_type = type(coll).__name__
1903
+
1904
+ if coll_type == "LineCollection":
1905
+ # LineCollection is used for errorbar caps/lines
1906
+ artist = {}
1907
+
1908
+ scitex_id = getattr(coll, "_scitex_id", None)
1909
+ label = coll.get_label() if hasattr(coll, "get_label") else ""
1910
+
1911
+ if scitex_id:
1912
+ artist["id"] = scitex_id
1913
+ elif label and not label.startswith("_"):
1914
+ artist["id"] = label
1915
+ else:
1916
+ artist["id"] = f"linecollection_{i}"
1917
+
1918
+ # Semantic layer - determine role
1919
+ artist["mark"] = "line"
1920
+ # Check if this is an errorbar based on context
1921
+ if plot_type == "bar" or method == "barh":
1922
+ artist["role"] = "errorbar"
1923
+ elif plot_type == "stem":
1924
+ artist["role"] = "stem_stem"
1925
+ artist["id"] = "stem_lines" # Override ID for stem
1926
+ else:
1927
+ artist["role"] = "line_collection"
1928
+
1929
+ artist["legend_included"] = False
1930
+ artist["zorder"] = coll.get_zorder()
1931
+
1932
+ # Backend layer
1933
+ backend = {
1934
+ "name": "matplotlib",
1935
+ "artist_class": coll_type,
1936
+ "props": {}
1937
+ }
1938
+
1939
+ try:
1940
+ colors = coll.get_colors()
1941
+ if len(colors) > 0:
1942
+ backend["props"]["color"] = mcolors.to_hex(colors[0], keep_alpha=False)
1943
+ except (ValueError, TypeError, IndexError):
1944
+ pass
1945
+
1946
+ try:
1947
+ linewidths = coll.get_linewidths()
1948
+ if len(linewidths) > 0:
1949
+ backend["props"]["linewidth_pt"] = float(linewidths[0])
1950
+ except (ValueError, TypeError, IndexError):
1951
+ pass
1952
+
1953
+ artist["backend"] = backend
1954
+
1955
+ # Add data_ref for errorbar LineCollections
1956
+ if artist["role"] == "errorbar":
1957
+ # Try to find the trace_id from history
1958
+ errorbar_trace_id = None
1959
+ error_var = "yerr" if method == "bar" else "xerr"
1960
+ if hasattr(ax_for_detection, "history"):
1961
+ for record in ax_for_detection.history.values():
1962
+ if isinstance(record, tuple) and len(record) >= 2:
1963
+ method_name = record[1]
1964
+ if method_name in ("bar", "barh"):
1965
+ errorbar_trace_id = record[0]
1966
+ break
1967
+ if errorbar_trace_id:
1968
+ base_ref = _get_csv_column_names(errorbar_trace_id, ax_row, ax_col)
1969
+ artist["data_ref"] = {
1970
+ "x": base_ref.get("x"),
1971
+ "y": base_ref.get("y"),
1972
+ error_var: f"ax-row-{ax_row}-col-{ax_col}_trace-id-{errorbar_trace_id}_variable-{error_var}"
1973
+ }
1974
+ elif artist["role"] == "stem_stem" and hasattr(ax_for_detection, "history"):
1975
+ # Add data_ref for stem LineCollection
1976
+ for record in ax_for_detection.history.values():
1977
+ if isinstance(record, tuple) and len(record) >= 2:
1978
+ method_name = record[1]
1979
+ if method_name == "stem":
1980
+ stem_trace_id = record[0]
1981
+ artist["data_ref"] = _get_csv_column_names(stem_trace_id, ax_row, ax_col)
1982
+ break
1983
+
1984
+ artists.append(artist)
1985
+
1986
+ return artists
1987
+
1988
+
1989
+ # Backward compatibility alias
1990
+ _extract_traces = _extract_artists
1991
+
1992
+
1993
+ def _extract_legend_info(ax) -> Optional[dict]:
1994
+ """
1995
+ Extract legend information from axes.
1996
+
1997
+ Uses matplotlib terminology for legend properties.
1998
+
1999
+ Parameters
2000
+ ----------
2001
+ ax : matplotlib.axes.Axes
2002
+ The axes to extract legend from
2003
+
2004
+ Returns
2005
+ -------
2006
+ dict or None
2007
+ Legend info dictionary with matplotlib properties, or None if no legend
2008
+ """
2009
+ legend = ax.get_legend()
2010
+ if legend is None:
2011
+ return None
2012
+
2013
+ legend_info = {
2014
+ "visible": legend.get_visible(),
2015
+ "loc": legend._loc if hasattr(legend, "_loc") else "best",
2016
+ "frameon": legend.get_frame_on() if hasattr(legend, "get_frame_on") else True,
2017
+ }
2018
+
2019
+ # ncol - number of columns
2020
+ if hasattr(legend, "_ncols"):
2021
+ legend_info["ncol"] = legend._ncols
2022
+ elif hasattr(legend, "_ncol"):
2023
+ legend_info["ncol"] = legend._ncol
2024
+
2025
+ # Extract legend handles with artist references
2026
+ # This allows reconstructing the legend by referencing artists
2027
+ handles = []
2028
+ texts = legend.get_texts()
2029
+ legend_handles = legend.legend_handles if hasattr(legend, 'legend_handles') else []
2030
+
2031
+ # Get the raw matplotlib axes for accessing lines to match IDs
2032
+ mpl_ax = ax._axis_mpl if hasattr(ax, "_axis_mpl") else ax
2033
+
2034
+ for i, text in enumerate(texts):
2035
+ label_text = text.get_text()
2036
+ handle_entry = {"label": label_text}
2037
+
2038
+ # Try to get artist_id from corresponding handle
2039
+ artist_id = None
2040
+ if i < len(legend_handles):
2041
+ handle = legend_handles[i]
2042
+ # Check if handle has scitex_id
2043
+ if hasattr(handle, "_scitex_id"):
2044
+ artist_id = handle._scitex_id
2045
+
2046
+ # Fallback: find matching artist by label in axes artists
2047
+ if artist_id is None:
2048
+ # Check lines
2049
+ for line in mpl_ax.lines:
2050
+ line_label = line.get_label()
2051
+ if line_label == label_text:
2052
+ if hasattr(line, "_scitex_id"):
2053
+ artist_id = line._scitex_id
2054
+ elif not line_label.startswith("_"):
2055
+ artist_id = line_label
2056
+ break
2057
+
2058
+ # Check collections (scatter)
2059
+ if artist_id is None:
2060
+ for coll in mpl_ax.collections:
2061
+ coll_label = coll.get_label() if hasattr(coll, "get_label") else ""
2062
+ if coll_label == label_text:
2063
+ if hasattr(coll, "_scitex_id"):
2064
+ artist_id = coll._scitex_id
2065
+ elif coll_label and not coll_label.startswith("_"):
2066
+ artist_id = coll_label
2067
+ break
2068
+
2069
+ # Check patches (bar/hist/pie)
2070
+ if artist_id is None:
2071
+ for patch in mpl_ax.patches:
2072
+ patch_label = patch.get_label() if hasattr(patch, "get_label") else ""
2073
+ if patch_label == label_text:
2074
+ if hasattr(patch, "_scitex_id"):
2075
+ artist_id = patch._scitex_id
2076
+ elif patch_label and not patch_label.startswith("_"):
2077
+ artist_id = patch_label
2078
+ break
2079
+
2080
+ # Check images (imshow)
2081
+ if artist_id is None:
2082
+ for img in mpl_ax.images:
2083
+ img_label = img.get_label() if hasattr(img, "get_label") else ""
2084
+ if img_label == label_text:
2085
+ if hasattr(img, "_scitex_id"):
2086
+ artist_id = img._scitex_id
2087
+ elif img_label and not img_label.startswith("_"):
2088
+ artist_id = img_label
2089
+ break
2090
+
2091
+ if artist_id:
2092
+ handle_entry["artist_id"] = artist_id
2093
+
2094
+ handles.append(handle_entry)
2095
+
2096
+ if handles:
2097
+ legend_info["handles"] = handles
2098
+
2099
+ return legend_info
2100
+
2101
+
2102
+ def _detect_plot_type(ax) -> tuple:
2103
+ """
2104
+ Detect the primary plot type and method from axes content.
2105
+
2106
+ Checks for:
2107
+ - Lines -> "line"
2108
+ - Scatter collections -> "scatter"
2109
+ - Bar containers -> "bar"
2110
+ - Patches (histogram) -> "hist"
2111
+ - Box plot -> "boxplot"
2112
+ - Violin plot -> "violin"
2113
+ - Image -> "image"
2114
+ - Contour -> "contour"
2115
+ - KDE -> "kde"
2116
+
2117
+ Parameters
2118
+ ----------
2119
+ ax : matplotlib.axes.Axes
2120
+ The axes to analyze
2121
+
2122
+ Returns
2123
+ -------
2124
+ tuple
2125
+ (plot_type, method) where method is the actual plotting function used,
2126
+ or (None, None) if unclear
2127
+ """
2128
+ # Check scitex history FIRST (most reliable for scitex plots)
2129
+ # History format: dict with keys as IDs and values as tuples (id, method, tracked_dict, kwargs)
2130
+ if hasattr(ax, "history") and len(ax.history) > 0:
2131
+ # Get all methods from history
2132
+ methods = []
2133
+ for record in ax.history.values():
2134
+ if isinstance(record, tuple) and len(record) >= 2:
2135
+ methods.append(record[1]) # record[1] is the method name
2136
+
2137
+ # Check methods in priority order (more specific first)
2138
+ for method in methods:
2139
+ if method == "stx_heatmap":
2140
+ return "heatmap", "stx_heatmap"
2141
+ elif method == "stx_kde":
2142
+ return "kde", "stx_kde"
2143
+ elif method == "stx_ecdf":
2144
+ return "ecdf", "stx_ecdf"
2145
+ elif method == "stx_violin":
2146
+ return "violin", "stx_violin"
2147
+ elif method in ("stx_box", "boxplot"):
2148
+ return "boxplot", method
2149
+ elif method == "stx_line":
2150
+ return "line", "stx_line"
2151
+ elif method == "plot_scatter":
2152
+ return "scatter", "plot_scatter"
2153
+ elif method == "stx_mean_std":
2154
+ return "line", "stx_mean_std"
2155
+ elif method == "stx_mean_ci":
2156
+ return "line", "stx_mean_ci"
2157
+ elif method == "stx_median_iqr":
2158
+ return "line", "stx_median_iqr"
2159
+ elif method == "stx_shaded_line":
2160
+ return "line", "stx_shaded_line"
2161
+ elif method == "sns_boxplot":
2162
+ return "boxplot", "sns_boxplot"
2163
+ elif method == "sns_violinplot":
2164
+ return "violin", "sns_violinplot"
2165
+ elif method == "sns_scatterplot":
2166
+ return "scatter", "sns_scatterplot"
2167
+ elif method == "sns_lineplot":
2168
+ return "line", "sns_lineplot"
2169
+ elif method == "sns_histplot":
2170
+ return "hist", "sns_histplot"
2171
+ elif method == "sns_barplot":
2172
+ return "bar", "sns_barplot"
2173
+ elif method == "sns_stripplot":
2174
+ return "scatter", "sns_stripplot"
2175
+ elif method == "sns_kdeplot":
2176
+ return "kde", "sns_kdeplot"
2177
+ elif method == "scatter":
2178
+ return "scatter", "scatter"
2179
+ elif method == "bar":
2180
+ return "bar", "bar"
2181
+ elif method == "barh":
2182
+ return "bar", "barh"
2183
+ elif method == "hist":
2184
+ return "hist", "hist"
2185
+ elif method == "hist2d":
2186
+ return "hist2d", "hist2d"
2187
+ elif method == "hexbin":
2188
+ return "hexbin", "hexbin"
2189
+ elif method == "violinplot":
2190
+ return "violin", "violinplot"
2191
+ elif method == "errorbar":
2192
+ return "errorbar", "errorbar"
2193
+ elif method == "fill_between":
2194
+ return "fill", "fill_between"
2195
+ elif method == "fill_betweenx":
2196
+ return "fill", "fill_betweenx"
2197
+ elif method == "imshow":
2198
+ return "image", "imshow"
2199
+ elif method == "matshow":
2200
+ return "image", "matshow"
2201
+ elif method == "contour":
2202
+ return "contour", "contour"
2203
+ elif method == "contourf":
2204
+ return "contour", "contourf"
2205
+ elif method == "stem":
2206
+ return "stem", "stem"
2207
+ elif method == "step":
2208
+ return "step", "step"
2209
+ elif method == "pie":
2210
+ return "pie", "pie"
2211
+ elif method == "quiver":
2212
+ return "quiver", "quiver"
2213
+ elif method == "streamplot":
2214
+ return "stream", "streamplot"
2215
+ elif method == "plot":
2216
+ return "line", "plot"
2217
+ # Note: "plot" method is handled last as a fallback since boxplot uses it internally
2218
+
2219
+ # Check for images (takes priority)
2220
+ if len(ax.images) > 0:
2221
+ return "image", "imshow"
2222
+
2223
+ # Check for 2D density plots (hist2d, hexbin) - QuadMesh or PolyCollection
2224
+ if hasattr(ax, "collections"):
2225
+ for coll in ax.collections:
2226
+ coll_type = type(coll).__name__
2227
+ if "QuadMesh" in coll_type:
2228
+ return "hist2d", "hist2d"
2229
+ if "PolyCollection" in coll_type and hasattr(coll, "get_array"):
2230
+ # hexbin creates PolyCollection with array data
2231
+ arr = coll.get_array()
2232
+ if arr is not None and len(arr) > 0:
2233
+ return "hexbin", "hexbin"
2234
+
2235
+ # Check for contours
2236
+ if hasattr(ax, "collections"):
2237
+ for coll in ax.collections:
2238
+ if "Contour" in type(coll).__name__:
2239
+ return "contour", "contour"
2240
+
2241
+ # Check for bar plots
2242
+ if len(ax.containers) > 0:
2243
+ # Check if it's a boxplot (has multiple containers with specific structure)
2244
+ if any("boxplot" in str(type(c)).lower() for c in ax.containers):
2245
+ return "boxplot", "boxplot"
2246
+ # Otherwise assume bar plot
2247
+ return "bar", "bar"
2248
+
2249
+ # Check for patches (could be histogram, violin, pie, etc.)
2250
+ if len(ax.patches) > 0:
2251
+ # Check for pie chart (Wedge patches)
2252
+ if any("Wedge" in type(p).__name__ for p in ax.patches):
2253
+ return "pie", "pie"
2254
+ # If there are many rectangular patches, likely histogram
2255
+ if len(ax.patches) > 5:
2256
+ return "hist", "hist"
2257
+ # Check for violin plot
2258
+ if any("Poly" in type(p).__name__ for p in ax.patches):
2259
+ return "violin", "violinplot"
2260
+
2261
+ # Check for scatter plots (PathCollection)
2262
+ if hasattr(ax, "collections") and len(ax.collections) > 0:
2263
+ for coll in ax.collections:
2264
+ if "PathCollection" in type(coll).__name__:
2265
+ return "scatter", "scatter"
2266
+
2267
+ # Check for line plots
2268
+ if len(ax.lines) > 0:
2269
+ # If there are error bars, it might be errorbar plot
2270
+ if any(hasattr(line, "_mpl_error") for line in ax.lines):
2271
+ return "errorbar", "errorbar"
2272
+ return "line", "plot"
2273
+
2274
+ return None, None
2275
+
2276
+
2277
+ def _extract_csv_columns_from_history(ax) -> list:
2278
+ """
2279
+ Extract CSV column names from scitex history for all plot types.
2280
+
2281
+ This function generates the exact column names that will be produced
2282
+ by export_as_csv(), providing a mapping between JSON metadata and CSV data.
2283
+
2284
+ Parameters
2285
+ ----------
2286
+ ax : AxisWrapper or matplotlib.axes.Axes
2287
+ The axes to extract CSV column info from
2288
+
2289
+ Returns
2290
+ -------
2291
+ list
2292
+ List of dictionaries containing CSV column mappings for each tracked plot,
2293
+ e.g., [{"id": "boxplot_0", "method": "boxplot", "columns": ["ax_00_boxplot_0_boxplot_0", "ax_00_boxplot_0_boxplot_1"]}]
2294
+ """
2295
+ from ._csv_column_naming import get_csv_column_name
2296
+
2297
+ # Get axes position for CSV column naming
2298
+ ax_row, ax_col = 0, 0 # Default for single axes
2299
+ if hasattr(ax, "_scitex_metadata") and "position_in_grid" in ax._scitex_metadata:
2300
+ pos = ax._scitex_metadata["position_in_grid"]
2301
+ ax_row, ax_col = pos[0], pos[1]
2302
+
2303
+ csv_columns_list = []
2304
+
2305
+ # Check if we have scitex history
2306
+ if not hasattr(ax, "history") or len(ax.history) == 0:
2307
+ return csv_columns_list
2308
+
2309
+ # Iterate through history to extract column names
2310
+ # Use enumerate to track trace index for proper CSV column naming
2311
+ for trace_index, (record_id, record) in enumerate(ax.history.items()):
2312
+ if not isinstance(record, tuple) or len(record) < 4:
2313
+ continue
2314
+
2315
+ id_val, method, tracked_dict, kwargs = record
2316
+
2317
+ # Generate column names using the same function as _extract_traces
2318
+ # This ensures consistency between plot.traces.csv_columns and data.columns
2319
+ columns = _get_csv_columns_for_method_with_index(
2320
+ id_val, method, tracked_dict, kwargs, ax_row, ax_col, trace_index
2321
+ )
2322
+
2323
+ if columns:
2324
+ csv_columns_list.append({
2325
+ "id": id_val,
2326
+ "method": method,
2327
+ "columns": columns,
2328
+ })
2329
+
2330
+ return csv_columns_list
2331
+
2332
+
2333
+ def _get_csv_columns_for_method_with_index(
2334
+ id_val, method, tracked_dict, kwargs, ax_row: int, ax_col: int, trace_index: int
2335
+ ) -> list:
2336
+ """
2337
+ Get CSV column names for a specific plotting method using trace index.
2338
+
2339
+ This function uses the same naming convention as _extract_traces to ensure
2340
+ consistency between plot.traces.csv_columns and data.columns.
2341
+
2342
+ Parameters
2343
+ ----------
2344
+ id_val : str
2345
+ The plot ID (e.g., "sine", "cosine")
2346
+ method : str
2347
+ The plotting method name (e.g., "plot", "scatter")
2348
+ tracked_dict : dict
2349
+ The tracked data dictionary
2350
+ kwargs : dict
2351
+ The keyword arguments passed to the plot
2352
+ ax_row : int
2353
+ Row index of axes in grid
2354
+ ax_col : int
2355
+ Column index of axes in grid
2356
+ trace_index : int
2357
+ Index of this trace (for deduplication)
2358
+
2359
+ Returns
2360
+ -------
2361
+ list
2362
+ List of column names that will be in the CSV
2363
+ """
2364
+ from ._csv_column_naming import get_csv_column_name
2365
+
2366
+ columns = []
2367
+
2368
+ # Use simplified variable names (x, y, bins, counts, etc.)
2369
+ # The full context comes from the column name structure:
2370
+ # ax-row_{row}_ax-col_{col}_trace-id_{id}_variable_{var}
2371
+ if method in ("plot", "stx_line"):
2372
+ columns = [
2373
+ get_csv_column_name("x", ax_row, ax_col, trace_index=trace_index),
2374
+ get_csv_column_name("y", ax_row, ax_col, trace_index=trace_index),
2375
+ ]
2376
+ elif method in ("scatter", "plot_scatter"):
2377
+ columns = [
2378
+ get_csv_column_name("x", ax_row, ax_col, trace_index=trace_index),
2379
+ get_csv_column_name("y", ax_row, ax_col, trace_index=trace_index),
2380
+ ]
2381
+ elif method in ("bar", "barh"):
2382
+ columns = [
2383
+ get_csv_column_name("x", ax_row, ax_col, trace_index=trace_index),
2384
+ get_csv_column_name("height", ax_row, ax_col, trace_index=trace_index),
2385
+ ]
2386
+ elif method == "hist":
2387
+ columns = [
2388
+ get_csv_column_name("bins", ax_row, ax_col, trace_index=trace_index),
2389
+ get_csv_column_name("counts", ax_row, ax_col, trace_index=trace_index),
2390
+ ]
2391
+ elif method in ("boxplot", "stx_box"):
2392
+ columns = [
2393
+ get_csv_column_name("data", ax_row, ax_col, trace_index=trace_index),
2394
+ ]
2395
+ elif method in ("violinplot", "stx_violin"):
2396
+ columns = [
2397
+ get_csv_column_name("data", ax_row, ax_col, trace_index=trace_index),
2398
+ ]
2399
+ elif method == "errorbar":
2400
+ columns = [
2401
+ get_csv_column_name("x", ax_row, ax_col, trace_index=trace_index),
2402
+ get_csv_column_name("y", ax_row, ax_col, trace_index=trace_index),
2403
+ get_csv_column_name("yerr", ax_row, ax_col, trace_index=trace_index),
2404
+ ]
2405
+ elif method == "fill_between":
2406
+ columns = [
2407
+ get_csv_column_name("x", ax_row, ax_col, trace_index=trace_index),
2408
+ get_csv_column_name("y1", ax_row, ax_col, trace_index=trace_index),
2409
+ get_csv_column_name("y2", ax_row, ax_col, trace_index=trace_index),
2410
+ ]
2411
+ elif method in ("imshow", "stx_heatmap", "stx_image"):
2412
+ columns = [
2413
+ get_csv_column_name("data", ax_row, ax_col, trace_index=trace_index),
2414
+ ]
2415
+ elif method in ("stx_kde", "stx_ecdf"):
2416
+ columns = [
2417
+ get_csv_column_name("x", ax_row, ax_col, trace_index=trace_index),
2418
+ get_csv_column_name("y", ax_row, ax_col, trace_index=trace_index),
2419
+ ]
2420
+ elif method in ("stx_mean_std", "stx_mean_ci", "stx_median_iqr", "stx_shaded_line"):
2421
+ columns = [
2422
+ get_csv_column_name("x", ax_row, ax_col, trace_index=trace_index),
2423
+ get_csv_column_name("y", ax_row, ax_col, trace_index=trace_index),
2424
+ get_csv_column_name("lower", ax_row, ax_col, trace_index=trace_index),
2425
+ get_csv_column_name("upper", ax_row, ax_col, trace_index=trace_index),
2426
+ ]
2427
+ elif method.startswith("sns_"):
2428
+ sns_type = method.replace("sns_", "")
2429
+ if sns_type in ("boxplot", "violinplot"):
2430
+ columns = [
2431
+ get_csv_column_name("data", ax_row, ax_col, trace_index=trace_index),
2432
+ ]
2433
+ elif sns_type in ("scatterplot", "lineplot"):
2434
+ columns = [
2435
+ get_csv_column_name("x", ax_row, ax_col, trace_index=trace_index),
2436
+ get_csv_column_name("y", ax_row, ax_col, trace_index=trace_index),
2437
+ ]
2438
+ elif sns_type == "barplot":
2439
+ columns = [
2440
+ get_csv_column_name("x", ax_row, ax_col, trace_index=trace_index),
2441
+ get_csv_column_name("y", ax_row, ax_col, trace_index=trace_index),
2442
+ ]
2443
+ elif sns_type == "histplot":
2444
+ columns = [
2445
+ get_csv_column_name("bins", ax_row, ax_col, trace_index=trace_index),
2446
+ get_csv_column_name("counts", ax_row, ax_col, trace_index=trace_index),
2447
+ ]
2448
+ elif sns_type == "kdeplot":
2449
+ columns = [
2450
+ get_csv_column_name("x", ax_row, ax_col, trace_index=trace_index),
2451
+ get_csv_column_name("y", ax_row, ax_col, trace_index=trace_index),
2452
+ ]
2453
+
2454
+ return columns
2455
+
2456
+
2457
+ def _compute_csv_hash_from_df(df) -> Optional[str]:
2458
+ """
2459
+ Compute a hash of CSV data from a DataFrame.
2460
+
2461
+ This is used after actual CSV export to compute the hash from the
2462
+ exact data that was written.
2463
+
2464
+ Parameters
2465
+ ----------
2466
+ df : pandas.DataFrame
2467
+ The DataFrame to compute hash from
2468
+
2469
+ Returns
2470
+ -------
2471
+ str or None
2472
+ SHA256 hash of the CSV data (first 16 chars), or None if unable to compute
2473
+ """
2474
+ import hashlib
2475
+
2476
+ try:
2477
+ if df is None or df.empty:
2478
+ return None
2479
+
2480
+ # Convert to CSV string for hashing
2481
+ csv_string = df.to_csv(index=False)
2482
+
2483
+ # Compute SHA256 hash
2484
+ hash_obj = hashlib.sha256(csv_string.encode("utf-8"))
2485
+ hash_hex = hash_obj.hexdigest()
2486
+
2487
+ # Return first 16 characters for readability
2488
+ return hash_hex[:16]
2489
+
2490
+ except Exception:
2491
+ return None
2492
+
2493
+
2494
+ def _compute_csv_hash(ax_or_df) -> Optional[str]:
2495
+ """
2496
+ Compute a hash of the CSV data for reproducibility verification.
2497
+
2498
+ The hash is computed from the actual data that would be exported to CSV,
2499
+ allowing verification that JSON and CSV files are in sync.
2500
+
2501
+ Note: The hash is computed from the AxisWrapper's export_as_csv(), which
2502
+ does NOT include the ax_{index:02d}_ prefix. The FigWrapper.export_as_csv()
2503
+ adds this prefix. We replicate this prefix addition here.
2504
+
2505
+ Parameters
2506
+ ----------
2507
+ ax_or_df : AxisWrapper, matplotlib.axes.Axes, or pandas.DataFrame
2508
+ The axes to compute CSV hash from, or a pre-exported DataFrame
2509
+
2510
+ Returns
2511
+ -------
2512
+ str or None
2513
+ SHA256 hash of the CSV data (first 16 chars), or None if unable to compute
2514
+ """
2515
+ import hashlib
2516
+
2517
+ import pandas as pd
2518
+
2519
+ # If it's already a DataFrame, use the direct hash function
2520
+ if isinstance(ax_or_df, pd.DataFrame):
2521
+ return _compute_csv_hash_from_df(ax_or_df)
2522
+
2523
+ ax = ax_or_df
2524
+
2525
+ # Check if we have scitex history with export capability
2526
+ if not hasattr(ax, "export_as_csv"):
2527
+ return None
2528
+
2529
+ try:
2530
+ # For single axes figures (most common case), ax_index = 0
2531
+ ax_index = 0
2532
+
2533
+ # Export the data as CSV from the AxisWrapper
2534
+ df = ax.export_as_csv()
2535
+
2536
+ if df is None or df.empty:
2537
+ return None
2538
+
2539
+ # Add axis prefix to match what FigWrapper.export_as_csv produces
2540
+ # Uses zero-padded index: ax_00_, ax_01_, etc.
2541
+ prefix = f"ax_{ax_index:02d}_"
2542
+ new_cols = []
2543
+ for col in df.columns:
2544
+ col_str = str(col)
2545
+ if not col_str.startswith(prefix):
2546
+ col_str = f"{prefix}{col_str}"
2547
+ new_cols.append(col_str)
2548
+ df.columns = new_cols
2549
+
2550
+ # Convert to CSV string for hashing
2551
+ csv_string = df.to_csv(index=False)
2552
+
2553
+ # Compute SHA256 hash
2554
+ hash_obj = hashlib.sha256(csv_string.encode("utf-8"))
2555
+ hash_hex = hash_obj.hexdigest()
2556
+
2557
+ # Return first 16 characters for readability
2558
+ return hash_hex[:16]
2559
+
2560
+ except Exception:
2561
+ return None
2562
+
2563
+
2564
+ def _get_csv_columns_for_method(id_val, method, tracked_dict, kwargs, ax_index: int) -> list:
2565
+ """
2566
+ Get CSV column names for a specific plotting method.
2567
+
2568
+ This simulates the actual CSV export to get exact column names.
2569
+ It uses the same formatters that generate the CSV to ensure consistency.
2570
+
2571
+ Architecture note:
2572
+ - CSV formatters (e.g., _format_boxplot) generate columns WITHOUT ax_ prefix
2573
+ - FigWrapper.export_as_csv() adds the ax_{index:02d}_ prefix
2574
+ - This function simulates that process to get the final column names
2575
+
2576
+ Parameters
2577
+ ----------
2578
+ id_val : str
2579
+ The plot ID (e.g., "boxplot_0", "plot_0")
2580
+ method : str
2581
+ The plotting method name (e.g., "boxplot", "plot", "scatter")
2582
+ tracked_dict : dict
2583
+ The tracked data dictionary
2584
+ kwargs : dict
2585
+ The keyword arguments passed to the plot
2586
+ ax_index : int
2587
+ Flattened index of axes (0 for single axes, 0-N for multi-axes)
2588
+
2589
+ Returns
2590
+ -------
2591
+ list
2592
+ List of column names that will be in the CSV (exact match)
2593
+ """
2594
+ # Import the actual formatters to ensure consistency
2595
+ # This is the single source of truth - we use the same code path as CSV export
2596
+ try:
2597
+ from scitex.plt._subplots._export_as_csv import format_record
2598
+ import pandas as pd
2599
+
2600
+ # Construct the record tuple as used in tracking
2601
+ record = (id_val, method, tracked_dict, kwargs)
2602
+
2603
+ # Call the actual formatter to get the DataFrame
2604
+ df = format_record(record)
2605
+
2606
+ if df is not None and not df.empty:
2607
+ # Add the axis prefix (this is what FigWrapper.export_as_csv does)
2608
+ # Uses zero-padded index: ax_00_, ax_01_, etc.
2609
+ prefix = f"ax_{ax_index:02d}_"
2610
+ columns = []
2611
+ for col in df.columns:
2612
+ col_str = str(col)
2613
+ if not col_str.startswith(prefix):
2614
+ col_str = f"{prefix}{col_str}"
2615
+ columns.append(col_str)
2616
+ return columns
2617
+
2618
+ except Exception:
2619
+ # If formatters fail, fall back to pattern-based generation
2620
+ pass
2621
+
2622
+ # Fallback: Pattern-based column name generation
2623
+ # This should rarely be used since we prefer the actual formatter
2624
+ import numpy as np
2625
+
2626
+ prefix = f"ax_{ax_index:02d}_"
2627
+ columns = []
2628
+
2629
+ # Get args from tracked_dict
2630
+ args = tracked_dict.get("args", []) if tracked_dict else []
2631
+
2632
+ if method in ("boxplot", "stx_box"):
2633
+ # Boxplot: one column per box (mirrors _format_boxplot)
2634
+ if len(args) >= 1:
2635
+ data = args[0]
2636
+ labels = kwargs.get("labels", None) if kwargs else None
2637
+
2638
+ from scitex.types import is_listed_X as scitex_types_is_listed_X
2639
+
2640
+ if isinstance(data, np.ndarray) or scitex_types_is_listed_X(data, [float, int]):
2641
+ # Single box
2642
+ if labels and len(labels) == 1:
2643
+ columns.append(f"{prefix}{id_val}_{labels[0]}")
2644
+ else:
2645
+ columns.append(f"{prefix}{id_val}_boxplot_0")
2646
+ else:
2647
+ # Multiple boxes
2648
+ try:
2649
+ num_boxes = len(data)
2650
+ if labels and len(labels) == num_boxes:
2651
+ for label in labels:
2652
+ columns.append(f"{prefix}{id_val}_{label}")
2653
+ else:
2654
+ for i in range(num_boxes):
2655
+ columns.append(f"{prefix}{id_val}_boxplot_{i}")
2656
+ except TypeError:
2657
+ columns.append(f"{prefix}{id_val}_boxplot_0")
2658
+
2659
+ elif method in ("plot", "stx_line"):
2660
+ # Line plot: x and y columns
2661
+ # For single axes (ax_index=0), use simple prefix
2662
+ columns.append(f"{prefix}{id_val}_plot_x")
2663
+ columns.append(f"{prefix}{id_val}_plot_y")
2664
+
2665
+ elif method in ("scatter", "plot_scatter"):
2666
+ columns.append(f"{prefix}{id_val}_scatter_x")
2667
+ columns.append(f"{prefix}{id_val}_scatter_y")
2668
+
2669
+ elif method in ("bar", "barh"):
2670
+ columns.append(f"{prefix}{id_val}_bar_x")
2671
+ columns.append(f"{prefix}{id_val}_bar_height")
2672
+
2673
+ elif method == "hist":
2674
+ columns.append(f"{prefix}{id_val}_hist_bins")
2675
+ columns.append(f"{prefix}{id_val}_hist_counts")
2676
+
2677
+ elif method in ("violinplot", "stx_violin"):
2678
+ if len(args) >= 1:
2679
+ data = args[0]
2680
+ try:
2681
+ num_violins = len(data)
2682
+ for i in range(num_violins):
2683
+ columns.append(f"{prefix}{id_val}_violin_{i}")
2684
+ except TypeError:
2685
+ columns.append(f"{prefix}{id_val}_violin_0")
2686
+
2687
+ elif method == "errorbar":
2688
+ columns.append(f"{prefix}{id_val}_errorbar_x")
2689
+ columns.append(f"{prefix}{id_val}_errorbar_y")
2690
+ columns.append(f"{prefix}{id_val}_errorbar_yerr")
2691
+
2692
+ elif method == "fill_between":
2693
+ columns.append(f"{prefix}{id_val}_fill_x")
2694
+ columns.append(f"{prefix}{id_val}_fill_y1")
2695
+ columns.append(f"{prefix}{id_val}_fill_y2")
2696
+
2697
+ elif method in ("imshow", "stx_heatmap", "stx_image"):
2698
+ if len(args) >= 1:
2699
+ data = args[0]
2700
+ try:
2701
+ if hasattr(data, "shape") and len(data.shape) >= 2:
2702
+ columns.append(f"{prefix}{id_val}_image_data")
2703
+ except (TypeError, AttributeError):
2704
+ pass
2705
+
2706
+ elif method in ("stx_kde", "stx_ecdf"):
2707
+ suffix = method.replace("stx_", "")
2708
+ columns.append(f"{prefix}{id_val}_{suffix}_x")
2709
+ columns.append(f"{prefix}{id_val}_{suffix}_y")
2710
+
2711
+ elif method in ("stx_mean_std", "stx_mean_ci", "stx_median_iqr", "stx_shaded_line"):
2712
+ suffix = method.replace("stx_", "")
2713
+ columns.append(f"{prefix}{id_val}_{suffix}_x")
2714
+ columns.append(f"{prefix}{id_val}_{suffix}_y")
2715
+ columns.append(f"{prefix}{id_val}_{suffix}_lower")
2716
+ columns.append(f"{prefix}{id_val}_{suffix}_upper")
2717
+
2718
+ elif method.startswith("sns_"):
2719
+ sns_type = method.replace("sns_", "")
2720
+ if sns_type in ("boxplot", "violinplot"):
2721
+ columns.append(f"{prefix}{id_val}_{sns_type}_data")
2722
+ elif sns_type in ("scatterplot", "lineplot"):
2723
+ columns.append(f"{prefix}{id_val}_{sns_type}_x")
2724
+ columns.append(f"{prefix}{id_val}_{sns_type}_y")
2725
+ elif sns_type == "barplot":
2726
+ columns.append(f"{prefix}{id_val}_barplot_x")
2727
+ columns.append(f"{prefix}{id_val}_barplot_y")
2728
+ elif sns_type == "histplot":
2729
+ columns.append(f"{prefix}{id_val}_histplot_bins")
2730
+ columns.append(f"{prefix}{id_val}_histplot_counts")
2731
+ elif sns_type == "kdeplot":
2732
+ columns.append(f"{prefix}{id_val}_kdeplot_x")
2733
+ columns.append(f"{prefix}{id_val}_kdeplot_y")
2734
+
2735
+ return columns
2736
+
2737
+
2738
+ def assert_csv_json_consistency(csv_path: str, json_path: str = None) -> None:
2739
+ """
2740
+ Assert that CSV data file and its JSON metadata are consistent.
2741
+
2742
+ Raises AssertionError if the column names don't match.
2743
+
2744
+ Parameters
2745
+ ----------
2746
+ csv_path : str
2747
+ Path to the CSV data file
2748
+ json_path : str, optional
2749
+ Path to the JSON metadata file. If not provided, assumes
2750
+ the JSON is at the same location with .json extension.
2751
+
2752
+ Raises
2753
+ ------
2754
+ AssertionError
2755
+ If CSV and JSON column names don't match
2756
+ FileNotFoundError
2757
+ If CSV or JSON files don't exist
2758
+
2759
+ Examples
2760
+ --------
2761
+ >>> assert_csv_json_consistency('/tmp/plot.csv') # Passes silently if valid
2762
+ >>> # Or use in tests:
2763
+ >>> try:
2764
+ ... assert_csv_json_consistency('/tmp/plot.csv')
2765
+ ... except AssertionError as e:
2766
+ ... print(f"Validation failed: {e}")
2767
+ """
2768
+ result = verify_csv_json_consistency(csv_path, json_path)
2769
+
2770
+ if result['errors']:
2771
+ raise FileNotFoundError('\n'.join(result['errors']))
2772
+
2773
+ if not result['valid']:
2774
+ msg_parts = ["CSV/JSON consistency check failed:"]
2775
+ if result['missing_in_csv']:
2776
+ msg_parts.append(f" columns_actual missing in CSV: {result['missing_in_csv']}")
2777
+ if result['extra_in_csv']:
2778
+ msg_parts.append(f" Extra columns in CSV: {result['extra_in_csv']}")
2779
+ if result.get('data_ref_missing'):
2780
+ msg_parts.append(f" data_ref columns missing in CSV: {result['data_ref_missing']}")
2781
+ raise AssertionError('\n'.join(msg_parts))
2782
+
2783
+
2784
+ def verify_csv_json_consistency(csv_path: str, json_path: str = None) -> dict:
2785
+ """
2786
+ Verify consistency between CSV data file and its JSON metadata.
2787
+
2788
+ This function checks that:
2789
+ 1. Column names in the CSV file match those declared in JSON's columns_actual
2790
+ 2. Artist data_ref values in JSON match actual CSV column names
2791
+
2792
+ Parameters
2793
+ ----------
2794
+ csv_path : str
2795
+ Path to the CSV data file
2796
+ json_path : str, optional
2797
+ Path to the JSON metadata file. If not provided, assumes
2798
+ the JSON is at the same location with .json extension.
2799
+
2800
+ Returns
2801
+ -------
2802
+ dict
2803
+ Verification result with keys:
2804
+ - 'valid': bool - True if CSV and JSON are consistent
2805
+ - 'csv_columns': list - Column names found in CSV
2806
+ - 'json_columns': list - Column names declared in JSON
2807
+ - 'data_ref_columns': list - Column names from artist data_ref
2808
+ - 'missing_in_csv': list - Columns in JSON but not in CSV
2809
+ - 'extra_in_csv': list - Columns in CSV but not in JSON
2810
+ - 'data_ref_missing': list - data_ref columns not found in CSV
2811
+ - 'errors': list - Any error messages
2812
+
2813
+ Examples
2814
+ --------
2815
+ >>> result = verify_csv_json_consistency('/tmp/plot.csv')
2816
+ >>> print(result['valid'])
2817
+ True
2818
+ >>> print(result['missing_in_csv'])
2819
+ []
2820
+ """
2821
+ import json
2822
+ import os
2823
+ import pandas as pd
2824
+
2825
+ result = {
2826
+ 'valid': False,
2827
+ 'csv_columns': [],
2828
+ 'json_columns': [],
2829
+ 'data_ref_columns': [],
2830
+ 'missing_in_csv': [],
2831
+ 'extra_in_csv': [],
2832
+ 'data_ref_missing': [],
2833
+ 'errors': [],
2834
+ }
2835
+
2836
+ # Determine JSON path
2837
+ if json_path is None:
2838
+ base, _ = os.path.splitext(csv_path)
2839
+ json_path = base + '.json'
2840
+
2841
+ # Check files exist
2842
+ if not os.path.exists(csv_path):
2843
+ result['errors'].append(f"CSV file not found: {csv_path}")
2844
+ return result
2845
+ if not os.path.exists(json_path):
2846
+ result['errors'].append(f"JSON file not found: {json_path}")
2847
+ return result
2848
+
2849
+ try:
2850
+ # Read CSV columns
2851
+ df = pd.read_csv(csv_path, nrows=0) # Just read header
2852
+ csv_columns = list(df.columns)
2853
+ result['csv_columns'] = csv_columns
2854
+ except Exception as e:
2855
+ result['errors'].append(f"Error reading CSV: {e}")
2856
+ return result
2857
+
2858
+ try:
2859
+ # Read JSON metadata
2860
+ with open(json_path, 'r') as f:
2861
+ metadata = json.load(f)
2862
+
2863
+ # Get columns_actual from data section
2864
+ json_columns = []
2865
+ if 'data' in metadata and 'columns_actual' in metadata['data']:
2866
+ json_columns = metadata['data']['columns_actual']
2867
+ result['json_columns'] = json_columns
2868
+
2869
+ # Extract data_ref columns from artists
2870
+ # Skip 'derived_from' key as it contains descriptive text, not CSV column names
2871
+ # Also skip 'row_index' as it's a numeric index, not a column name
2872
+ data_ref_columns = []
2873
+ skip_keys = {'derived_from', 'row_index'}
2874
+ if 'axes' in metadata:
2875
+ for ax_key, ax_data in metadata['axes'].items():
2876
+ if 'artists' in ax_data:
2877
+ for artist in ax_data['artists']:
2878
+ if 'data_ref' in artist:
2879
+ for key, val in artist['data_ref'].items():
2880
+ if key not in skip_keys and isinstance(val, str):
2881
+ data_ref_columns.append(val)
2882
+ result['data_ref_columns'] = data_ref_columns
2883
+
2884
+ except Exception as e:
2885
+ result['errors'].append(f"Error reading JSON: {e}")
2886
+ return result
2887
+
2888
+ # Compare columns_actual with CSV
2889
+ csv_set = set(csv_columns)
2890
+ json_set = set(json_columns)
2891
+
2892
+ result['missing_in_csv'] = list(json_set - csv_set)
2893
+ result['extra_in_csv'] = list(csv_set - json_set)
2894
+
2895
+ # Check data_ref columns exist in CSV (if there are any)
2896
+ if data_ref_columns:
2897
+ data_ref_set = set(data_ref_columns)
2898
+ result['data_ref_missing'] = list(data_ref_set - csv_set)
2899
+
2900
+ # Valid only if columns_actual matches AND data_ref columns are found in CSV
2901
+ result['valid'] = (
2902
+ len(result['missing_in_csv']) == 0 and
2903
+ len(result['extra_in_csv']) == 0 and
2904
+ len(result['data_ref_missing']) == 0
2905
+ )
2906
+
2907
+ return result
2908
+
2909
+
2910
+ def collect_recipe_metadata(
2911
+ fig,
2912
+ ax=None,
2913
+ auto_crop: bool = True,
2914
+ crop_margin_mm: float = 1.0,
2915
+ ) -> Dict:
2916
+ """
2917
+ Collect minimal "recipe" metadata from figure - method calls + data refs.
2918
+
2919
+ Unlike `collect_figure_metadata()` which captures every rendered artist,
2920
+ this function captures only what's needed to reproduce the figure:
2921
+ - Figure/axes dimensions and limits
2922
+ - Method calls with arguments (from ax.history)
2923
+ - Data column references for CSV linkage
2924
+ - Cropping settings
2925
+
2926
+ This produces much smaller JSON files (e.g., 60 lines vs 1300 for histogram).
2927
+
2928
+ Parameters
2929
+ ----------
2930
+ fig : matplotlib.figure.Figure
2931
+ Figure to collect metadata from
2932
+ ax : matplotlib.axes.Axes or AxisWrapper, optional
2933
+ Primary axes to collect from. If not provided, uses first axes.
2934
+ auto_crop : bool, optional
2935
+ Whether auto-cropping is enabled. Default is True.
2936
+ crop_margin_mm : float, optional
2937
+ Margin in mm for auto-cropping. Default is 1.0.
2938
+
2939
+ Returns
2940
+ -------
2941
+ dict
2942
+ Minimal metadata dictionary with structure:
2943
+ - scitex_schema: "scitex.plt.figure.recipe"
2944
+ - scitex_schema_version: "0.2.0"
2945
+ - figure: {size_mm, dpi, mode, auto_crop, crop_margin_mm}
2946
+ - axes: {ax_00: {xaxis, yaxis, calls: [...]}}
2947
+ - data: {csv_path, columns}
2948
+
2949
+ Examples
2950
+ --------
2951
+ >>> fig, ax = scitex.plt.subplots()
2952
+ >>> ax.hist(data, bins=40, id="histogram")
2953
+ >>> metadata = collect_recipe_metadata(fig, ax)
2954
+ >>> # Result has ~60 lines instead of ~1300
2955
+ """
2956
+ import datetime
2957
+ import uuid
2958
+
2959
+ import matplotlib
2960
+ import scitex
2961
+
2962
+ metadata = {
2963
+ "scitex_schema": "scitex.plt.figure.recipe",
2964
+ "scitex_schema_version": "0.2.0",
2965
+ "figure_uuid": str(uuid.uuid4()),
2966
+ "runtime": {
2967
+ "scitex_version": scitex.__version__,
2968
+ "matplotlib_version": matplotlib.__version__,
2969
+ "created_at": datetime.datetime.now().isoformat(),
2970
+ },
2971
+ }
2972
+
2973
+ # Collect axes - handle AxesWrapper (multi-axes) properly
2974
+ all_axes = [] # List of (ax_wrapper, row, col) tuples
2975
+ grid_shape = (1, 1)
2976
+
2977
+ if ax is not None:
2978
+ # Handle AxesWrapper (multi-axes) - extract individual AxisWrappers with positions
2979
+ if hasattr(ax, "_axes_scitex"):
2980
+ import numpy as np
2981
+ axes_array = ax._axes_scitex
2982
+ if isinstance(axes_array, np.ndarray):
2983
+ grid_shape = axes_array.shape
2984
+ for idx, ax_item in enumerate(axes_array.flat):
2985
+ row = idx // grid_shape[1]
2986
+ col = idx % grid_shape[1]
2987
+ all_axes.append((ax_item, row, col))
2988
+ else:
2989
+ all_axes = [(axes_array, 0, 0)]
2990
+ # Handle AxisWrapper (single axes)
2991
+ elif hasattr(ax, "_axis_mpl"):
2992
+ all_axes = [(ax, 0, 0)]
2993
+ else:
2994
+ # Assume it's a matplotlib axes
2995
+ all_axes = [(ax, 0, 0)]
2996
+ elif hasattr(fig, "axes") and len(fig.axes) > 0:
2997
+ # Fallback to figure axes (linear indexing)
2998
+ for idx, ax_item in enumerate(fig.axes):
2999
+ all_axes.append((ax_item, 0, idx))
3000
+
3001
+ # Figure-level properties
3002
+ if all_axes:
3003
+ try:
3004
+ from ._figure_from_axes_mm import get_dimension_info
3005
+ first_ax_tuple = all_axes[0]
3006
+ first_ax = first_ax_tuple[0]
3007
+ # Get underlying matplotlib axis if wrapped
3008
+ mpl_ax = getattr(first_ax, '_axis_mpl', first_ax)
3009
+ dim_info = get_dimension_info(fig, mpl_ax)
3010
+
3011
+ # Convert to plain lists/floats for JSON serialization
3012
+ size_mm = dim_info["figure_size_mm"]
3013
+ if hasattr(size_mm, 'tolist'):
3014
+ size_mm = size_mm.tolist()
3015
+ elif isinstance(size_mm, (list, tuple)):
3016
+ size_mm = [float(v) if hasattr(v, 'value') else v for v in size_mm]
3017
+
3018
+ metadata["figure"] = {
3019
+ "size_mm": size_mm,
3020
+ "dpi": int(dim_info["dpi"]),
3021
+ "auto_crop": auto_crop,
3022
+ "crop_margin_mm": crop_margin_mm,
3023
+ }
3024
+
3025
+ # Add top-level axes_bbox_px for canvas/web alignment (x0/y0/x1/y1 format)
3026
+ # x0: left edge (Y-axis position), y1: bottom edge (X-axis position)
3027
+ if "axes_bbox_px" in dim_info:
3028
+ bbox = dim_info["axes_bbox_px"]
3029
+ metadata["axes_bbox_px"] = {
3030
+ "x0": int(bbox["x0"]),
3031
+ "y0": int(bbox["y0"]),
3032
+ "x1": int(bbox["x1"]),
3033
+ "y1": int(bbox["y1"]),
3034
+ "width": int(bbox["width"]),
3035
+ "height": int(bbox["height"]),
3036
+ }
3037
+ if "axes_bbox_mm" in dim_info:
3038
+ bbox = dim_info["axes_bbox_mm"]
3039
+ metadata["axes_bbox_mm"] = {
3040
+ "x0": round(float(bbox["x0"]), 2),
3041
+ "y0": round(float(bbox["y0"]), 2),
3042
+ "x1": round(float(bbox["x1"]), 2),
3043
+ "y1": round(float(bbox["y1"]), 2),
3044
+ "width": round(float(bbox["width"]), 2),
3045
+ "height": round(float(bbox["height"]), 2),
3046
+ }
3047
+ except Exception:
3048
+ pass
3049
+
3050
+ # Add mode from scitex metadata
3051
+ scitex_meta = None
3052
+ if ax is not None and hasattr(ax, "_scitex_metadata"):
3053
+ scitex_meta = ax._scitex_metadata
3054
+ elif hasattr(fig, "_scitex_metadata"):
3055
+ scitex_meta = fig._scitex_metadata
3056
+
3057
+ if scitex_meta:
3058
+ if "figure" not in metadata:
3059
+ metadata["figure"] = {}
3060
+ if "mode" in scitex_meta:
3061
+ metadata["figure"]["mode"] = scitex_meta["mode"]
3062
+ # Include style_mm for reproducibility (thickness, fonts, etc.)
3063
+ if "style_mm" in scitex_meta:
3064
+ metadata["style"] = scitex_meta["style_mm"]
3065
+
3066
+ # Collect per-axes metadata with calls
3067
+ if all_axes:
3068
+ metadata["axes"] = {}
3069
+ for current_ax, row, col in all_axes:
3070
+ # Use row-col format: ax_00, ax_01, ax_10, ax_11 for 2x2 grid
3071
+ ax_key = f"ax_{row}{col}"
3072
+
3073
+ # Get underlying matplotlib axis if wrapped
3074
+ mpl_ax = getattr(current_ax, '_axis_mpl', current_ax)
3075
+
3076
+ ax_meta = {
3077
+ "grid_position": {"row": row, "col": col}
3078
+ }
3079
+
3080
+ # Additional position info from scitex_metadata if available
3081
+ if hasattr(current_ax, "_scitex_metadata"):
3082
+ pos = current_ax._scitex_metadata.get("position_in_grid")
3083
+ if pos:
3084
+ ax_meta["grid_position"] = {"row": pos[0], "col": pos[1]}
3085
+
3086
+ # Axis labels and limits (minimal - for axis alignment)
3087
+ try:
3088
+ xlim = mpl_ax.get_xlim()
3089
+ ylim = mpl_ax.get_ylim()
3090
+ ax_meta["xaxis"] = {
3091
+ "label": mpl_ax.get_xlabel() or "",
3092
+ "lim": [round(xlim[0], 4), round(xlim[1], 4)],
3093
+ }
3094
+ ax_meta["yaxis"] = {
3095
+ "label": mpl_ax.get_ylabel() or "",
3096
+ "lim": [round(ylim[0], 4), round(ylim[1], 4)],
3097
+ }
3098
+ except Exception:
3099
+ pass
3100
+
3101
+ # Method calls from history - the core "recipe"
3102
+ # Pass row and col for proper data_ref column naming
3103
+ ax_index = row * grid_shape[1] + col
3104
+ ax_meta["calls"] = _extract_calls_from_history(current_ax, ax_index)
3105
+
3106
+ metadata["axes"][ax_key] = ax_meta
3107
+
3108
+ return metadata
3109
+
3110
+
3111
+ def _extract_calls_from_history(ax, ax_index: int) -> List[dict]:
3112
+ """
3113
+ Extract method call records from axis history.
3114
+
3115
+ Parameters
3116
+ ----------
3117
+ ax : AxisWrapper or matplotlib.axes.Axes
3118
+ Axis to extract history from
3119
+ ax_index : int
3120
+ Index of axis in figure (for CSV column naming)
3121
+
3122
+ Returns
3123
+ -------
3124
+ list
3125
+ List of call records: [{id, method, data_ref, kwargs}, ...]
3126
+ """
3127
+ calls = []
3128
+
3129
+ # Check for scitex wrapper with history
3130
+ if not hasattr(ax, 'history') and not hasattr(ax, '_ax_history'):
3131
+ return calls
3132
+
3133
+ # Get history dict
3134
+ history = getattr(ax, 'history', None)
3135
+ if history is None:
3136
+ history = getattr(ax, '_ax_history', {})
3137
+
3138
+ # Get grid position
3139
+ ax_row = 0
3140
+ ax_col = 0
3141
+ if hasattr(ax, "_scitex_metadata"):
3142
+ pos = ax._scitex_metadata.get("position_in_grid", [0, 0])
3143
+ ax_row, ax_col = pos[0], pos[1]
3144
+
3145
+ for trace_id, record in history.items():
3146
+ # record format: (id, method_name, tracked_dict, kwargs)
3147
+ if not isinstance(record, (list, tuple)) or len(record) < 3:
3148
+ continue
3149
+
3150
+ call_id, method_name, tracked_dict = record[0], record[1], record[2]
3151
+ kwargs = record[3] if len(record) > 3 else {}
3152
+
3153
+ call = {
3154
+ "id": str(call_id),
3155
+ "method": method_name,
3156
+ }
3157
+
3158
+ # Build data_ref from tracked_dict to CSV column names
3159
+ data_ref = _build_data_ref(call_id, method_name, tracked_dict, ax_row, ax_col)
3160
+ if data_ref:
3161
+ call["data_ref"] = data_ref
3162
+
3163
+ # Filter kwargs to only style-relevant ones (not data)
3164
+ style_kwargs = _filter_style_kwargs(kwargs, method_name)
3165
+ if style_kwargs:
3166
+ call["kwargs"] = style_kwargs
3167
+
3168
+ calls.append(call)
3169
+
3170
+ return calls
3171
+
3172
+
3173
+ def _build_data_ref(trace_id, method_name: str, tracked_dict: dict,
3174
+ ax_row: int, ax_col: int) -> dict:
3175
+ """
3176
+ Build data_ref mapping from tracked_dict to CSV column names.
3177
+
3178
+ Parameters
3179
+ ----------
3180
+ trace_id : str
3181
+ Trace identifier
3182
+ method_name : str
3183
+ Name of the method called
3184
+ tracked_dict : dict
3185
+ Data tracked by the method (contains arrays, dataframes)
3186
+ ax_row, ax_col : int
3187
+ Axis position in grid
3188
+
3189
+ Returns
3190
+ -------
3191
+ dict
3192
+ Mapping of variable names to CSV column names
3193
+ """
3194
+ prefix = f"ax-row-{ax_row}-col-{ax_col}_trace-id-{trace_id}_variable-"
3195
+
3196
+ data_ref = {}
3197
+
3198
+ # Method-specific column naming
3199
+ if method_name == 'hist':
3200
+ # Histogram: raw data + computed bins
3201
+ data_ref["raw_data"] = f"{prefix}raw-data"
3202
+ data_ref["bin_centers"] = f"{prefix}bin-centers"
3203
+ data_ref["bin_counts"] = f"{prefix}bin-counts"
3204
+ elif method_name in ('plot', 'scatter', 'step', 'errorbar'):
3205
+ # Standard x, y plots
3206
+ data_ref["x"] = f"{prefix}x"
3207
+ data_ref["y"] = f"{prefix}y"
3208
+ # Check for error bars in tracked_dict
3209
+ if tracked_dict and 'yerr' in tracked_dict:
3210
+ data_ref["yerr"] = f"{prefix}yerr"
3211
+ if tracked_dict and 'xerr' in tracked_dict:
3212
+ data_ref["xerr"] = f"{prefix}xerr"
3213
+ elif method_name in ('bar', 'barh'):
3214
+ data_ref["x"] = f"{prefix}x"
3215
+ data_ref["y"] = f"{prefix}y"
3216
+ elif method_name == 'stem':
3217
+ data_ref["x"] = f"{prefix}x"
3218
+ data_ref["y"] = f"{prefix}y"
3219
+ elif method_name in ('fill_between', 'fill_betweenx'):
3220
+ data_ref["x"] = f"{prefix}x"
3221
+ data_ref["y1"] = f"{prefix}y1"
3222
+ data_ref["y2"] = f"{prefix}y2"
3223
+ elif method_name in ('imshow', 'matshow', 'pcolormesh'):
3224
+ data_ref["data"] = f"{prefix}data"
3225
+ elif method_name in ('contour', 'contourf'):
3226
+ data_ref["x"] = f"{prefix}x"
3227
+ data_ref["y"] = f"{prefix}y"
3228
+ data_ref["z"] = f"{prefix}z"
3229
+ elif method_name in ('boxplot', 'violinplot'):
3230
+ data_ref["data"] = f"{prefix}data"
3231
+ elif method_name == 'pie':
3232
+ data_ref["x"] = f"{prefix}x"
3233
+ elif method_name in ('quiver', 'streamplot'):
3234
+ data_ref["x"] = f"{prefix}x"
3235
+ data_ref["y"] = f"{prefix}y"
3236
+ data_ref["u"] = f"{prefix}u"
3237
+ data_ref["v"] = f"{prefix}v"
3238
+ elif method_name == 'hexbin':
3239
+ data_ref["x"] = f"{prefix}x"
3240
+ data_ref["y"] = f"{prefix}y"
3241
+ elif method_name == 'hist2d':
3242
+ data_ref["x"] = f"{prefix}x"
3243
+ data_ref["y"] = f"{prefix}y"
3244
+ elif method_name == 'kde':
3245
+ data_ref["x"] = f"{prefix}x"
3246
+ data_ref["y"] = f"{prefix}y"
3247
+ # SciTeX custom methods (stx_*) - use same naming as matplotlib wrappers
3248
+ elif method_name == 'stx_line':
3249
+ data_ref["x"] = f"{prefix}x"
3250
+ data_ref["y"] = f"{prefix}y"
3251
+ elif method_name in ('stx_mean_std', 'stx_mean_ci', 'stx_median_iqr', 'stx_shaded_line'):
3252
+ data_ref["x"] = f"{prefix}x"
3253
+ data_ref["y_lower"] = f"{prefix}y-lower"
3254
+ data_ref["y_middle"] = f"{prefix}y-middle"
3255
+ data_ref["y_upper"] = f"{prefix}y-upper"
3256
+ elif method_name in ('stx_box', 'stx_violin'):
3257
+ data_ref["data"] = f"{prefix}data"
3258
+ elif method_name == 'stx_scatter_hist':
3259
+ data_ref["x"] = f"{prefix}x"
3260
+ data_ref["y"] = f"{prefix}y"
3261
+ elif method_name in ('stx_heatmap', 'stx_conf_mat', 'stx_image', 'stx_raster'):
3262
+ data_ref["data"] = f"{prefix}data"
3263
+ elif method_name in ('stx_kde', 'stx_ecdf'):
3264
+ data_ref["x"] = f"{prefix}x"
3265
+ data_ref["y"] = f"{prefix}y"
3266
+ elif method_name.startswith('stx_'):
3267
+ # Generic fallback for other stx_ methods
3268
+ data_ref["x"] = f"{prefix}x"
3269
+ data_ref["y"] = f"{prefix}y"
3270
+ else:
3271
+ # Generic fallback for tracked data
3272
+ if tracked_dict:
3273
+ if 'x' in tracked_dict or 'args' in tracked_dict:
3274
+ data_ref["x"] = f"{prefix}x"
3275
+ data_ref["y"] = f"{prefix}y"
3276
+
3277
+ return data_ref
3278
+
3279
+
3280
+ def _filter_style_kwargs(kwargs: dict, method_name: str) -> dict:
3281
+ """
3282
+ Filter kwargs to only include style-relevant parameters.
3283
+
3284
+ Removes data arrays and internal parameters, keeps style settings
3285
+ that affect appearance (color, linewidth, etc.).
3286
+
3287
+ Parameters
3288
+ ----------
3289
+ kwargs : dict
3290
+ Original keyword arguments
3291
+ method_name : str
3292
+ Name of the method
3293
+
3294
+ Returns
3295
+ -------
3296
+ dict
3297
+ Filtered kwargs with only style parameters
3298
+ """
3299
+ if not kwargs:
3300
+ return {}
3301
+
3302
+ # Style-relevant kwargs to keep
3303
+ style_keys = {
3304
+ 'color', 'c', 'facecolor', 'edgecolor', 'linecolor',
3305
+ 'linewidth', 'lw', 'linestyle', 'ls',
3306
+ 'marker', 'markersize', 'ms', 'markerfacecolor', 'markeredgecolor',
3307
+ 'alpha', 'zorder',
3308
+ 'label',
3309
+ 'bins', 'density', 'histtype', 'orientation',
3310
+ 'width', 'height', 'align',
3311
+ 'cmap', 'vmin', 'vmax', 'norm',
3312
+ 'levels', 'extend',
3313
+ 'scale', 'units',
3314
+ 'autopct', 'explode', 'shadow', 'startangle',
3315
+ }
3316
+
3317
+ filtered = {}
3318
+ for key, value in kwargs.items():
3319
+ if key in style_keys:
3320
+ # Skip if value is a large array (data, not style)
3321
+ if hasattr(value, '__len__') and not isinstance(value, str):
3322
+ if len(value) > 10:
3323
+ continue
3324
+ # Round float values to 4 decimal places for cleaner JSON
3325
+ if isinstance(value, float):
3326
+ value = round(value, 4)
3327
+ filtered[key] = value
3328
+
3329
+ return filtered
3330
+
3331
+
3332
+ if __name__ == "__main__":
3333
+ import numpy as np
3334
+
3335
+ from ._figure_from_axes_mm import create_axes_with_size_mm
3336
+
3337
+ print("=" * 60)
3338
+ print("METADATA COLLECTION DEMO")
3339
+ print("=" * 60)
3340
+
3341
+ # Create a figure with mm control
3342
+ print("\n1. Creating figure with mm control...")
3343
+ fig, ax = create_axes_with_size_mm(
3344
+ axes_width_mm=30,
3345
+ axes_height_mm=21,
3346
+ mode="publication",
3347
+ style_mm={
3348
+ "axis_thickness_mm": 0.2,
3349
+ "trace_thickness_mm": 0.12,
3350
+ "tick_length_mm": 0.8,
3351
+ },
3352
+ )
3353
+
3354
+ # Plot something
3355
+ x = np.linspace(0, 2 * np.pi, 100)
3356
+ ax.plot(x, np.sin(x), "b-")
3357
+ ax.set_xlabel("X axis")
3358
+ ax.set_ylabel("Y axis")
3359
+
3360
+ # Collect metadata
3361
+ print("\n2. Collecting metadata...")
3362
+ metadata = collect_figure_metadata(fig, ax)
3363
+
3364
+ # Display metadata
3365
+ print("\n3. Collected metadata:")
3366
+ print("-" * 60)
3367
+ import json
3368
+
3369
+ print(json.dumps(metadata, indent=2))
3370
+ print("-" * 60)
3371
+
3372
+ print("\n✅ Metadata collection complete!")
3373
+ print("\nKey fields collected:")
3374
+ print(f" • Software version: {metadata['scitex']['version']}")
3375
+ print(f" • Timestamp: {metadata['scitex']['created_at']}")
3376
+ if "dimensions" in metadata:
3377
+ print(f" • Axes size: {metadata['dimensions']['axes_size_mm']} mm")
3378
+ print(f" • DPI: {metadata['dimensions']['dpi']}")
3379
+ if "runtime" in metadata and "mode" in metadata["runtime"]:
3380
+ print(f" • Mode: {metadata['scitex']['mode']}")
3381
+ if "runtime" in metadata and "style_mm" in metadata["runtime"]:
3382
+ print(" • Style: Embedded ✓")
3383
+
3384
+ # EOF