scitex 2.8.1__py3-none-any.whl → 2.10.2__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.2.dist-info}/METADATA +368 -183
  409. {scitex-2.8.1.dist-info → scitex-2.10.2.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.2.dist-info}/WHEEL +0 -0
  414. {scitex-2.8.1.dist-info → scitex-2.10.2.dist-info}/entry_points.txt +0 -0
  415. {scitex-2.8.1.dist-info → scitex-2.10.2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,188 @@
1
+ #!/usr/bin/env python3
2
+ # File: ./src/scitex/vis/backend/parser.py
3
+ """Parse figure JSON to Python object models."""
4
+
5
+ from typing import Any, Dict
6
+
7
+ from .._models import (
8
+ AnnotationModel,
9
+ AxesModel,
10
+ FigureModel,
11
+ GuideModel,
12
+ PlotModel,
13
+ )
14
+
15
+
16
+ def parse_figure_json(fig_json: Dict[str, Any]) -> FigureModel:
17
+ """
18
+ Parse figure JSON into FigureModel.
19
+
20
+ Parameters
21
+ ----------
22
+ fig_json : Dict[str, Any]
23
+ Figure JSON specification
24
+
25
+ Returns
26
+ -------
27
+ FigureModel
28
+ Parsed figure model
29
+
30
+ Examples
31
+ --------
32
+ >>> fig_json = {
33
+ ... "width_mm": 180,
34
+ ... "height_mm": 120,
35
+ ... "nrows": 1,
36
+ ... "ncols": 2,
37
+ ... "axes": [...]
38
+ ... }
39
+ >>> fig_model = parse_figure_json(fig_json)
40
+ """
41
+ # Parse nested axes
42
+ axes_data = fig_json.get("axes", [])
43
+ parsed_axes = [parse_axes_json(ax) for ax in axes_data]
44
+
45
+ # Create FigureModel with parsed axes
46
+ fig_data = fig_json.copy()
47
+ fig_data["axes"] = [ax.to_dict() for ax in parsed_axes]
48
+
49
+ return FigureModel.from_dict(fig_data)
50
+
51
+
52
+ def parse_axes_json(axes_json: Dict[str, Any]) -> AxesModel:
53
+ """
54
+ Parse axes JSON into AxesModel.
55
+
56
+ Parameters
57
+ ----------
58
+ axes_json : Dict[str, Any]
59
+ Axes JSON specification
60
+
61
+ Returns
62
+ -------
63
+ AxesModel
64
+ Parsed axes model
65
+ """
66
+ # Parse nested plots
67
+ plots_data = axes_json.get("plots", [])
68
+ parsed_plots = [parse_plot_json(plot) for plot in plots_data]
69
+
70
+ # Parse nested annotations
71
+ annotations_data = axes_json.get("annotations", [])
72
+ parsed_annotations = [parse_annotation_json(ann) for ann in annotations_data]
73
+
74
+ # Parse nested guides
75
+ guides_data = axes_json.get("guides", [])
76
+ parsed_guides = [parse_guide_json(guide) for guide in guides_data]
77
+
78
+ # Create AxesModel with parsed children
79
+ ax_data = axes_json.copy()
80
+ ax_data["plots"] = [p.to_dict() for p in parsed_plots]
81
+ ax_data["annotations"] = [a.to_dict() for a in parsed_annotations]
82
+ ax_data["guides"] = [g.to_dict() for g in parsed_guides]
83
+
84
+ return AxesModel.from_dict(ax_data)
85
+
86
+
87
+ def parse_plot_json(plot_json: Dict[str, Any]) -> PlotModel:
88
+ """
89
+ Parse plot JSON into PlotModel.
90
+
91
+ Parameters
92
+ ----------
93
+ plot_json : Dict[str, Any]
94
+ Plot JSON specification
95
+
96
+ Returns
97
+ -------
98
+ PlotModel
99
+ Parsed plot model
100
+ """
101
+ return PlotModel.from_dict(plot_json)
102
+
103
+
104
+ def parse_guide_json(guide_json: Dict[str, Any]) -> GuideModel:
105
+ """
106
+ Parse guide JSON into GuideModel.
107
+
108
+ Parameters
109
+ ----------
110
+ guide_json : Dict[str, Any]
111
+ Guide JSON specification
112
+
113
+ Returns
114
+ -------
115
+ GuideModel
116
+ Parsed guide model
117
+ """
118
+ return GuideModel.from_dict(guide_json)
119
+
120
+
121
+ def parse_annotation_json(annotation_json: Dict[str, Any]) -> AnnotationModel:
122
+ """
123
+ Parse annotation JSON into AnnotationModel.
124
+
125
+ Parameters
126
+ ----------
127
+ annotation_json : Dict[str, Any]
128
+ Annotation JSON specification
129
+
130
+ Returns
131
+ -------
132
+ AnnotationModel
133
+ Parsed annotation model
134
+ """
135
+ return AnnotationModel.from_dict(annotation_json)
136
+
137
+
138
+ def validate_figure_json(fig_json: Dict[str, Any]) -> bool:
139
+ """
140
+ Validate figure JSON structure (single entry point for validation).
141
+
142
+ This is the recommended validation function that performs:
143
+ 1. Basic JSON structure validation (utils.validate_json_structure)
144
+ 2. Model parsing and validation (FigureModel.validate)
145
+ 3. Axes layout validation (utils.validate_axes_layout)
146
+
147
+ Parameters
148
+ ----------
149
+ fig_json : Dict[str, Any]
150
+ Figure JSON to validate
151
+
152
+ Returns
153
+ -------
154
+ bool
155
+ True if valid, raises ValueError otherwise
156
+
157
+ Raises
158
+ ------
159
+ ValueError
160
+ If JSON structure is invalid
161
+
162
+ Examples
163
+ --------
164
+ >>> from scitex.fig.backend import validate_figure_json
165
+ >>> fig_json = {"width_mm": 180, "height_mm": 120, "axes": []}
166
+ >>> validate_figure_json(fig_json)
167
+ True
168
+ """
169
+ from ..utils import validate_axes_layout, validate_json_structure
170
+
171
+ # Step 1: Validate basic JSON structure
172
+ validate_json_structure(fig_json)
173
+
174
+ # Step 2: Parse to models and validate
175
+ fig_model = parse_figure_json(fig_json)
176
+ fig_model.validate()
177
+
178
+ # Step 3: Validate axes layout consistency
179
+ validate_axes_layout(
180
+ nrows=fig_model.nrows,
181
+ ncols=fig_model.ncols,
182
+ num_axes=len(fig_model.axes),
183
+ )
184
+
185
+ return True
186
+
187
+
188
+ # EOF
@@ -0,0 +1,538 @@
1
+ #!/usr/bin/env python3
2
+ # File: ./src/scitex/vis/backend/render.py
3
+ """Render figure models to matplotlib figures using scitex.plt."""
4
+
5
+ from typing import Any, Dict
6
+
7
+ import numpy as np
8
+
9
+ import scitex.logging as logging
10
+
11
+ from .._models import AnnotationModel, AxesModel, FigureModel, GuideModel, PlotModel
12
+ from ._parser import parse_figure_json
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ def render_figure(fig_model: FigureModel):
18
+ """
19
+ Render FigureModel to matplotlib figure using scitex.plt.
20
+
21
+ Parameters
22
+ ----------
23
+ fig_model : FigureModel
24
+ Figure model to render
25
+
26
+ Returns
27
+ -------
28
+ tuple
29
+ (fig, axes) where fig is FigWrapper and axes is list of AxisWrapper
30
+
31
+ Examples
32
+ --------
33
+ >>> fig_model = FigureModel(width_mm=180, height_mm=120)
34
+ >>> fig, axes = render_figure(fig_model)
35
+ """
36
+ import scitex as stx
37
+
38
+ # Validate before rendering
39
+ fig_model.validate()
40
+
41
+ # Convert mm to inches for matplotlib
42
+ MM_TO_INCH = 1 / 25.4
43
+ figsize_inch = (
44
+ fig_model.width_mm * MM_TO_INCH,
45
+ fig_model.height_mm * MM_TO_INCH,
46
+ )
47
+
48
+ # Create figure with mm dimensions
49
+ fig, axes = stx.plt.subplots(
50
+ nrows=fig_model.nrows,
51
+ ncols=fig_model.ncols,
52
+ figsize=figsize_inch,
53
+ dpi=fig_model.dpi,
54
+ facecolor=fig_model.facecolor,
55
+ edgecolor=fig_model.edgecolor,
56
+ )
57
+
58
+ # Ensure axes is always a list
59
+ if not isinstance(axes, (list, np.ndarray)):
60
+ axes = [axes]
61
+ elif isinstance(axes, np.ndarray):
62
+ axes = axes.flatten().tolist()
63
+
64
+ # TODO: Apply mm-based spacing (left_mm, right_mm, etc.)
65
+ # Currently, matplotlib subplots_adjust uses figure coordinates (0-1), not mm
66
+ # Future: Pass margin_left_mm, margin_right_mm etc. to stx.plt.subplots()
67
+ # to leverage scitex.plt's mm-control system
68
+
69
+ # Add suptitle if specified
70
+ if fig_model.suptitle:
71
+ suptitle_kwargs = {"t": fig_model.suptitle}
72
+ if fig_model.suptitle_fontsize is not None:
73
+ suptitle_kwargs["fontsize"] = fig_model.suptitle_fontsize
74
+ if fig_model.suptitle_fontweight is not None:
75
+ suptitle_kwargs["fontweight"] = fig_model.suptitle_fontweight
76
+ if fig_model.suptitle_y is not None:
77
+ suptitle_kwargs["y"] = fig_model.suptitle_y
78
+ fig.suptitle(**suptitle_kwargs)
79
+
80
+ # Render each axes
81
+ for i, axes_config in enumerate(fig_model.axes):
82
+ if i < len(axes):
83
+ axes_model = AxesModel.from_dict(axes_config)
84
+ render_axes(axes[i], axes_model)
85
+
86
+ return fig, axes
87
+
88
+
89
+ def render_axes(ax, axes_model: AxesModel):
90
+ """
91
+ Render AxesModel onto a matplotlib axis.
92
+
93
+ Parameters
94
+ ----------
95
+ ax : AxisWrapper or matplotlib Axes
96
+ Target axis
97
+ axes_model : AxesModel
98
+ Axes model to render
99
+ """
100
+ # Validate
101
+ axes_model.validate()
102
+
103
+ # Render plots first (background layer)
104
+ for plot_config in axes_model.plots:
105
+ plot_model = PlotModel.from_dict(plot_config)
106
+ render_plot(ax, plot_model)
107
+
108
+ # Render guides (middle layer - reference lines/spans over plots)
109
+ # Note: Use zorder in guide config to control layering if needed
110
+ for guide_config in axes_model.guides:
111
+ guide_model = GuideModel.from_dict(guide_config)
112
+ render_guide(ax, guide_model)
113
+
114
+ # Render annotations last (foreground layer)
115
+ for annotation_config in axes_model.annotations:
116
+ annotation_model = AnnotationModel.from_dict(annotation_config)
117
+ render_annotation(ax, annotation_model)
118
+
119
+ # Apply axis properties
120
+ if axes_model.xlabel:
121
+ ax.set_xlabel(axes_model.xlabel)
122
+
123
+ if axes_model.ylabel:
124
+ ax.set_ylabel(axes_model.ylabel)
125
+
126
+ if axes_model.title:
127
+ ax.set_title(axes_model.title)
128
+
129
+ # Set axis limits
130
+ if axes_model.xlim:
131
+ ax.set_xlim(axes_model.xlim)
132
+
133
+ if axes_model.ylim:
134
+ ax.set_ylim(axes_model.ylim)
135
+
136
+ # Set axis scales
137
+ ax.set_xscale(axes_model.xscale)
138
+ ax.set_yscale(axes_model.yscale)
139
+
140
+ # Ticks
141
+ if axes_model.xticks is not None:
142
+ ax.set_xticks(axes_model.xticks)
143
+
144
+ if axes_model.yticks is not None:
145
+ ax.set_yticks(axes_model.yticks)
146
+
147
+ if axes_model.xticklabels is not None:
148
+ ax.set_xticklabels(axes_model.xticklabels)
149
+
150
+ if axes_model.yticklabels is not None:
151
+ ax.set_yticklabels(axes_model.yticklabels)
152
+
153
+ # Apply style properties
154
+ style = axes_model.style
155
+
156
+ # Grid
157
+ if style.grid:
158
+ ax.grid(
159
+ alpha=style.grid_alpha,
160
+ linestyle=style.grid_linestyle,
161
+ linewidth=style.grid_linewidth,
162
+ )
163
+
164
+ # Spines
165
+ for spine, visible in style.spines_visible.items():
166
+ if hasattr(ax, "spines") and spine in ax.spines:
167
+ ax.spines[spine].set_visible(visible)
168
+
169
+ # Tick label size
170
+ if style.tick_fontsize is not None:
171
+ ax.tick_params(labelsize=style.tick_fontsize)
172
+
173
+ # Legend
174
+ if style.legend:
175
+ legend_kwargs = {
176
+ "loc": style.legend_loc,
177
+ "frameon": style.legend_frameon,
178
+ }
179
+ if style.legend_fontsize is not None:
180
+ legend_kwargs["fontsize"] = style.legend_fontsize
181
+ ax.legend(**legend_kwargs)
182
+
183
+ # Aspect
184
+ if style.aspect:
185
+ ax.set_aspect(style.aspect)
186
+
187
+ # Background color
188
+ if style.facecolor:
189
+ ax.set_facecolor(style.facecolor)
190
+
191
+
192
+ def render_plot(ax, plot_model: PlotModel):
193
+ """
194
+ Render PlotModel onto an axis.
195
+
196
+ Parameters
197
+ ----------
198
+ ax : AxisWrapper or matplotlib Axes
199
+ Target axis
200
+ plot_model : PlotModel
201
+ Plot model to render
202
+ """
203
+ # Validate
204
+ plot_model.validate()
205
+
206
+ data = plot_model.data
207
+
208
+ # Build kwargs from style (clean separation of concerns)
209
+ # Only include relevant kwargs for each plot type
210
+ style_dict = plot_model.style.to_dict()
211
+
212
+ # Common kwargs for all plot types
213
+ common_keys = {"color", "alpha", "zorder"}
214
+
215
+ # Define relevant keys for each plot type
216
+ plot_type_keys = {
217
+ "line": {"linewidth", "linestyle", "marker", "markersize"},
218
+ "scatter": {"marker", "markersize"},
219
+ "errorbar": {"linewidth", "linestyle", "marker", "xerr", "yerr", "capsize"},
220
+ "bar": {"width"},
221
+ "barh": {"width"}, # Will be renamed to height
222
+ "hist": {"bins", "density", "cumulative"},
223
+ "fill_between": {"linewidth", "fill_color", "fill_alpha"},
224
+ "heatmap": {"cmap", "vmin", "vmax", "interpolation"},
225
+ "imshow": {"cmap", "vmin", "vmax", "interpolation"},
226
+ "contour": {"cmap", "vmin", "vmax"},
227
+ "contourf": {"cmap", "vmin", "vmax"},
228
+ }
229
+
230
+ # Get relevant keys for this plot type
231
+ relevant_keys = common_keys | plot_type_keys.get(plot_model.plot_type, set())
232
+
233
+ # Filter kwargs to only include relevant ones
234
+ kwargs = {k: v for k, v in style_dict.items() if k in relevant_keys}
235
+
236
+ # Add label if present
237
+ if plot_model.label:
238
+ kwargs["label"] = plot_model.label
239
+
240
+ # Merge extra kwargs
241
+ kwargs.update(plot_model.extra_kwargs)
242
+
243
+ # Render based on plot type
244
+ if plot_model.plot_type == "line":
245
+ ax.plot(data["x"], data["y"], **kwargs)
246
+
247
+ elif plot_model.plot_type == "scatter":
248
+ # Rename markersize -> s for scatter
249
+ if "markersize" in kwargs:
250
+ kwargs["s"] = kwargs.pop("markersize")
251
+ ax.scatter(data["x"], data["y"], **kwargs)
252
+
253
+ elif plot_model.plot_type == "errorbar":
254
+ ax.errorbar(data["x"], data["y"], **kwargs)
255
+
256
+ elif plot_model.plot_type == "bar":
257
+ height = data.get("height", data.get("y"))
258
+ ax.bar(data["x"], height, **kwargs)
259
+
260
+ elif plot_model.plot_type == "barh":
261
+ # Rename width -> height for horizontal bars
262
+ if "width" in kwargs:
263
+ kwargs["height"] = kwargs.pop("width")
264
+ width = data.get("width", data.get("x"))
265
+ ax.barh(data["y"], width, **kwargs)
266
+
267
+ elif plot_model.plot_type == "hist":
268
+ ax.hist(data["x"], **kwargs)
269
+
270
+ elif plot_model.plot_type == "fill_between":
271
+ # Use fill_alpha and fill_color if present
272
+ if "fill_alpha" in kwargs:
273
+ kwargs["alpha"] = kwargs.pop("fill_alpha")
274
+ if "fill_color" in kwargs:
275
+ kwargs["color"] = kwargs.pop("fill_color")
276
+ ax.fill_between(data["x"], data["y1"], data["y2"], **kwargs)
277
+
278
+ elif plot_model.plot_type in ["heatmap", "imshow"]:
279
+ img_data = data.get("z", data.get("img"))
280
+ ax.imshow(img_data, **kwargs)
281
+
282
+ elif plot_model.plot_type == "contour":
283
+ ax.contour(data["x"], data["y"], data["z"], **kwargs)
284
+
285
+ elif plot_model.plot_type == "contourf":
286
+ ax.contourf(data["x"], data["y"], data["z"], **kwargs)
287
+
288
+ else:
289
+ raise ValueError(f"Unsupported plot type: {plot_model.plot_type}")
290
+
291
+
292
+ def render_guide(ax, guide_model: GuideModel):
293
+ """
294
+ Render GuideModel onto an axis.
295
+
296
+ Parameters
297
+ ----------
298
+ ax : AxisWrapper or matplotlib Axes
299
+ Target axis
300
+ guide_model : GuideModel
301
+ Guide model to render
302
+ """
303
+ # Validate
304
+ guide_model.validate()
305
+
306
+ # Build kwargs from style
307
+ kwargs = guide_model.style.to_dict()
308
+
309
+ # Add label if present
310
+ if guide_model.label:
311
+ kwargs["label"] = guide_model.label
312
+
313
+ # Render based on guide type
314
+ if guide_model.guide_type == "axhline":
315
+ ax.axhline(guide_model.y, **kwargs)
316
+
317
+ elif guide_model.guide_type == "axvline":
318
+ ax.axvline(guide_model.x, **kwargs)
319
+
320
+ elif guide_model.guide_type == "axhspan":
321
+ ax.axhspan(guide_model.ymin, guide_model.ymax, **kwargs)
322
+
323
+ elif guide_model.guide_type == "axvspan":
324
+ ax.axvspan(guide_model.xmin, guide_model.xmax, **kwargs)
325
+
326
+
327
+ def render_annotation(ax, annotation_model: AnnotationModel):
328
+ """
329
+ Render AnnotationModel onto an axis.
330
+
331
+ Parameters
332
+ ----------
333
+ ax : AxisWrapper or matplotlib Axes
334
+ Target axis
335
+ annotation_model : AnnotationModel
336
+ Annotation model to render
337
+ """
338
+ # Validate
339
+ annotation_model.validate()
340
+
341
+ # Build kwargs from style
342
+ kwargs = annotation_model.style.to_dict()
343
+
344
+ # Render based on annotation type
345
+ if annotation_model.annotation_type == "text":
346
+ ax.text(
347
+ annotation_model.x,
348
+ annotation_model.y,
349
+ annotation_model.text,
350
+ **kwargs,
351
+ )
352
+
353
+ elif annotation_model.annotation_type == "annotate":
354
+ ax.annotate(
355
+ annotation_model.text,
356
+ xy=(annotation_model.x, annotation_model.y),
357
+ xytext=annotation_model.xytext,
358
+ **kwargs,
359
+ )
360
+
361
+
362
+ def build_figure_from_json(fig_json: Dict[str, Any]):
363
+ """
364
+ Build matplotlib figure from figure JSON.
365
+
366
+ This is the main entry point for rendering.
367
+
368
+ Parameters
369
+ ----------
370
+ fig_json : Dict[str, Any]
371
+ Figure JSON specification
372
+
373
+ Returns
374
+ -------
375
+ tuple
376
+ (fig, axes) where fig is FigWrapper and axes is list of AxisWrapper
377
+
378
+ Examples
379
+ --------
380
+ >>> fig_json = {...}
381
+ >>> fig, axes = build_figure_from_json(fig_json)
382
+ >>> import scitex as stx
383
+ >>> stx.io.save(fig, "output.png")
384
+ """
385
+ fig_model = parse_figure_json(fig_json)
386
+ return render_figure(fig_model)
387
+
388
+
389
+ def _find_column(data, col_name):
390
+ """Find a column in data, supporting prefixed column names.
391
+
392
+ Parameters
393
+ ----------
394
+ data : pd.DataFrame
395
+ Data to search
396
+ col_name : str
397
+ Column name to find (can be simple like "x" or prefixed)
398
+
399
+ Returns
400
+ -------
401
+ str or None
402
+ Actual column name in data, or None if not found
403
+ """
404
+ if col_name in data.columns:
405
+ return col_name
406
+
407
+ # Try to find a column that ends with the variable name
408
+ # e.g., "ax-row-0-col-0_trace-id-plot-0_variable-x" matches "x"
409
+ suffix = f"_variable-{col_name}"
410
+ for col in data.columns:
411
+ if col.endswith(suffix):
412
+ return col
413
+
414
+ # Try partial match at the end
415
+ for col in data.columns:
416
+ if col.endswith(f"-{col_name}"):
417
+ return col
418
+
419
+ return None
420
+
421
+
422
+ def _is_special_trace(trace_id):
423
+ """Check if trace is a special type that doesn't require x/y encoding.
424
+
425
+ Parameters
426
+ ----------
427
+ trace_id : str
428
+ Trace identifier
429
+
430
+ Returns
431
+ -------
432
+ bool
433
+ True if this is a special trace type
434
+ """
435
+ special_patterns = [
436
+ "fill-between",
437
+ "fill_between",
438
+ "axhline",
439
+ "axvline",
440
+ "axhspan",
441
+ "axvspan",
442
+ "text",
443
+ "annotation",
444
+ ]
445
+ trace_lower = trace_id.lower()
446
+ return any(pattern in trace_lower for pattern in special_patterns)
447
+
448
+
449
+ def render_traces(ax, trace, data, theme=None):
450
+ """
451
+ Render a TraceEncoding onto an axis.
452
+
453
+ Parameters
454
+ ----------
455
+ ax : matplotlib Axes
456
+ Target axis
457
+ trace : TraceEncoding
458
+ Trace encoding specification
459
+ data : pd.DataFrame or None
460
+ Data to plot
461
+ theme : Theme, optional
462
+ Theme for styling
463
+
464
+ Raises
465
+ ------
466
+ ValueError
467
+ If data is None or required columns are missing (for standard traces)
468
+ """
469
+ # Skip special traces that don't require standard x/y encoding
470
+ if _is_special_trace(trace.trace_id):
471
+ logger.debug(f"Skipping special trace '{trace.trace_id}' (no x/y rendering)")
472
+ return
473
+
474
+ if data is None:
475
+ logger.warning(f"No data provided for trace '{trace.trace_id}', skipping")
476
+ return
477
+
478
+ # Get style from theme
479
+ color = None
480
+ linewidth = 1.0
481
+ alpha = 1.0
482
+
483
+ if theme:
484
+ # Check for trace-specific theme
485
+ if hasattr(theme, "traces") and theme.traces:
486
+ for t in theme.traces:
487
+ if t.trace_id == trace.trace_id:
488
+ color = t.color
489
+ if t.line_width_pt:
490
+ linewidth = t.line_width_pt
491
+ if t.alpha:
492
+ alpha = t.alpha
493
+ break
494
+
495
+ # Fall back to default colors
496
+ if not color and hasattr(theme, "colors"):
497
+ palette = theme.colors.palette
498
+ if palette:
499
+ color = palette[0]
500
+
501
+ if hasattr(theme, "lines"):
502
+ linewidth = theme.lines.width_pt
503
+
504
+ # Default color if none set
505
+ if not color:
506
+ color = "#1f77b4"
507
+
508
+ # Get column names from encoding
509
+ x_col_name = trace.x.column if trace.x else None
510
+ y_col_name = trace.y.column if trace.y else None
511
+
512
+ if not x_col_name or not y_col_name:
513
+ logger.warning(f"Trace '{trace.trace_id}' missing x or y encoding, skipping")
514
+ return
515
+
516
+ # Find actual columns in data (handles prefixed names)
517
+ x_col = _find_column(data, x_col_name)
518
+ y_col = _find_column(data, y_col_name)
519
+
520
+ if x_col is None:
521
+ logger.warning(f"Column '{x_col_name}' not found in data for trace '{trace.trace_id}'. Available: {list(data.columns)[:5]}...")
522
+ return
523
+ if y_col is None:
524
+ logger.warning(f"Column '{y_col_name}' not found in data for trace '{trace.trace_id}'. Available: {list(data.columns)[:5]}...")
525
+ return
526
+
527
+ # Plot the data
528
+ ax.plot(
529
+ data[x_col],
530
+ data[y_col],
531
+ color=color,
532
+ linewidth=linewidth,
533
+ alpha=alpha,
534
+ label=trace.trace_id,
535
+ )
536
+
537
+
538
+ # EOF