scitex 2.8.1__py3-none-any.whl → 2.10.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 (415) hide show
  1. scitex/__init__.py +15 -7
  2. scitex/__version__.py +1 -2
  3. scitex/_install_guide.py +250 -0
  4. scitex/_optional_deps.py +206 -39
  5. scitex/ai/_gen_ai/_Groq.py +2 -4
  6. scitex/ai/_gen_ai/_OpenAI.py +5 -2
  7. scitex/ai/_gen_ai/_Perplexity.py +20 -6
  8. scitex/audio/__init__.py +24 -15
  9. scitex/audio/_cross_process_lock.py +139 -0
  10. scitex/audio/_mcp_handlers.py +256 -0
  11. scitex/audio/_mcp_tool_schemas.py +203 -0
  12. scitex/audio/engines/elevenlabs_engine.py +5 -2
  13. scitex/audio/mcp_server.py +98 -457
  14. scitex/bridge/__init__.py +30 -19
  15. scitex/bridge/_figrecipe.py +245 -0
  16. scitex/bridge/_helpers.py +2 -1
  17. scitex/bridge/_plt_vis.py +23 -10
  18. scitex/bridge/_stats_plt.py +18 -5
  19. scitex/bridge/_stats_vis.py +16 -2
  20. scitex/browser/__init__.py +84 -44
  21. scitex/browser/automation/__init__.py +5 -1
  22. scitex/browser/core/BrowserMixin.py +17 -4
  23. scitex/browser/core/__init__.py +11 -2
  24. scitex/browser/remote/CaptchaHandler.py +1 -1
  25. scitex/browser/remote/ZenRowsAPIClient.py +1 -1
  26. scitex/capture/grid.py +487 -0
  27. scitex/capture/mcp_handlers.py +401 -0
  28. scitex/capture/mcp_tool_defs.py +192 -0
  29. scitex/capture/mcp_tools.py +241 -0
  30. scitex/capture/mcp_utils.py +30 -0
  31. scitex/cli/convert.py +421 -0
  32. scitex/cli/main.py +6 -4
  33. scitex/datetime/__init__.py +46 -0
  34. scitex/datetime/_linspace.py +100 -0
  35. scitex/datetime/_normalize_timestamp.py +306 -0
  36. scitex/db/_delete_duplicates.py +4 -4
  37. scitex/db/_sqlite3/_delete_duplicates.py +11 -2
  38. scitex/dev/plt/__init__.py +61 -62
  39. scitex/dev/plt/demo_plotters/__init__.py +0 -0
  40. scitex/dev/plt/demo_plotters/plot_mpl_axhline.py +28 -0
  41. scitex/dev/plt/demo_plotters/plot_mpl_axhspan.py +28 -0
  42. scitex/dev/plt/demo_plotters/plot_mpl_axvline.py +28 -0
  43. scitex/dev/plt/demo_plotters/plot_mpl_axvspan.py +28 -0
  44. scitex/dev/plt/demo_plotters/plot_mpl_bar.py +29 -0
  45. scitex/dev/plt/demo_plotters/plot_mpl_barh.py +29 -0
  46. scitex/dev/plt/demo_plotters/plot_mpl_boxplot.py +28 -0
  47. scitex/dev/plt/demo_plotters/plot_mpl_contour.py +31 -0
  48. scitex/dev/plt/demo_plotters/plot_mpl_contourf.py +31 -0
  49. scitex/dev/plt/demo_plotters/plot_mpl_errorbar.py +30 -0
  50. scitex/dev/plt/demo_plotters/plot_mpl_eventplot.py +28 -0
  51. scitex/dev/plt/demo_plotters/plot_mpl_fill.py +30 -0
  52. scitex/dev/plt/demo_plotters/plot_mpl_fill_between.py +31 -0
  53. scitex/dev/plt/demo_plotters/plot_mpl_hexbin.py +28 -0
  54. scitex/dev/plt/demo_plotters/plot_mpl_hist.py +28 -0
  55. scitex/dev/plt/demo_plotters/plot_mpl_hist2d.py +28 -0
  56. scitex/dev/plt/demo_plotters/plot_mpl_imshow.py +29 -0
  57. scitex/dev/plt/demo_plotters/plot_mpl_pcolormesh.py +31 -0
  58. scitex/dev/plt/demo_plotters/plot_mpl_pie.py +29 -0
  59. scitex/dev/plt/demo_plotters/plot_mpl_plot.py +29 -0
  60. scitex/dev/plt/demo_plotters/plot_mpl_quiver.py +31 -0
  61. scitex/dev/plt/demo_plotters/plot_mpl_scatter.py +28 -0
  62. scitex/dev/plt/demo_plotters/plot_mpl_stackplot.py +31 -0
  63. scitex/dev/plt/demo_plotters/plot_mpl_stem.py +29 -0
  64. scitex/dev/plt/demo_plotters/plot_mpl_step.py +29 -0
  65. scitex/dev/plt/demo_plotters/plot_mpl_violinplot.py +28 -0
  66. scitex/dev/plt/demo_plotters/plot_sns_barplot.py +29 -0
  67. scitex/dev/plt/demo_plotters/plot_sns_boxplot.py +29 -0
  68. scitex/dev/plt/demo_plotters/plot_sns_heatmap.py +28 -0
  69. scitex/dev/plt/demo_plotters/plot_sns_histplot.py +29 -0
  70. scitex/dev/plt/demo_plotters/plot_sns_kdeplot.py +29 -0
  71. scitex/dev/plt/demo_plotters/plot_sns_lineplot.py +31 -0
  72. scitex/dev/plt/demo_plotters/plot_sns_scatterplot.py +29 -0
  73. scitex/dev/plt/demo_plotters/plot_sns_stripplot.py +29 -0
  74. scitex/dev/plt/demo_plotters/plot_sns_swarmplot.py +29 -0
  75. scitex/dev/plt/demo_plotters/plot_sns_violinplot.py +29 -0
  76. scitex/dev/plt/demo_plotters/plot_stx_bar.py +29 -0
  77. scitex/dev/plt/demo_plotters/plot_stx_barh.py +29 -0
  78. scitex/dev/plt/demo_plotters/plot_stx_box.py +28 -0
  79. scitex/dev/plt/demo_plotters/plot_stx_boxplot.py +28 -0
  80. scitex/dev/plt/demo_plotters/plot_stx_conf_mat.py +28 -0
  81. scitex/dev/plt/demo_plotters/plot_stx_contour.py +31 -0
  82. scitex/dev/plt/demo_plotters/plot_stx_ecdf.py +28 -0
  83. scitex/dev/plt/demo_plotters/plot_stx_errorbar.py +30 -0
  84. scitex/dev/plt/demo_plotters/plot_stx_fill_between.py +31 -0
  85. scitex/dev/plt/demo_plotters/plot_stx_fillv.py +28 -0
  86. scitex/dev/plt/demo_plotters/plot_stx_heatmap.py +28 -0
  87. scitex/dev/plt/demo_plotters/plot_stx_image.py +28 -0
  88. scitex/dev/plt/demo_plotters/plot_stx_imshow.py +28 -0
  89. scitex/dev/plt/demo_plotters/plot_stx_joyplot.py +28 -0
  90. scitex/dev/plt/demo_plotters/plot_stx_kde.py +28 -0
  91. scitex/dev/plt/demo_plotters/plot_stx_line.py +28 -0
  92. scitex/dev/plt/demo_plotters/plot_stx_mean_ci.py +28 -0
  93. scitex/dev/plt/demo_plotters/plot_stx_mean_std.py +28 -0
  94. scitex/dev/plt/demo_plotters/plot_stx_median_iqr.py +28 -0
  95. scitex/dev/plt/demo_plotters/plot_stx_raster.py +28 -0
  96. scitex/dev/plt/demo_plotters/plot_stx_rectangle.py +28 -0
  97. scitex/dev/plt/demo_plotters/plot_stx_scatter.py +29 -0
  98. scitex/dev/plt/demo_plotters/plot_stx_shaded_line.py +29 -0
  99. scitex/dev/plt/demo_plotters/plot_stx_violin.py +28 -0
  100. scitex/dev/plt/demo_plotters/plot_stx_violinplot.py +28 -0
  101. scitex/dev/plt/mpl/get_dir_ax.py +46 -0
  102. scitex/dev/plt/mpl/get_signatures.py +176 -0
  103. scitex/dev/plt/mpl/get_signatures_details.py +522 -0
  104. scitex/dict/_pop_keys.py +1 -7
  105. scitex/dsp/__init__.py +15 -10
  106. scitex/dsp/add_noise.py +5 -2
  107. scitex/dsp/example.py +35 -22
  108. scitex/dsp/filt.py +8 -3
  109. scitex/dsp/reference.py +3 -2
  110. scitex/dsp/utils/__init__.py +2 -1
  111. scitex/dsp/utils/_differential_bandpass_filters.py +14 -4
  112. scitex/dt/__init__.py +39 -2
  113. scitex/errors.py +82 -521
  114. scitex/fig/__init__.py +4 -4
  115. scitex/fig/editor/edit/panel_loader.py +1 -1
  116. scitex/fig/io/_bundle.py +7 -7
  117. scitex/fts/README.md +262 -0
  118. scitex/fts/TODO.md +66 -0
  119. scitex/fts/__init__.py +90 -0
  120. scitex/fts/_bundle/README_IN_BUNDLE.md +102 -0
  121. scitex/fts/_bundle/_FTS.py +657 -0
  122. scitex/fts/_bundle/__init__.py +38 -0
  123. scitex/fts/_bundle/_children.py +216 -0
  124. scitex/fts/_bundle/_conversion/__init__.py +15 -0
  125. scitex/fts/_bundle/_conversion/_bundle2dict.py +44 -0
  126. scitex/fts/_bundle/_conversion/_dict2bundle.py +50 -0
  127. scitex/fts/_bundle/_dataclasses/_Axes.py +57 -0
  128. scitex/fts/_bundle/_dataclasses/_BBox.py +54 -0
  129. scitex/fts/_bundle/_dataclasses/_ColumnDef.py +72 -0
  130. scitex/fts/_bundle/_dataclasses/_DataFormat.py +40 -0
  131. scitex/fts/_bundle/_dataclasses/_DataInfo.py +135 -0
  132. scitex/fts/_bundle/_dataclasses/_DataSource.py +44 -0
  133. scitex/fts/_bundle/_dataclasses/_Node.py +319 -0
  134. scitex/fts/_bundle/_dataclasses/_NodeRefs.py +45 -0
  135. scitex/fts/_bundle/_dataclasses/_SizeMM.py +38 -0
  136. scitex/fts/_bundle/_dataclasses/__init__.py +35 -0
  137. scitex/fts/_bundle/_extractors/__init__.py +32 -0
  138. scitex/fts/_bundle/_extractors/_extract_bar.py +131 -0
  139. scitex/fts/_bundle/_extractors/_extract_line.py +71 -0
  140. scitex/fts/_bundle/_extractors/_extract_scatter.py +79 -0
  141. scitex/fts/_bundle/_loader.py +134 -0
  142. scitex/fts/_bundle/_mpl_helpers.py +389 -0
  143. scitex/fts/_bundle/_saver.py +269 -0
  144. scitex/fts/_bundle/_storage.py +200 -0
  145. scitex/fts/_bundle/_utils/__init__.py +55 -0
  146. scitex/fts/_bundle/_utils/_const.py +26 -0
  147. scitex/fts/_bundle/_utils/_errors.py +73 -0
  148. scitex/fts/_bundle/_utils/_generate.py +21 -0
  149. scitex/fts/_bundle/_utils/_types.py +76 -0
  150. scitex/fts/_bundle/_validation.py +434 -0
  151. scitex/fts/_bundle/_zipbundle.py +165 -0
  152. scitex/fts/_fig/__init__.py +22 -0
  153. scitex/fts/_fig/_backend/__init__.py +53 -0
  154. scitex/fts/_fig/_backend/_export.py +165 -0
  155. scitex/fts/_fig/_backend/_parser.py +188 -0
  156. scitex/fts/_fig/_backend/_render.py +538 -0
  157. scitex/fts/_fig/_composite.py +345 -0
  158. scitex/fts/_fig/_dataclasses/_ChannelEncoding.py +46 -0
  159. scitex/fts/_fig/_dataclasses/_Encoding.py +82 -0
  160. scitex/fts/_fig/_dataclasses/_Theme.py +441 -0
  161. scitex/fts/_fig/_dataclasses/_TraceEncoding.py +52 -0
  162. scitex/fts/_fig/_dataclasses/__init__.py +47 -0
  163. scitex/fts/_fig/_editor/__init__.py +14 -0
  164. scitex/fts/_fig/_editor/_cui/__init__.py +33 -0
  165. scitex/fts/_fig/_editor/_cui/_backend_detector.py +39 -0
  166. scitex/fts/_fig/_editor/_cui/_bundle_resolver.py +366 -0
  167. scitex/fts/_fig/_editor/_cui/_editor_launcher.py +175 -0
  168. scitex/fts/_fig/_editor/_cui/_manual_handler.py +52 -0
  169. scitex/fts/_fig/_editor/_cui/_panel_loader.py +246 -0
  170. scitex/fts/_fig/_editor/_cui/_path_resolver.py +66 -0
  171. scitex/fts/_fig/_editor/_defaults.py +300 -0
  172. scitex/fts/_fig/_editor/_gui/__init__.py +11 -0
  173. scitex/fts/_fig/_editor/_gui/_flask_editor/__init__.py +20 -0
  174. scitex/fts/_fig/_editor/_gui/_flask_editor/_bbox.py +1339 -0
  175. scitex/fts/_fig/_editor/_gui/_flask_editor/_core.py +1688 -0
  176. scitex/fts/_fig/_editor/_gui/_flask_editor/_plotter.py +664 -0
  177. scitex/fts/_fig/_editor/_gui/_flask_editor/_renderer.py +853 -0
  178. scitex/fts/_fig/_editor/_gui/_flask_editor/_utils.py +79 -0
  179. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/reset.css +41 -0
  180. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/typography.css +16 -0
  181. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/variables.css +85 -0
  182. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/buttons.css +217 -0
  183. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/context-menu.css +93 -0
  184. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/dropdown.css +57 -0
  185. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/forms.css +112 -0
  186. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/modal.css +59 -0
  187. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/sections.css +212 -0
  188. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/canvas.css +176 -0
  189. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/element-inspector.css +190 -0
  190. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/loading.css +59 -0
  191. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/overlay.css +45 -0
  192. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/panel-grid.css +95 -0
  193. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/selection.css +101 -0
  194. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/statistics.css +138 -0
  195. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/index.css +31 -0
  196. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/container.css +7 -0
  197. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/controls.css +56 -0
  198. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/preview.css +78 -0
  199. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/axis.js +314 -0
  200. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/basic.js +107 -0
  201. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/distribute.js +54 -0
  202. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/canvas.js +172 -0
  203. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/dragging.js +258 -0
  204. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/resize.js +48 -0
  205. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/selection.js +71 -0
  206. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/api.js +288 -0
  207. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/state.js +143 -0
  208. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/utils.js +245 -0
  209. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/dev/element-inspector.js +992 -0
  210. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/bbox.js +339 -0
  211. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/element-drag.js +286 -0
  212. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/overlay.js +371 -0
  213. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/preview.js +293 -0
  214. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/main.js +426 -0
  215. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/shortcuts/context-menu.js +152 -0
  216. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/shortcuts/keyboard.js +265 -0
  217. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/controls.js +184 -0
  218. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/download.js +57 -0
  219. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/help.js +100 -0
  220. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/theme.js +34 -0
  221. scitex/fts/_fig/_editor/_gui/_flask_editor/templates/__init__.py +124 -0
  222. scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_html.py +851 -0
  223. scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_scripts.py +4932 -0
  224. scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_styles.py +1657 -0
  225. scitex/fts/_fig/_editor/_gui/_flask_editor.py +36 -0
  226. scitex/fts/_fig/_models/_Annotations.py +115 -0
  227. scitex/fts/_fig/_models/_Axes.py +152 -0
  228. scitex/fts/_fig/_models/_Figure.py +138 -0
  229. scitex/fts/_fig/_models/_Guides.py +104 -0
  230. scitex/fts/_fig/_models/_Plot.py +123 -0
  231. scitex/fts/_fig/_models/_Styles.py +245 -0
  232. scitex/fts/_fig/_models/__init__.py +80 -0
  233. scitex/fts/_fig/_models/_plot_types/__init__.py +156 -0
  234. scitex/fts/_fig/_models/_plot_types/_bar.py +43 -0
  235. scitex/fts/_fig/_models/_plot_types/_box.py +38 -0
  236. scitex/fts/_fig/_models/_plot_types/_distribution.py +36 -0
  237. scitex/fts/_fig/_models/_plot_types/_errorbar.py +60 -0
  238. scitex/fts/_fig/_models/_plot_types/_histogram.py +30 -0
  239. scitex/fts/_fig/_models/_plot_types/_image.py +61 -0
  240. scitex/fts/_fig/_models/_plot_types/_line.py +57 -0
  241. scitex/fts/_fig/_models/_plot_types/_scatter.py +30 -0
  242. scitex/fts/_fig/_models/_plot_types/_seaborn.py +121 -0
  243. scitex/fts/_fig/_models/_plot_types/_violin.py +36 -0
  244. scitex/fts/_fig/_utils/__init__.py +129 -0
  245. scitex/fts/_fig/_utils/_auto_layout.py +127 -0
  246. scitex/fts/_fig/_utils/_calc_bounds.py +111 -0
  247. scitex/fts/_fig/_utils/_const_sizes.py +48 -0
  248. scitex/fts/_fig/_utils/_convert_coords.py +77 -0
  249. scitex/fts/_fig/_utils/_get_template.py +178 -0
  250. scitex/fts/_fig/_utils/_normalize.py +73 -0
  251. scitex/fts/_fig/_utils/_plot_layout.py +397 -0
  252. scitex/fts/_fig/_utils/_validate.py +197 -0
  253. scitex/fts/_kinds/__init__.py +45 -0
  254. scitex/fts/_kinds/_figure/__init__.py +19 -0
  255. scitex/fts/_kinds/_figure/_composite.py +345 -0
  256. scitex/fts/_kinds/_plot/__init__.py +25 -0
  257. scitex/fts/_kinds/_plot/_backend/__init__.py +53 -0
  258. scitex/fts/_kinds/_plot/_backend/_export.py +165 -0
  259. scitex/fts/_kinds/_plot/_backend/_parser.py +188 -0
  260. scitex/fts/_kinds/_plot/_backend/_render.py +538 -0
  261. scitex/fts/_kinds/_plot/_dataclasses/_ChannelEncoding.py +46 -0
  262. scitex/fts/_kinds/_plot/_dataclasses/_Encoding.py +82 -0
  263. scitex/fts/_kinds/_plot/_dataclasses/_Theme.py +441 -0
  264. scitex/fts/_kinds/_plot/_dataclasses/_TraceEncoding.py +52 -0
  265. scitex/fts/_kinds/_plot/_dataclasses/__init__.py +47 -0
  266. scitex/fts/_kinds/_plot/_models/_Annotations.py +115 -0
  267. scitex/fts/_kinds/_plot/_models/_Axes.py +152 -0
  268. scitex/fts/_kinds/_plot/_models/_Figure.py +138 -0
  269. scitex/fts/_kinds/_plot/_models/_Guides.py +104 -0
  270. scitex/fts/_kinds/_plot/_models/_Plot.py +123 -0
  271. scitex/fts/_kinds/_plot/_models/_Styles.py +245 -0
  272. scitex/fts/_kinds/_plot/_models/__init__.py +80 -0
  273. scitex/fts/_kinds/_plot/_models/_plot_types/__init__.py +156 -0
  274. scitex/fts/_kinds/_plot/_models/_plot_types/_bar.py +43 -0
  275. scitex/fts/_kinds/_plot/_models/_plot_types/_box.py +38 -0
  276. scitex/fts/_kinds/_plot/_models/_plot_types/_distribution.py +36 -0
  277. scitex/fts/_kinds/_plot/_models/_plot_types/_errorbar.py +60 -0
  278. scitex/fts/_kinds/_plot/_models/_plot_types/_histogram.py +30 -0
  279. scitex/fts/_kinds/_plot/_models/_plot_types/_image.py +61 -0
  280. scitex/fts/_kinds/_plot/_models/_plot_types/_line.py +57 -0
  281. scitex/fts/_kinds/_plot/_models/_plot_types/_scatter.py +30 -0
  282. scitex/fts/_kinds/_plot/_models/_plot_types/_seaborn.py +121 -0
  283. scitex/fts/_kinds/_plot/_models/_plot_types/_violin.py +36 -0
  284. scitex/fts/_kinds/_plot/_utils/__init__.py +129 -0
  285. scitex/fts/_kinds/_plot/_utils/_auto_layout.py +127 -0
  286. scitex/fts/_kinds/_plot/_utils/_calc_bounds.py +111 -0
  287. scitex/fts/_kinds/_plot/_utils/_const_sizes.py +48 -0
  288. scitex/fts/_kinds/_plot/_utils/_convert_coords.py +77 -0
  289. scitex/fts/_kinds/_plot/_utils/_get_template.py +178 -0
  290. scitex/fts/_kinds/_plot/_utils/_normalize.py +73 -0
  291. scitex/fts/_kinds/_plot/_utils/_plot_layout.py +397 -0
  292. scitex/fts/_kinds/_plot/_utils/_validate.py +197 -0
  293. scitex/fts/_kinds/_shape/__init__.py +141 -0
  294. scitex/fts/_kinds/_stats/__init__.py +56 -0
  295. scitex/fts/_kinds/_stats/_dataclasses/_Stats.py +423 -0
  296. scitex/fts/_kinds/_stats/_dataclasses/__init__.py +48 -0
  297. scitex/fts/_kinds/_table/__init__.py +72 -0
  298. scitex/fts/_kinds/_table/_latex/__init__.py +93 -0
  299. scitex/fts/_kinds/_table/_latex/_editor/__init__.py +11 -0
  300. scitex/fts/_kinds/_table/_latex/_editor/_app.py +725 -0
  301. scitex/fts/_kinds/_table/_latex/_export.py +279 -0
  302. scitex/fts/_kinds/_table/_latex/_figure_exporter.py +153 -0
  303. scitex/fts/_kinds/_table/_latex/_stats_formatter.py +274 -0
  304. scitex/fts/_kinds/_table/_latex/_table_exporter.py +362 -0
  305. scitex/fts/_kinds/_table/_latex/_utils.py +369 -0
  306. scitex/fts/_kinds/_table/_latex/_validator.py +445 -0
  307. scitex/fts/_kinds/_text/__init__.py +77 -0
  308. scitex/fts/_schemas/data_info.schema.json +75 -0
  309. scitex/fts/_schemas/encoding.schema.json +90 -0
  310. scitex/fts/_schemas/node.schema.json +145 -0
  311. scitex/fts/_schemas/render_manifest.schema.json +62 -0
  312. scitex/fts/_schemas/stats.schema.json +132 -0
  313. scitex/fts/_schemas/theme.schema.json +141 -0
  314. scitex/fts/_stats/__init__.py +48 -0
  315. scitex/fts/_stats/_dataclasses/_Stats.py +423 -0
  316. scitex/fts/_stats/_dataclasses/__init__.py +48 -0
  317. scitex/fts/_tables/__init__.py +65 -0
  318. scitex/fts/_tables/_latex/__init__.py +93 -0
  319. scitex/fts/_tables/_latex/_editor/__init__.py +11 -0
  320. scitex/fts/_tables/_latex/_editor/_app.py +725 -0
  321. scitex/fts/_tables/_latex/_export.py +279 -0
  322. scitex/fts/_tables/_latex/_figure_exporter.py +153 -0
  323. scitex/fts/_tables/_latex/_stats_formatter.py +274 -0
  324. scitex/fts/_tables/_latex/_table_exporter.py +362 -0
  325. scitex/fts/_tables/_latex/_utils.py +369 -0
  326. scitex/fts/_tables/_latex/_validator.py +445 -0
  327. scitex/gen/__init__.py +66 -25
  328. scitex/gen/misc.py +28 -0
  329. scitex/io/__init__.py +47 -32
  330. scitex/io/_load.py +87 -36
  331. scitex/io/_load_modules/__init__.py +10 -7
  332. scitex/io/_load_modules/_pandas.py +6 -1
  333. scitex/io/_save.py +299 -1556
  334. scitex/io/_save_modules/__init__.py +76 -19
  335. scitex/io/_save_modules/_figure_utils.py +90 -0
  336. scitex/io/_save_modules/_image_csv.py +497 -0
  337. scitex/io/_save_modules/_legends.py +91 -0
  338. scitex/io/_save_modules/_pltz_bundle.py +356 -0
  339. scitex/io/_save_modules/_pltz_stx.py +536 -0
  340. scitex/io/_save_modules/_stx_bundle.py +104 -0
  341. scitex/io/_save_modules/_symlink.py +96 -0
  342. scitex/io/_save_modules/_yaml.py +1 -1
  343. scitex/io/_save_modules/_zarr.py +64 -18
  344. scitex/io/bundle/README.md +212 -0
  345. scitex/io/bundle/__init__.py +110 -0
  346. scitex/io/{_bundle.py → bundle/_core.py} +168 -97
  347. scitex/io/bundle/_nested.py +713 -0
  348. scitex/io/bundle/_types.py +74 -0
  349. scitex/io/{_zip_bundle.py → bundle/_zip.py} +93 -45
  350. scitex/io/utils/h5_to_zarr.py +1 -1
  351. scitex/logging/__init__.py +108 -13
  352. scitex/logging/_errors.py +508 -0
  353. scitex/logging/_formatters.py +30 -6
  354. scitex/logging/_warnings.py +261 -0
  355. scitex/plt/__init__.py +4 -1
  356. scitex/plt/_figrecipe.py +236 -0
  357. scitex/plt/_subplots/_AxisWrapper.py +6 -0
  358. scitex/plt/_subplots/_AxisWrapperMixins/_UnitAwareMixin.py +112 -1
  359. scitex/plt/_subplots/_FigWrapper.py +15 -0
  360. scitex/plt/_subplots/_SubplotsWrapper.py +125 -489
  361. scitex/plt/_subplots/_export_as_csv.py +11 -0
  362. scitex/plt/_subplots/_export_as_csv_formatters/__init__.py +2 -0
  363. scitex/plt/_subplots/_export_as_csv_formatters/_format_pcolormesh.py +66 -0
  364. scitex/plt/_subplots/_export_as_csv_formatters/_format_stackplot.py +62 -0
  365. scitex/plt/_subplots/_export_as_csv_formatters/test_formatters.py +208 -0
  366. scitex/plt/_subplots/_fonts.py +71 -0
  367. scitex/plt/_subplots/_mm_layout.py +282 -0
  368. scitex/plt/gallery/__init__.py +99 -2
  369. scitex/plt/styles/_plot_postprocess.py +3 -1
  370. scitex/plt/utils/_configure_mpl.py +16 -19
  371. scitex/repro/_RandomStateManager.py +13 -8
  372. scitex/resource/__init__.py +19 -1
  373. scitex/resource/_utils/_get_env_info.py +13 -25
  374. scitex/schema/__init__.py +149 -160
  375. scitex/schema/_encoding.py +273 -0
  376. scitex/schema/_figure_elements.py +406 -0
  377. scitex/schema/_theme.py +360 -0
  378. scitex/schema/_validation.py +0 -98
  379. scitex/scholar/__init__.py +56 -14
  380. scitex/scholar/auth/ScholarAuthManager.py +1 -1
  381. scitex/scholar/auth/__init__.py +11 -2
  382. scitex/scholar/auth/providers/BaseAuthenticator.py +1 -1
  383. scitex/scholar/auth/providers/EZProxyAuthenticator.py +1 -1
  384. scitex/scholar/auth/providers/OpenAthensAuthenticator.py +1 -1
  385. scitex/scholar/auth/providers/ShibbolethAuthenticator.py +1 -1
  386. scitex/scholar/config/ScholarConfig.py +1 -1
  387. scitex/scholar/core/Scholar.py +1 -1
  388. scitex/session/_decorator.py +18 -16
  389. scitex/session/_lifecycle.py +9 -11
  390. scitex/session/template.py +9 -8
  391. scitex/sh/test_sh.py +72 -0
  392. scitex/sh/test_sh_simple.py +61 -0
  393. scitex/stats/__init__.py +221 -97
  394. scitex/stats/_schema.py +21 -22
  395. scitex/stats/descriptive/_circular.py +212 -351
  396. scitex/stats/descriptive/_describe.py +81 -132
  397. scitex/stats/descriptive/_nan.py +205 -433
  398. scitex/stats/descriptive/_real.py +127 -141
  399. scitex/str/_format_plot_text.py +5 -5
  400. scitex/str/_latex.py +26 -84
  401. scitex/str/_latex_fallback.py +53 -47
  402. scitex/web/_search_pubmed.py +5 -4
  403. scitex/writer/tests/test_diff_between.py +451 -0
  404. scitex/writer/tests/test_document_section.py +311 -0
  405. scitex/writer/tests/test_document_workflow.py +393 -0
  406. scitex/writer/tests/test_writer.py +361 -0
  407. scitex/writer/tests/test_writer_integration.py +303 -0
  408. {scitex-2.8.1.dist-info → scitex-2.10.0.dist-info}/METADATA +364 -181
  409. {scitex-2.8.1.dist-info → scitex-2.10.0.dist-info}/RECORD +412 -97
  410. scitex/scholar/docs/to_claude/guidelines/examples/mgmt/ARCHITECTURE_EXAMPLE.md +0 -905
  411. scitex/scholar/docs/to_claude/guidelines/examples/mgmt/BULLETIN_BOARD_EXAMPLE.md +0 -99
  412. scitex/scholar/docs/to_claude/guidelines/examples/mgmt/PROJECT_DESCRIPTION_EXAMPLE.md +0 -96
  413. {scitex-2.8.1.dist-info → scitex-2.10.0.dist-info}/WHEEL +0 -0
  414. {scitex-2.8.1.dist-info → scitex-2.10.0.dist-info}/entry_points.txt +0 -0
  415. {scitex-2.8.1.dist-info → scitex-2.10.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,261 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # Timestamp: "2025-12-21"
4
+ # File: /home/ywatanabe/proj/scitex-code/src/scitex/logging/_warnings.py
5
+
6
+ """Warning system for SciTeX, mimicking Python's warnings module.
7
+
8
+ Usage:
9
+ import scitex.logging as logging
10
+ from scitex.logging import UnitWarning
11
+
12
+ # Emit a warning
13
+ logging.warn("Missing units on axis label", UnitWarning)
14
+
15
+ # Filter warnings (like warnings.filterwarnings)
16
+ logging.filterwarnings("ignore", category=UnitWarning)
17
+ logging.filterwarnings("error", category=UnitWarning) # Raise as exception
18
+ logging.filterwarnings("always", category=UnitWarning) # Always show
19
+ """
20
+
21
+ import logging as _logging
22
+ from typing import Dict, Optional, Type, Set
23
+
24
+ # =============================================================================
25
+ # Warning Categories (similar to Python's warning classes)
26
+ # =============================================================================
27
+
28
+
29
+ class SciTeXWarning(UserWarning):
30
+ """Base warning class for all SciTeX warnings."""
31
+
32
+ pass
33
+
34
+
35
+ class UnitWarning(SciTeXWarning):
36
+ """Warning for axis label unit issues (educational for SI conventions).
37
+
38
+ Raised when:
39
+ - Axis labels are missing units
40
+ - Units use parentheses instead of brackets (SI prefers [])
41
+ - Units use division instead of negative exponents (m/s vs m·s⁻¹)
42
+ """
43
+
44
+ pass
45
+
46
+
47
+ class StyleWarning(SciTeXWarning):
48
+ """Warning for style/formatting issues."""
49
+
50
+ pass
51
+
52
+
53
+ class SciTeXDeprecationWarning(SciTeXWarning):
54
+ """Warning for deprecated SciTeX features."""
55
+
56
+ pass
57
+
58
+
59
+ class PerformanceWarning(SciTeXWarning):
60
+ """Warning for performance issues."""
61
+
62
+ pass
63
+
64
+
65
+ class DataLossWarning(SciTeXWarning):
66
+ """Warning for potential data loss."""
67
+
68
+ pass
69
+
70
+
71
+ # =============================================================================
72
+ # Warning Filter Registry
73
+ # =============================================================================
74
+
75
+ # Actions: "ignore", "error", "always", "default", "once", "module"
76
+ _filters: Dict[Type[SciTeXWarning], str] = {}
77
+ _seen_warnings: Set[str] = set() # For "once" action
78
+
79
+
80
+ def filterwarnings(
81
+ action: str,
82
+ category: Type[SciTeXWarning] = SciTeXWarning,
83
+ message: Optional[str] = None,
84
+ ) -> None:
85
+ """Control warning behavior (like warnings.filterwarnings).
86
+
87
+ Parameters
88
+ ----------
89
+ action : str
90
+ One of:
91
+ - "ignore": Never show this warning
92
+ - "error": Raise as exception
93
+ - "always": Always show
94
+ - "default": Show first occurrence per location
95
+ - "once": Show only once total
96
+ - "module": Show once per module
97
+ category : type
98
+ Warning category (default: SciTeXWarning = all)
99
+ message : str, optional
100
+ Regex pattern to match warning message (not implemented yet)
101
+
102
+ Examples
103
+ --------
104
+ >>> import scitex.logging as logging
105
+ >>> from scitex.logging import UnitWarning
106
+ >>> logging.filterwarnings("ignore", category=UnitWarning)
107
+ """
108
+ valid_actions = {"ignore", "error", "always", "default", "once", "module"}
109
+ if action not in valid_actions:
110
+ raise ValueError(f"Invalid action '{action}'. Must be one of: {valid_actions}")
111
+
112
+ _filters[category] = action
113
+
114
+
115
+ def resetwarnings() -> None:
116
+ """Reset all warning filters to default behavior."""
117
+ global _filters, _seen_warnings
118
+ _filters = {}
119
+ _seen_warnings = set()
120
+
121
+
122
+ def _get_action(category: Type[SciTeXWarning]) -> str:
123
+ """Get the action for a warning category, checking inheritance."""
124
+ # Check exact match first
125
+ if category in _filters:
126
+ return _filters[category]
127
+
128
+ # Check parent classes
129
+ for filter_cat, action in _filters.items():
130
+ if issubclass(category, filter_cat):
131
+ return action
132
+
133
+ # Default action
134
+ return "default"
135
+
136
+
137
+ # =============================================================================
138
+ # Warning Emission
139
+ # =============================================================================
140
+
141
+
142
+ def warn(
143
+ message: str,
144
+ category: Type[SciTeXWarning] = SciTeXWarning,
145
+ stacklevel: int = 2,
146
+ ) -> None:
147
+ """Emit a warning (like warnings.warn but integrated with scitex.logging).
148
+
149
+ Parameters
150
+ ----------
151
+ message : str
152
+ Warning message
153
+ category : type
154
+ Warning category (default: SciTeXWarning)
155
+ stacklevel : int
156
+ Stack level for source location (default: 2 = caller)
157
+
158
+ Examples
159
+ --------
160
+ >>> import scitex.logging as logging
161
+ >>> from scitex.logging import UnitWarning
162
+ >>> logging.warn("X axis has no units", UnitWarning)
163
+ """
164
+ import inspect
165
+
166
+ action = _get_action(category)
167
+
168
+ # Handle action
169
+ if action == "ignore":
170
+ return
171
+
172
+ if action == "error":
173
+ raise category(message)
174
+
175
+ # Get source location for "once", "module", "default" actions
176
+ frame = inspect.currentframe()
177
+ for _ in range(stacklevel):
178
+ if frame is not None:
179
+ frame = frame.f_back
180
+
181
+ location = ""
182
+ if frame is not None:
183
+ filename = frame.f_code.co_filename
184
+ lineno = frame.f_lineno
185
+ location = f"{filename}:{lineno}"
186
+
187
+ # Check if already seen
188
+ warn_key = f"{category.__name__}:{message}:{location}"
189
+
190
+ if action == "once":
191
+ if warn_key in _seen_warnings:
192
+ return
193
+ _seen_warnings.add(warn_key)
194
+ elif action == "default":
195
+ # Show first per location
196
+ loc_key = f"{category.__name__}:{location}"
197
+ if loc_key in _seen_warnings:
198
+ return
199
+ _seen_warnings.add(loc_key)
200
+ elif action == "module":
201
+ # Show once per module
202
+ if frame is not None:
203
+ module_key = f"{category.__name__}:{frame.f_code.co_filename}"
204
+ if module_key in _seen_warnings:
205
+ return
206
+ _seen_warnings.add(module_key)
207
+
208
+ # Emit via scitex.logging
209
+ logger = _logging.getLogger("scitex.warnings")
210
+ category_name = category.__name__
211
+
212
+ # Format: "UnitWarning: message"
213
+ logger.warning(f"{category_name}: {message}")
214
+
215
+
216
+ # =============================================================================
217
+ # Convenience Warning Functions
218
+ # =============================================================================
219
+
220
+
221
+ def warn_deprecated(
222
+ old_name: str, new_name: str, version: Optional[str] = None
223
+ ) -> None:
224
+ """Issue a deprecation warning."""
225
+ message = f"{old_name} is deprecated. Use {new_name} instead."
226
+ if version:
227
+ message += f" Will be removed in version {version}."
228
+ warn(message, SciTeXDeprecationWarning, stacklevel=3)
229
+
230
+
231
+ def warn_performance(operation: str, suggestion: str) -> None:
232
+ """Issue a performance warning."""
233
+ message = f"Performance warning in {operation}: {suggestion}"
234
+ warn(message, PerformanceWarning, stacklevel=3)
235
+
236
+
237
+ def warn_data_loss(operation: str, detail: str) -> None:
238
+ """Issue a data loss warning."""
239
+ message = f"Potential data loss in {operation}: {detail}"
240
+ warn(message, DataLossWarning, stacklevel=3)
241
+
242
+
243
+ __all__ = [
244
+ # Warning categories
245
+ "SciTeXWarning",
246
+ "UnitWarning",
247
+ "StyleWarning",
248
+ "SciTeXDeprecationWarning",
249
+ "PerformanceWarning",
250
+ "DataLossWarning",
251
+ # Functions
252
+ "warn",
253
+ "filterwarnings",
254
+ "resetwarnings",
255
+ # Convenience functions
256
+ "warn_deprecated",
257
+ "warn_performance",
258
+ "warn_data_loss",
259
+ ]
260
+
261
+ # EOF
scitex/plt/__init__.py CHANGED
@@ -179,7 +179,10 @@ try:
179
179
  except Exception:
180
180
  pass # Use matplotlib default colors if color module fails
181
181
 
182
- from ._tpl import termplot
182
+ try:
183
+ from ._tpl import termplot
184
+ except ImportError:
185
+ termplot = None
183
186
  from . import color
184
187
  from . import utils
185
188
  from . import ax
@@ -0,0 +1,236 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: "2026-01-01 (ywatanabe)"
3
+ # File: /home/ywatanabe/proj/scitex-code/src/scitex/plt/_figrecipe.py
4
+ # ----------------------------------------
5
+ """
6
+ Figrecipe integration for scitex.
7
+
8
+ This module provides integration with figrecipe for reproducible matplotlib figures.
9
+ Uses csv_format="single" by default for backward compatibility with scitex's
10
+ SigmaPlot-compatible CSV format.
11
+
12
+ Usage
13
+ -----
14
+ >>> import scitex.plt as splt
15
+ >>> fig, ax = splt.subplots()
16
+ >>> ax.plot([1, 2, 3], [4, 5, 6], id='data')
17
+ >>> splt.save_recipe(fig, 'figure.yaml') # Saves recipe with single CSV
18
+ """
19
+
20
+ from pathlib import Path
21
+ from typing import Any, Dict, Literal, Optional, Tuple, Union
22
+
23
+ from scitex import logging
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+ # Check if figrecipe is available
28
+ try:
29
+ import figrecipe as fr
30
+
31
+ FIGRECIPE_AVAILABLE = True
32
+ except ImportError:
33
+ FIGRECIPE_AVAILABLE = False
34
+ fr = None
35
+
36
+
37
+ def check_figrecipe_available() -> bool:
38
+ """Check if figrecipe is installed."""
39
+ return FIGRECIPE_AVAILABLE
40
+
41
+
42
+ def subplots(
43
+ nrows: int = 1,
44
+ ncols: int = 1,
45
+ **kwargs,
46
+ ) -> Tuple[Any, Any]:
47
+ """Create recording-enabled subplots using figrecipe.
48
+
49
+ This is a wrapper around figrecipe.subplots() that creates
50
+ figures with recording capabilities for reproducibility.
51
+
52
+ Parameters
53
+ ----------
54
+ nrows, ncols : int
55
+ Number of rows and columns.
56
+ **kwargs
57
+ Additional arguments passed to figrecipe.subplots().
58
+
59
+ Returns
60
+ -------
61
+ fig : RecordingFigure
62
+ Figrecipe's wrapped figure.
63
+ axes : RecordingAxes or ndarray
64
+ Wrapped axes.
65
+
66
+ Raises
67
+ ------
68
+ ImportError
69
+ If figrecipe is not installed.
70
+ """
71
+ if not FIGRECIPE_AVAILABLE:
72
+ raise ImportError(
73
+ "figrecipe is not installed. Install with: pip install figrecipe"
74
+ )
75
+
76
+ return fr.subplots(nrows, ncols, **kwargs)
77
+
78
+
79
+ def save_recipe(
80
+ fig,
81
+ path: Union[str, Path],
82
+ csv_format: Literal["single", "separate"] = "single",
83
+ data_format: Literal["csv", "npz", "inline"] = "csv",
84
+ validate: bool = True,
85
+ verbose: bool = True,
86
+ **kwargs,
87
+ ) -> Optional[Tuple[Path, Path]]:
88
+ """Save figure recipe using figrecipe with scitex-compatible CSV format.
89
+
90
+ This function saves a matplotlib figure as a reproducible recipe using
91
+ figrecipe. By default, uses csv_format="single" for backward compatibility
92
+ with scitex's SigmaPlot-compatible CSV format.
93
+
94
+ Parameters
95
+ ----------
96
+ fig : matplotlib.figure.Figure or RecordingFigure
97
+ The figure to save.
98
+ path : str or Path
99
+ Output path (.yaml for recipe, .png/.pdf for image+recipe).
100
+ csv_format : str
101
+ CSV format: 'single' (scitex-compatible, default) or 'separate'.
102
+ - 'single': All columns in one CSV with scitex naming convention
103
+ - 'separate': One CSV per variable (figrecipe default)
104
+ data_format : str
105
+ Data format: 'csv' (default), 'npz', or 'inline'.
106
+ validate : bool
107
+ If True (default), validate reproducibility after saving.
108
+ verbose : bool
109
+ If True (default), print save status.
110
+ **kwargs
111
+ Additional arguments passed to figrecipe.save().
112
+
113
+ Returns
114
+ -------
115
+ tuple or None
116
+ (image_path, yaml_path) if successful, None if figrecipe unavailable.
117
+
118
+ Examples
119
+ --------
120
+ >>> import scitex.plt as splt
121
+ >>> fig, ax = splt.subplots()
122
+ >>> ax.plot([1, 2, 3], [4, 5, 6])
123
+ >>> splt.save_recipe(fig, 'output.yaml') # Single CSV format (scitex-compatible)
124
+ >>> splt.save_recipe(fig, 'output.yaml', csv_format='separate') # Separate CSVs
125
+ """
126
+ if not FIGRECIPE_AVAILABLE:
127
+ logger.warning(
128
+ "figrecipe is not installed. Recipe not saved. "
129
+ "Install with: pip install figrecipe"
130
+ )
131
+ return None
132
+
133
+ path = Path(path)
134
+
135
+ # Handle different figure types
136
+ # If it's a scitex FigWrapper, extract the matplotlib figure
137
+ if hasattr(fig, "_fig_mpl"):
138
+ mpl_fig = fig._fig_mpl
139
+ elif hasattr(fig, "figure"):
140
+ mpl_fig = fig.figure
141
+ else:
142
+ mpl_fig = fig
143
+
144
+ # Check if fig is already a figrecipe RecordingFigure
145
+ if hasattr(fig, "_recorder"):
146
+ # Already a RecordingFigure, use figrecipe's save directly
147
+ return fr.save(
148
+ fig,
149
+ path,
150
+ data_format=data_format,
151
+ csv_format=csv_format,
152
+ validate=validate,
153
+ verbose=verbose,
154
+ **kwargs,
155
+ )
156
+
157
+ # For regular matplotlib figures, we need to wrap them first
158
+ # This requires re-creating the figure with figrecipe
159
+ logger.warning(
160
+ "Figure is not a RecordingFigure. For full recipe support, "
161
+ "create figures with fr.subplots() or splt.subplots_recipe()."
162
+ )
163
+ return None
164
+
165
+
166
+ def load_recipe(path: Union[str, Path]) -> Tuple[Any, Any]:
167
+ """Load and reproduce a figure from a recipe file.
168
+
169
+ Parameters
170
+ ----------
171
+ path : str or Path
172
+ Path to .yaml recipe file.
173
+
174
+ Returns
175
+ -------
176
+ fig : matplotlib.figure.Figure
177
+ Reproduced figure.
178
+ axes : Axes or list of Axes
179
+ Reproduced axes.
180
+
181
+ Raises
182
+ ------
183
+ ImportError
184
+ If figrecipe is not installed.
185
+ """
186
+ if not FIGRECIPE_AVAILABLE:
187
+ raise ImportError(
188
+ "figrecipe is not installed. Install with: pip install figrecipe"
189
+ )
190
+
191
+ return fr.reproduce(path)
192
+
193
+
194
+ def recipe_info(path: Union[str, Path]) -> Dict[str, Any]:
195
+ """Get information about a recipe without reproducing.
196
+
197
+ Parameters
198
+ ----------
199
+ path : str or Path
200
+ Path to .yaml recipe file.
201
+
202
+ Returns
203
+ -------
204
+ dict
205
+ Recipe information including figure settings, calls, etc.
206
+
207
+ Raises
208
+ ------
209
+ ImportError
210
+ If figrecipe is not installed.
211
+ """
212
+ if not FIGRECIPE_AVAILABLE:
213
+ raise ImportError(
214
+ "figrecipe is not installed. Install with: pip install figrecipe"
215
+ )
216
+
217
+ return fr.info(path)
218
+
219
+
220
+ # Convenience aliases
221
+ reproduce = load_recipe
222
+ info = recipe_info
223
+
224
+
225
+ __all__ = [
226
+ "FIGRECIPE_AVAILABLE",
227
+ "check_figrecipe_available",
228
+ "subplots",
229
+ "save_recipe",
230
+ "load_recipe",
231
+ "reproduce",
232
+ "recipe_info",
233
+ "info",
234
+ ]
235
+
236
+ # EOF
@@ -189,10 +189,14 @@ class AxisWrapper(
189
189
  "errorbar",
190
190
  "step",
191
191
  "stem",
192
+ # Fill and area plots
193
+ "fill",
194
+ "stackplot",
192
195
  # Statistical plots
193
196
  "hist2d",
194
197
  "hexbin",
195
198
  "pie",
199
+ "eventplot",
196
200
  # Contour plots
197
201
  "contour",
198
202
  "contourf",
@@ -202,6 +206,8 @@ class AxisWrapper(
202
206
  "imshow",
203
207
  "matshow",
204
208
  "spy",
209
+ "pcolormesh",
210
+ "pcolor",
205
211
  # Quiver plots
206
212
  "quiver",
207
213
  "streamplot",
@@ -19,9 +19,111 @@ Features:
19
19
  """
20
20
 
21
21
  from typing import Optional, Dict, Tuple, Union, Any
22
+ import re
22
23
  import numpy as np
23
24
  from scitex.units import Unit, Q, Units
24
- from scitex.errors import SciTeXError
25
+ from scitex.logging import SciTeXError
26
+ import scitex.logging as logging
27
+ from scitex.logging import UnitWarning, warn as _warn
28
+
29
+ # Valid dimensionless/special unit markers
30
+ _VALID_DIMENSIONLESS = {"[-]", "[a.u.]", "[arb. units]", "[dimensionless]", "[1]", "[A.U.]"}
31
+
32
+
33
+ def _convert_to_negative_exponent(unit: str) -> str:
34
+ """Convert unit with / to negative exponent format.
35
+
36
+ Examples:
37
+ m/s -> m·s⁻¹
38
+ kg/m^2 -> kg·m⁻²
39
+ W/m^2/K -> W·m⁻²·K⁻¹
40
+ """
41
+ superscript = str.maketrans("0123456789-", "⁰¹²³⁴⁵⁶⁷⁸⁹⁻")
42
+
43
+ parts = unit.split("/")
44
+ if len(parts) < 2:
45
+ return unit
46
+
47
+ result = parts[0]
48
+ for part in parts[1:]:
49
+ exp_match = re.match(r"([a-zA-Z]+)\^?(\d+)?", part)
50
+ if exp_match:
51
+ base = exp_match.group(1)
52
+ exp = exp_match.group(2) or "1"
53
+ neg_exp = f"-{exp}".translate(superscript)
54
+ result += f"·{base}{neg_exp}"
55
+ else:
56
+ result += f"·{part}⁻¹"
57
+
58
+ return result
59
+
60
+
61
+ def validate_axis_label(label: str, axis_name: str = "axis") -> str:
62
+ """Validate and warn about axis label units (educational for scientific standards).
63
+
64
+ Checks for:
65
+ - Missing units
66
+ - Non-standard format (prefer [] over ())
67
+ - Suggests ^-1 format over /
68
+
69
+ Parameters
70
+ ----------
71
+ label : str
72
+ Axis label to validate
73
+ axis_name : str
74
+ Name for warning messages (e.g., "X axis", "Y axis")
75
+
76
+ Returns
77
+ -------
78
+ str
79
+ Original label (warnings are educational, not auto-correcting)
80
+ """
81
+ if not label:
82
+ return label
83
+
84
+ # Check for units in brackets [] or parentheses ()
85
+ has_square_brackets = bool(re.search(r"\[.*?\]", label))
86
+ has_parentheses = bool(re.search(r"\(.*?\)", label))
87
+
88
+ unit_match_square = re.search(r"\[(.*?)\]", label)
89
+ unit_match_paren = re.search(r"\((.*?)\)", label)
90
+
91
+ if not has_square_brackets and not has_parentheses:
92
+ _warn(
93
+ f"{axis_name} label '{label}' has no units. "
94
+ f"Consider: '{label} [unit]' or '{label} [-]' for dimensionless",
95
+ UnitWarning,
96
+ stacklevel=3,
97
+ )
98
+ return label
99
+
100
+ if has_parentheses and not has_square_brackets:
101
+ unit = unit_match_paren.group(1) if unit_match_paren else ""
102
+ suggested = re.sub(r"\((.*?)\)", f"[{unit}]", label)
103
+ _warn(
104
+ f"{axis_name} label '{label}' uses parentheses. "
105
+ f"SI convention prefers: '{suggested}'",
106
+ UnitWarning,
107
+ stacklevel=3,
108
+ )
109
+
110
+ unit_content = None
111
+ if unit_match_square:
112
+ unit_content = unit_match_square.group(1)
113
+ elif unit_match_paren:
114
+ unit_content = unit_match_paren.group(1)
115
+
116
+ if unit_content and "/" in unit_content:
117
+ suggested_unit = _convert_to_negative_exponent(unit_content)
118
+ if suggested_unit != unit_content:
119
+ suggested_label = label.replace(f"[{unit_content}]", f"[{suggested_unit}]")
120
+ _warn(
121
+ f"{axis_name} uses '/' in units. Consider: '{suggested_label}'",
122
+ UnitWarning,
123
+ stacklevel=3,
124
+ )
125
+
126
+ return label
25
127
 
26
128
 
27
129
  class UnitMismatchError(SciTeXError):
@@ -287,6 +389,9 @@ class UnitAwareMixin:
287
389
  if self._x_unit:
288
390
  label = f"{label} [{self._x_unit.symbol}]"
289
391
 
392
+ # Validate units (educational warnings for scientific standards)
393
+ validate_axis_label(label, "X axis")
394
+
290
395
  self._axes_mpl.set_xlabel(label)
291
396
 
292
397
  def set_ylabel(self, label: str, unit: Optional[Union[str, Unit]] = None) -> None:
@@ -305,6 +410,9 @@ class UnitAwareMixin:
305
410
  if self._y_unit:
306
411
  label = f"{label} [{self._y_unit.symbol}]"
307
412
 
413
+ # Validate units (educational warnings for scientific standards)
414
+ validate_axis_label(label, "Y axis")
415
+
308
416
  self._axes_mpl.set_ylabel(label)
309
417
 
310
418
  def set_zlabel(self, label: str, unit: Optional[Union[str, Unit]] = None) -> None:
@@ -326,4 +434,7 @@ class UnitAwareMixin:
326
434
  if self._z_unit:
327
435
  label = f"{label} [{self._z_unit.symbol}]"
328
436
 
437
+ # Validate units (educational warnings for scientific standards)
438
+ validate_axis_label(label, "Z axis")
439
+
329
440
  self._axes_mpl.set_zlabel(label)
@@ -304,6 +304,21 @@ class FigWrapper:
304
304
  for ax in self.axes:
305
305
  yield ax
306
306
 
307
+ @property
308
+ def history(self):
309
+ """Aggregate tracking history from all axes in the figure.
310
+
311
+ Returns a combined OrderedDict of all tracking records from all axes,
312
+ enabling FTS bundle creation to build encoding from plot operations.
313
+ """
314
+ from collections import OrderedDict
315
+
316
+ combined = OrderedDict()
317
+ for ax in self._traverse_axes():
318
+ if hasattr(ax, "history") and ax.history:
319
+ combined.update(ax.history)
320
+ return combined
321
+
307
322
  def legend(self, *args, loc="best", **kwargs):
308
323
  """Legend with 'best' automatic placement by default for all axes."""
309
324
  for ax in self._traverse_axes():