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,345 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: 2025-12-20
3
+ # File: /home/ywatanabe/proj/scitex-code/src/scitex/fts/_fig/_composite.py
4
+
5
+ """Composite figure renderer for FTS bundles.
6
+
7
+ Renders composite figures (kind=figure) containing multiple children.
8
+ ALWAYS re-renders from child's canonical (exports = optional cache only).
9
+
10
+ Design principles:
11
+ - Re-render children from canonical/encoding.json + payload/data.csv
12
+ - Recursively render nested figures (NOT from exports/)
13
+ - Apply container's theme for unified styling
14
+ - Generate geometry_px.json with flattened child geometry
15
+ - Cache key includes canonical_hash + effective_theme_hash + renderer_version
16
+ """
17
+
18
+ import io
19
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple
20
+
21
+ if TYPE_CHECKING:
22
+ from matplotlib.axes import Axes as MplAxes
23
+ from matplotlib.figure import Figure as MplFigure
24
+
25
+ from .._bundle._FTS import FTS
26
+ from ._dataclasses import Theme
27
+
28
+
29
+ def render_composite(
30
+ children: Dict[str, "FTS"],
31
+ layout: Dict,
32
+ size_mm: Optional[Dict[str, float]] = None,
33
+ theme: Optional["Theme"] = None,
34
+ dpi: int = 300,
35
+ ) -> Tuple["MplFigure", Dict]:
36
+ """Render composite figure with children in grid layout.
37
+
38
+ ALWAYS re-renders from child's canonical (exports = optional cache only).
39
+
40
+ Args:
41
+ children: Dict mapping child_name -> FTS object
42
+ layout: Layout specification {rows, cols, panels: [...]}
43
+ size_mm: Figure size in mm (default: 170x85 for two-column)
44
+ theme: Theme to apply to all children
45
+ dpi: Output DPI
46
+
47
+ Returns:
48
+ (figure, geometry_data)
49
+ """
50
+ import matplotlib.pyplot as plt
51
+ from matplotlib.gridspec import GridSpec
52
+
53
+ # Default size
54
+ if size_mm is None:
55
+ size_mm = {"width": 170, "height": 85}
56
+
57
+ # Convert mm to inches (matplotlib uses inches)
58
+ width_in = size_mm.get("width", 170) / 25.4
59
+ height_in = size_mm.get("height", 85) / 25.4
60
+
61
+ # Create figure with gridspec
62
+ rows = layout.get("rows", 1)
63
+ cols = layout.get("cols", 1)
64
+
65
+ fig = plt.figure(figsize=(width_in, height_in), dpi=dpi)
66
+ gs = GridSpec(rows, cols, figure=fig)
67
+
68
+ # Collect geometry data for all elements
69
+ geometry = {
70
+ "elements": [],
71
+ "panels": [],
72
+ }
73
+
74
+ # Render each panel
75
+ panels = layout.get("panels", [])
76
+ for panel_info in panels:
77
+ child_name = panel_info.get("child")
78
+ row = panel_info.get("row", 0)
79
+ col = panel_info.get("col", 0)
80
+ row_span = panel_info.get("row_span", 1)
81
+ col_span = panel_info.get("col_span", 1)
82
+ label = panel_info.get("label")
83
+
84
+ if child_name not in children:
85
+ continue
86
+
87
+ child = children[child_name]
88
+
89
+ # Create axes for this panel
90
+ ax = fig.add_subplot(gs[row : row + row_span, col : col + col_span])
91
+
92
+ # Render child into axes (ALWAYS re-render from canonical)
93
+ child_geometry = render_child_in_axes(ax, child, theme)
94
+
95
+ # Add panel label if specified
96
+ if label:
97
+ _add_panel_label(ax, label)
98
+
99
+ # Record panel geometry (flatten into parent's figure_px space)
100
+ panel_geometry = {
101
+ "child": child_name,
102
+ "child_id": panel_info.get("child_id"),
103
+ "label": label,
104
+ "position": {"row": row, "col": col, "row_span": row_span, "col_span": col_span},
105
+ "bbox_figure": _get_axes_bbox(ax, fig),
106
+ "child_elements": child_geometry.get("elements", []),
107
+ }
108
+ geometry["panels"].append(panel_geometry)
109
+
110
+ plt.tight_layout()
111
+
112
+ return fig, geometry
113
+
114
+
115
+ def render_child_in_axes(
116
+ ax: "MplAxes",
117
+ child: "FTS",
118
+ theme: Optional["Theme"] = None,
119
+ ) -> Dict:
120
+ """Render child FTS into axes.
121
+
122
+ ALWAYS re-renders from canonical (exports = optional cache only):
123
+ - kind=plot: Re-render from canonical/spec.json + payload/data.csv
124
+ - kind=figure: Recursively render children (NOT use exports/figure.png)
125
+
126
+ Args:
127
+ ax: Matplotlib axes to render into
128
+ child: Child FTS bundle
129
+ theme: Theme to apply (overrides child's theme)
130
+
131
+ Returns:
132
+ Geometry data for this child
133
+ """
134
+ geometry = {"elements": []}
135
+
136
+ if child.node.is_leaf_kind():
137
+ # Leaf node: render from encoding + payload
138
+ geometry = _render_leaf_in_axes(ax, child, theme)
139
+ elif child.node.is_composite_kind():
140
+ # Composite: recursive render (nested figure)
141
+ geometry = _render_composite_in_axes(ax, child, theme)
142
+
143
+ return geometry
144
+
145
+
146
+ def _render_leaf_in_axes(
147
+ ax: "MplAxes",
148
+ child: "FTS",
149
+ theme: Optional["Theme"] = None,
150
+ ) -> Dict:
151
+ """Render leaf FTS (plot/table/stats) into axes.
152
+
153
+ Uses child's pre-rendered export (artifacts/exports/figure.png) to embed
154
+ the visualization. This approach ensures visual consistency with the
155
+ original rendered plot.
156
+ """
157
+ import io
158
+
159
+ import matplotlib.pyplot as plt
160
+
161
+ geometry = {"elements": []}
162
+
163
+ # Try to use pre-rendered export image
164
+ try:
165
+ storage = child.storage
166
+
167
+ # Check for exported image
168
+ if storage.exists("artifacts/exports/figure.png"):
169
+ img_data = storage.read("artifacts/exports/figure.png")
170
+ img = plt.imread(io.BytesIO(img_data), format="png")
171
+
172
+ # Display image in axes, filling the entire axes
173
+ ax.imshow(img, aspect="auto", extent=[0, 1, 0, 1])
174
+ ax.set_xlim(0, 1)
175
+ ax.set_ylim(0, 1)
176
+ ax.axis("off")
177
+
178
+ geometry["elements"].append({
179
+ "type": "embedded_image",
180
+ "source": "artifacts/exports/figure.png",
181
+ })
182
+ return geometry
183
+
184
+ except Exception as e:
185
+ pass # Fall through to placeholder
186
+
187
+ # Fallback: draw placeholder with child name
188
+ ax.text(
189
+ 0.5,
190
+ 0.5,
191
+ f"[{child.node.name or child.node.id[:8]}]",
192
+ ha="center",
193
+ va="center",
194
+ transform=ax.transAxes,
195
+ fontsize=10,
196
+ )
197
+ ax.set_xlim(0, 1)
198
+ ax.set_ylim(0, 1)
199
+
200
+ return geometry
201
+
202
+
203
+ def _render_composite_in_axes(
204
+ ax: "MplAxes",
205
+ child: "FTS",
206
+ theme: Optional["Theme"] = None,
207
+ ) -> Dict:
208
+ """Render composite FTS (figure) into axes.
209
+
210
+ Creates a nested gridspec for the child's children.
211
+ """
212
+ from .._bundle._children import load_embedded_children
213
+
214
+ geometry = {"elements": [], "nested_panels": []}
215
+
216
+ # Load child's children
217
+ child_children = child.load_children()
218
+ if not child_children:
219
+ ax.text(
220
+ 0.5,
221
+ 0.5,
222
+ f"[Empty: {child.node.name or child.node.id[:8]}]",
223
+ ha="center",
224
+ va="center",
225
+ transform=ax.transAxes,
226
+ )
227
+ return geometry
228
+
229
+ # Get child's layout
230
+ child_layout = child.node.layout or {"rows": 1, "cols": 1, "panels": []}
231
+
232
+ # For nested figures, we need to subdivide the axes
233
+ # This is a simplified approach - full implementation would use inset axes
234
+ from mpl_toolkits.axes_grid1 import make_axes_locatable
235
+
236
+ ax.axis("off") # Hide the parent axes
237
+
238
+ # Create a figure-in-figure effect using inset axes
239
+ # For simplicity, render children side by side
240
+ n_children = len(child_children)
241
+ width = 1.0 / max(n_children, 1)
242
+
243
+ for i, (grandchild_name, grandchild) in enumerate(child_children.items()):
244
+ # Create inset axes
245
+ inset_bounds = [width * i, 0, width * 0.95, 1]
246
+ inset_ax = ax.inset_axes(inset_bounds)
247
+
248
+ # Recursively render grandchild
249
+ grandchild_geometry = render_child_in_axes(inset_ax, grandchild, theme)
250
+
251
+ geometry["nested_panels"].append(
252
+ {
253
+ "child": grandchild_name,
254
+ "child_id": grandchild.node.id if grandchild.node else None,
255
+ "elements": grandchild_geometry.get("elements", []),
256
+ }
257
+ )
258
+
259
+ return geometry
260
+
261
+
262
+ def _add_panel_label(ax: "MplAxes", label: str) -> None:
263
+ """Add panel label (A, B, C...) to axes."""
264
+ ax.text(
265
+ -0.1,
266
+ 1.05,
267
+ label,
268
+ transform=ax.transAxes,
269
+ fontsize=14,
270
+ fontweight="bold",
271
+ va="bottom",
272
+ ha="right",
273
+ )
274
+
275
+
276
+ def _get_axes_bbox(ax: "MplAxes", fig: "MplFigure") -> Dict[str, float]:
277
+ """Get axes bounding box in figure coordinates."""
278
+ bbox = ax.get_position()
279
+ return {
280
+ "x": bbox.x0,
281
+ "y": bbox.y0,
282
+ "width": bbox.width,
283
+ "height": bbox.height,
284
+ }
285
+
286
+
287
+ def check_cache_valid(
288
+ child: "FTS",
289
+ current_theme_hash: str,
290
+ renderer_version: str = "1.0.0",
291
+ ) -> bool:
292
+ """Check if child's cached exports are still valid.
293
+
294
+ Cache is valid only if ALL match:
295
+ 1. artifacts/exports/figure.png exists
296
+ 2. artifacts/cache/render_manifest.json exists
297
+ 3. render_manifest.canonical_hash matches current hash
298
+ 4. render_manifest.effective_theme_hash matches current theme
299
+ 5. render_manifest.renderer_version matches current renderer
300
+
301
+ Args:
302
+ child: Child FTS bundle
303
+ current_theme_hash: Hash of current effective theme
304
+ renderer_version: Current renderer version
305
+
306
+ Returns:
307
+ True if cache is valid and can be used
308
+ """
309
+ from .._bundle._storage import get_storage
310
+ from .._bundle._saver import compute_canonical_hash
311
+
312
+ storage = get_storage(child.path)
313
+
314
+ # Check exports exist
315
+ if not storage.exists("artifacts/exports/figure.png"):
316
+ return False
317
+
318
+ # Check manifest exists
319
+ manifest = storage.read_json("artifacts/cache/render_manifest.json")
320
+ if manifest is None:
321
+ return False
322
+
323
+ # Check canonical hash
324
+ current_canonical_hash = compute_canonical_hash(storage)
325
+ if manifest.get("canonical_hash") != current_canonical_hash:
326
+ return False
327
+
328
+ # Check theme hash
329
+ if manifest.get("effective_theme_hash") != current_theme_hash:
330
+ return False
331
+
332
+ # Check renderer version
333
+ if manifest.get("renderer_version") != renderer_version:
334
+ return False
335
+
336
+ return True
337
+
338
+
339
+ __all__ = [
340
+ "render_composite",
341
+ "render_child_in_axes",
342
+ "check_cache_valid",
343
+ ]
344
+
345
+ # EOF
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: 2025-12-21
3
+ # File: /home/ywatanabe/proj/scitex-code/src/scitex/fts/_kinds/_plot/__init__.py
4
+
5
+ """Plot kind - Data visualization with encoding.
6
+
7
+ A plot bundle contains data (payload/data.csv) and an encoding
8
+ specification that maps data columns to visual channels (x, y, color, etc.).
9
+
10
+ Structure:
11
+ - payload/data.csv: Source data
12
+ - canonical/encoding.json: Data-to-visual mappings
13
+ - canonical/theme.json: Visual styling
14
+ """
15
+
16
+ from ._dataclasses import Encoding, Theme
17
+ from ._backend._render import render_traces
18
+
19
+ __all__ = [
20
+ "Encoding",
21
+ "Theme",
22
+ "render_traces",
23
+ ]
24
+
25
+ # EOF
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env python3
2
+ # File: ./src/scitex/vis/backend/__init__.py
3
+ """
4
+ Backend for rendering figure JSON to matplotlib figures.
5
+
6
+ This module bridges the gap between JSON specifications and
7
+ actual matplotlib figures using scitex.plt.
8
+ """
9
+
10
+ from ._export import (
11
+ export_figure,
12
+ export_figure_from_file,
13
+ export_multiple_formats,
14
+ )
15
+ from ._parser import (
16
+ parse_annotation_json,
17
+ parse_axes_json,
18
+ parse_figure_json,
19
+ parse_guide_json,
20
+ parse_plot_json,
21
+ validate_figure_json,
22
+ )
23
+ from ._render import (
24
+ build_figure_from_json,
25
+ render_annotation,
26
+ render_axes,
27
+ render_figure,
28
+ render_guide,
29
+ render_plot,
30
+ )
31
+
32
+ __all__ = [
33
+ # Parser
34
+ "parse_figure_json",
35
+ "parse_axes_json",
36
+ "parse_plot_json",
37
+ "parse_guide_json",
38
+ "parse_annotation_json",
39
+ "validate_figure_json",
40
+ # Renderer
41
+ "render_figure",
42
+ "render_axes",
43
+ "render_plot",
44
+ "render_guide",
45
+ "render_annotation",
46
+ "build_figure_from_json",
47
+ # Exporter
48
+ "export_figure",
49
+ "export_figure_from_file",
50
+ "export_multiple_formats",
51
+ ]
52
+
53
+ # EOF
@@ -0,0 +1,165 @@
1
+ #!/usr/bin/env python3
2
+ # File: ./src/scitex/vis/backend/export.py
3
+ """Export figure models to image files via scitex.io."""
4
+
5
+ from pathlib import Path
6
+ from typing import Any, Dict, Optional, Union
7
+
8
+ from ._render import build_figure_from_json
9
+
10
+
11
+ def export_figure(
12
+ fig_json: Dict[str, Any],
13
+ output_path: Union[str, Path],
14
+ fmt: Optional[str] = None,
15
+ dpi: int = 300,
16
+ auto_crop: bool = False,
17
+ **kwargs,
18
+ ) -> Path:
19
+ """
20
+ Export figure JSON to image file.
21
+
22
+ Parameters
23
+ ----------
24
+ fig_json : Dict[str, Any]
25
+ Figure JSON specification
26
+ output_path : str or Path
27
+ Output file path
28
+ fmt : str, optional
29
+ Output format ("png", "pdf", "svg", etc.)
30
+ If None, inferred from output_path extension
31
+ dpi : int, optional
32
+ Resolution in dots per inch (default: 300)
33
+ auto_crop : bool, optional
34
+ Automatically crop whitespace (default: False)
35
+ **kwargs
36
+ Additional keyword arguments passed to scitex.io.save()
37
+
38
+ Returns
39
+ -------
40
+ Path
41
+ Path to the saved file
42
+
43
+ Examples
44
+ --------
45
+ >>> fig_json = {"width_mm": 180, "height_mm": 120, ...}
46
+ >>> export_figure(fig_json, "output.png", dpi=300)
47
+ PosixPath('output.png')
48
+
49
+ >>> # Auto-crop for publication
50
+ >>> export_figure(fig_json, "figure.pdf", auto_crop=True)
51
+ PosixPath('figure.pdf')
52
+ """
53
+ import scitex as stx
54
+
55
+ output_path = Path(output_path)
56
+
57
+ # Build figure from JSON
58
+ fig, axes = build_figure_from_json(fig_json)
59
+
60
+ # Save using scitex.io
61
+ save_kwargs = {"dpi": dpi, "auto_crop": auto_crop}
62
+ save_kwargs.update(kwargs)
63
+
64
+ if fmt:
65
+ save_kwargs["fmt"] = fmt
66
+
67
+ stx.io.save(fig, output_path, **save_kwargs)
68
+
69
+ return output_path
70
+
71
+
72
+ def export_figure_from_file(
73
+ json_path: Union[str, Path],
74
+ output_path: Union[str, Path],
75
+ **kwargs,
76
+ ) -> Path:
77
+ """
78
+ Export figure from JSON file to image file.
79
+
80
+ Parameters
81
+ ----------
82
+ json_path : str or Path
83
+ Path to figure JSON file
84
+ output_path : str or Path
85
+ Output image file path
86
+ **kwargs
87
+ Additional keyword arguments passed to export_figure()
88
+
89
+ Returns
90
+ -------
91
+ Path
92
+ Path to the saved file
93
+
94
+ Examples
95
+ --------
96
+ >>> export_figure_from_file("figure.json", "figure.png")
97
+ PosixPath('figure.png')
98
+ """
99
+ import scitex as stx
100
+
101
+ json_path = Path(json_path)
102
+
103
+ # Load JSON
104
+ fig_json = stx.io.load(json_path)
105
+
106
+ # Export
107
+ return export_figure(fig_json, output_path, **kwargs)
108
+
109
+
110
+ def export_multiple_formats(
111
+ fig_json: Dict[str, Any],
112
+ output_dir: Union[str, Path],
113
+ base_name: str,
114
+ formats: list = None,
115
+ **kwargs,
116
+ ) -> Dict[str, Path]:
117
+ """
118
+ Export figure to multiple formats.
119
+
120
+ Parameters
121
+ ----------
122
+ fig_json : Dict[str, Any]
123
+ Figure JSON specification
124
+ output_dir : str or Path
125
+ Output directory
126
+ base_name : str
127
+ Base filename (without extension)
128
+ formats : list, optional
129
+ List of formats (default: ["png", "pdf", "svg"])
130
+ **kwargs
131
+ Additional keyword arguments passed to export_figure()
132
+
133
+ Returns
134
+ -------
135
+ Dict[str, Path]
136
+ Dictionary mapping format to output path
137
+
138
+ Examples
139
+ --------
140
+ >>> fig_json = {...}
141
+ >>> paths = export_multiple_formats(
142
+ ... fig_json,
143
+ ... "output",
144
+ ... "figure-01",
145
+ ... formats=["png", "pdf"]
146
+ ... )
147
+ >>> paths
148
+ {'png': PosixPath('output/figure-01.png'),
149
+ 'pdf': PosixPath('output/figure-01.pdf')}
150
+ """
151
+ if formats is None:
152
+ formats = ["png", "pdf", "svg"]
153
+
154
+ output_dir = Path(output_dir)
155
+ output_dir.mkdir(parents=True, exist_ok=True)
156
+
157
+ results = {}
158
+ for fmt in formats:
159
+ output_path = output_dir / f"{base_name}.{fmt}"
160
+ results[fmt] = export_figure(fig_json, output_path, fmt=fmt, **kwargs)
161
+
162
+ return results
163
+
164
+
165
+ # EOF