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,44 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: 2025-12-20
3
+ # File: /home/ywatanabe/proj/scitex-code/src/scitex/fsb/_dataclasses/DataSource.py
4
+
5
+ """DataSource - Original data source information."""
6
+
7
+ from dataclasses import dataclass
8
+ from typing import Any, Dict, Optional
9
+
10
+
11
+ @dataclass
12
+ class DataSource:
13
+ """Original data source information."""
14
+
15
+ path: Optional[str] = None
16
+ sha256: Optional[str] = None
17
+ created_at: Optional[str] = None
18
+ description: Optional[str] = None
19
+
20
+ def to_dict(self) -> Dict[str, Any]:
21
+ result = {}
22
+ if self.path:
23
+ result["path"] = self.path
24
+ if self.sha256:
25
+ result["sha256"] = self.sha256
26
+ if self.created_at:
27
+ result["created_at"] = self.created_at
28
+ if self.description:
29
+ result["description"] = self.description
30
+ return result
31
+
32
+ @classmethod
33
+ def from_dict(cls, data: Dict[str, Any]) -> "DataSource":
34
+ return cls(
35
+ path=data.get("path"),
36
+ sha256=data.get("sha256"),
37
+ created_at=data.get("created_at"),
38
+ description=data.get("description"),
39
+ )
40
+
41
+
42
+ __all__ = ["DataSource"]
43
+
44
+ # EOF
@@ -0,0 +1,319 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: 2025-12-21
3
+ # File: /home/ywatanabe/proj/scitex-code/src/scitex/fts/_bundle/_dataclasses/_Node.py
4
+
5
+ """Node - Core FTS Node model with kind-based constraints."""
6
+
7
+ from dataclasses import dataclass, field
8
+ from datetime import datetime
9
+ from typing import Any, ClassVar, Dict, List, Optional, Set
10
+
11
+ from ._Axes import Axes
12
+ from ._BBox import BBox
13
+ from ._NodeRefs import NodeRefs
14
+ from ._SizeMM import SizeMM
15
+
16
+
17
+ @dataclass
18
+ class TextContent:
19
+ """Text content for kind=text nodes."""
20
+
21
+ content: str = ""
22
+ fontsize: Optional[float] = None
23
+ fontweight: Optional[str] = None # "normal" | "bold"
24
+ ha: str = "center" # "left" | "center" | "right"
25
+ va: str = "center" # "top" | "center" | "bottom"
26
+
27
+ def to_dict(self) -> Dict[str, Any]:
28
+ result = {"content": self.content, "ha": self.ha, "va": self.va}
29
+ if self.fontsize is not None:
30
+ result["fontsize"] = self.fontsize
31
+ if self.fontweight is not None:
32
+ result["fontweight"] = self.fontweight
33
+ return result
34
+
35
+ @classmethod
36
+ def from_dict(cls, data: Dict[str, Any]) -> "TextContent":
37
+ return cls(
38
+ content=data.get("content", ""),
39
+ fontsize=data.get("fontsize"),
40
+ fontweight=data.get("fontweight"),
41
+ ha=data.get("ha", "center"),
42
+ va=data.get("va", "center"),
43
+ )
44
+
45
+
46
+ @dataclass
47
+ class ShapeParams:
48
+ """Shape parameters for kind=shape nodes."""
49
+
50
+ shape_type: str = "rectangle" # "rectangle" | "ellipse" | "arrow" | "line"
51
+ color: str = "#000000"
52
+ linewidth: float = 1.0
53
+ fill: bool = False
54
+
55
+ def to_dict(self) -> Dict[str, Any]:
56
+ return {
57
+ "shape_type": self.shape_type,
58
+ "color": self.color,
59
+ "linewidth": self.linewidth,
60
+ "fill": self.fill,
61
+ }
62
+
63
+ @classmethod
64
+ def from_dict(cls, data: Dict[str, Any]) -> "ShapeParams":
65
+ return cls(
66
+ shape_type=data.get("shape_type", "rectangle"),
67
+ color=data.get("color", "#000000"),
68
+ linewidth=data.get("linewidth", 1.0),
69
+ fill=data.get("fill", False),
70
+ )
71
+
72
+
73
+ @dataclass
74
+ class Node:
75
+ """Core FTS Node model with kind-based constraints.
76
+
77
+ The central structural element of an FTS bundle.
78
+ Stored in canonical/spec.json.
79
+
80
+ Kind categories:
81
+ - Data leaf kinds (plot, table, stats): require payload data files
82
+ - Annotation leaf kinds (text, shape): no payload required, params in node
83
+ - Image leaf kinds (image): require payload image file
84
+ - Composite kinds (figure): contain children, no payload
85
+
86
+ All bundles have IDENTICAL directory structure:
87
+ - canonical/: Source of truth (spec.json, encoding.json, theme.json, etc.)
88
+ - payload/: ALWAYS exists (empty for composites/annotations, populated for data/image)
89
+ - artifacts/: Derived files (exports/, cache/)
90
+ - children/: ALWAYS exists (empty for leaves, populated for composites)
91
+ """
92
+
93
+ # Required fields
94
+ id: str
95
+ kind: str # "figure" | "plot" | "table" | "stats" | "text" | "shape" | "image"
96
+
97
+ # Schema versioning for forward compatibility
98
+ scitex_schema: str = "scitex.fts.spec"
99
+ scitex_schema_version: str = "1.0.0"
100
+
101
+ # Children and layout (for composite kinds)
102
+ children: List[str] = field(default_factory=list)
103
+ layout: Optional[Dict] = None
104
+
105
+ # Payload schema (for data leaf kinds)
106
+ payload_schema: Optional[str] = None
107
+
108
+ # Visual properties
109
+ bbox_norm: BBox = field(default_factory=BBox)
110
+ name: Optional[str] = None
111
+ size_mm: Optional[SizeMM] = None
112
+ axes: Optional[Axes] = None
113
+
114
+ # Kind-specific content
115
+ text: Optional[TextContent] = None # For kind=text
116
+ shape: Optional[ShapeParams] = None # For kind=shape
117
+
118
+ # References and timestamps
119
+ refs: NodeRefs = field(default_factory=NodeRefs)
120
+ created_at: Optional[str] = None
121
+ modified_at: Optional[str] = None
122
+
123
+ # === Kind Constants ===
124
+ # Data leaf kinds: require payload data files, forbid children
125
+ DATA_LEAF_KINDS: ClassVar[Set[str]] = {"plot", "table", "stats"}
126
+ # Annotation leaf kinds: no payload required, forbid children
127
+ ANNOTATION_LEAF_KINDS: ClassVar[Set[str]] = {"text", "shape"}
128
+ # Image leaf kinds: require payload image file, forbid children
129
+ IMAGE_LEAF_KINDS: ClassVar[Set[str]] = {"image"}
130
+ # All leaf kinds (for convenience)
131
+ LEAF_KINDS: ClassVar[Set[str]] = DATA_LEAF_KINDS | ANNOTATION_LEAF_KINDS | IMAGE_LEAF_KINDS
132
+ # Composite kinds: allow children, forbid payload
133
+ COMPOSITE_KINDS: ClassVar[Set[str]] = {"figure"}
134
+ # All valid kinds
135
+ ALL_KINDS: ClassVar[Set[str]] = LEAF_KINDS | COMPOSITE_KINDS
136
+
137
+ # Payload schema -> required file mapping
138
+ PAYLOAD_REQUIRED_FILES: ClassVar[Dict[str, str]] = {
139
+ "scitex.fts.payload.plot@1": "payload/data.csv",
140
+ "scitex.fts.payload.table@1": "payload/table.csv",
141
+ "scitex.fts.payload.stats@1": "payload/stats.json",
142
+ "scitex.fts.payload.image@1": "payload/image.png",
143
+ }
144
+
145
+ def __post_init__(self):
146
+ if self.created_at is None:
147
+ self.created_at = datetime.utcnow().isoformat() + "Z"
148
+ if self.modified_at is None:
149
+ self.modified_at = self.created_at
150
+
151
+ # === Kind Methods ===
152
+
153
+ def is_leaf_kind(self) -> bool:
154
+ """Check if this is any leaf kind (forbids children)."""
155
+ return self.kind in self.LEAF_KINDS
156
+
157
+ def is_data_leaf_kind(self) -> bool:
158
+ """Check if this is a data leaf kind (requires payload data)."""
159
+ return self.kind in self.DATA_LEAF_KINDS
160
+
161
+ def is_annotation_leaf_kind(self) -> bool:
162
+ """Check if this is an annotation leaf kind (no payload required)."""
163
+ return self.kind in self.ANNOTATION_LEAF_KINDS
164
+
165
+ def is_image_leaf_kind(self) -> bool:
166
+ """Check if this is an image leaf kind (requires payload image)."""
167
+ return self.kind in self.IMAGE_LEAF_KINDS
168
+
169
+ def is_composite_kind(self) -> bool:
170
+ """Check if this is a composite kind (allows children, forbids payload)."""
171
+ return self.kind in self.COMPOSITE_KINDS
172
+
173
+ def get_required_payload_file(self) -> Optional[str]:
174
+ """Get required payload file path based on payload_schema."""
175
+ return self.PAYLOAD_REQUIRED_FILES.get(self.payload_schema)
176
+
177
+ # === Validation ===
178
+
179
+ def validate(self) -> List[str]:
180
+ """Validate logical constraints (not file existence).
181
+
182
+ Returns:
183
+ List of error messages (empty if valid)
184
+ """
185
+ errors = []
186
+
187
+ # Check: kind is valid
188
+ if self.kind not in self.ALL_KINDS:
189
+ errors.append(f"Unknown kind: {self.kind}. Valid kinds: {sorted(self.ALL_KINDS)}")
190
+ return errors # Early return - other checks don't make sense
191
+
192
+ # Check: children list has no duplicates
193
+ if len(self.children) != len(set(self.children)):
194
+ errors.append("children list has duplicates")
195
+
196
+ if self.is_leaf_kind():
197
+ # All leaf kinds: children must be empty
198
+ if self.children:
199
+ errors.append(f"kind={self.kind} cannot have children")
200
+
201
+ # Data leaf kinds: payload_schema is optional but recommended
202
+ # Annotation leaf kinds: should not have payload_schema
203
+ if self.is_annotation_leaf_kind() and self.payload_schema:
204
+ errors.append(f"kind={self.kind} should not have payload_schema")
205
+
206
+ elif self.is_composite_kind():
207
+ # Composite kinds: payload_schema must be None
208
+ if self.payload_schema:
209
+ errors.append(f"kind={self.kind} should not have payload_schema")
210
+
211
+ # Validate layout if present
212
+ if self.layout:
213
+ panels = self.layout.get("panels", [])
214
+ panel_children = [p.get("child") for p in panels]
215
+
216
+ # Check: panel child references must be subset of children
217
+ for child_ref in panel_children:
218
+ if child_ref not in self.children:
219
+ errors.append(f"layout.panels references unknown child: {child_ref}")
220
+
221
+ # Check: no duplicate panel child references
222
+ if len(panel_children) != len(set(panel_children)):
223
+ errors.append("layout.panels has duplicate child references")
224
+
225
+ return errors
226
+
227
+ # === Serialization ===
228
+
229
+ def to_dict(self) -> Dict[str, Any]:
230
+ """Convert to dictionary for JSON serialization."""
231
+ result = {
232
+ "id": self.id,
233
+ "kind": self.kind,
234
+ "scitex_schema": self.scitex_schema,
235
+ "scitex_schema_version": self.scitex_schema_version,
236
+ "bbox_norm": self.bbox_norm.to_dict(),
237
+ }
238
+
239
+ if self.name:
240
+ result["name"] = self.name
241
+ if self.size_mm:
242
+ result["size_mm"] = self.size_mm.to_dict()
243
+ if self.axes:
244
+ result["axes"] = self.axes.to_dict()
245
+ if self.children:
246
+ result["children"] = self.children
247
+ if self.layout:
248
+ result["layout"] = self.layout
249
+ if self.payload_schema:
250
+ result["payload_schema"] = self.payload_schema
251
+ if self.text:
252
+ result["text"] = self.text.to_dict()
253
+ if self.shape:
254
+ result["shape"] = self.shape.to_dict()
255
+
256
+ result["refs"] = self.refs.to_dict()
257
+ result["created_at"] = self.created_at
258
+ result["modified_at"] = self.modified_at
259
+ return result
260
+
261
+ @classmethod
262
+ def from_dict(cls, data: Dict[str, Any]) -> "Node":
263
+ """Create Node from dictionary."""
264
+ # Handle legacy 'type' field
265
+ kind = data.get("kind") or data.get("type", "plot")
266
+
267
+ # Handle payload_schema
268
+ payload_schema = data.get("payload_schema")
269
+
270
+ # Handle size_mm from different formats
271
+ size_mm_data = data.get("size_mm")
272
+ if size_mm_data is None and "size" in data:
273
+ size = data["size"]
274
+ if isinstance(size, dict):
275
+ size_mm_data = {
276
+ "width": size.get("width_mm", size.get("width", 85)),
277
+ "height": size.get("height_mm", size.get("height", 85)),
278
+ }
279
+
280
+ # Handle name from 'title' field (legacy format)
281
+ name = data.get("name") or data.get("title")
282
+
283
+ # Handle text content
284
+ text = None
285
+ if "text" in data and isinstance(data["text"], dict):
286
+ text = TextContent.from_dict(data["text"])
287
+
288
+ # Handle shape params
289
+ shape = None
290
+ if "shape" in data and isinstance(data["shape"], dict):
291
+ shape = ShapeParams.from_dict(data["shape"])
292
+
293
+ return cls(
294
+ id=data.get("id", "unknown"),
295
+ kind=kind,
296
+ scitex_schema=data.get("scitex_schema", "scitex.fts.spec"),
297
+ scitex_schema_version=data.get("scitex_schema_version", "1.0.0"),
298
+ children=data.get("children", []),
299
+ layout=data.get("layout"),
300
+ payload_schema=payload_schema,
301
+ bbox_norm=BBox.from_dict(data.get("bbox_norm", {})),
302
+ name=name,
303
+ size_mm=SizeMM.from_dict(size_mm_data) if size_mm_data else None,
304
+ axes=Axes.from_dict(data["axes"]) if "axes" in data else None,
305
+ text=text,
306
+ shape=shape,
307
+ refs=NodeRefs.from_dict(data.get("refs", {})),
308
+ created_at=data.get("created_at"),
309
+ modified_at=data.get("modified_at"),
310
+ )
311
+
312
+ def touch(self):
313
+ """Update modified timestamp."""
314
+ self.modified_at = datetime.utcnow().isoformat() + "Z"
315
+
316
+
317
+ __all__ = ["Node", "TextContent", "ShapeParams"]
318
+
319
+ # EOF
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: 2025-12-20
3
+ # File: /home/ywatanabe/proj/scitex-code/src/scitex/fsb/_dataclasses/NodeRefs.py
4
+
5
+ """NodeRefs - References to associated files within the bundle."""
6
+
7
+ from dataclasses import dataclass
8
+ from typing import Any, Dict, Optional
9
+
10
+
11
+ @dataclass
12
+ class NodeRefs:
13
+ """References to associated files within the bundle.
14
+
15
+ All paths are relative to the bundle root.
16
+ """
17
+
18
+ encoding: str = "encoding.json"
19
+ theme: str = "theme.json"
20
+ data: Optional[str] = None # data/ directory or specific file
21
+ stats: str = "stats/stats.json"
22
+
23
+ def to_dict(self) -> Dict[str, str]:
24
+ result = {
25
+ "encoding": self.encoding,
26
+ "theme": self.theme,
27
+ "stats": self.stats,
28
+ }
29
+ if self.data:
30
+ result["data"] = self.data
31
+ return result
32
+
33
+ @classmethod
34
+ def from_dict(cls, data: Dict[str, Any]) -> "NodeRefs":
35
+ return cls(
36
+ encoding=data.get("encoding", "encoding.json"),
37
+ theme=data.get("theme", "theme.json"),
38
+ data=data.get("data"),
39
+ stats=data.get("stats", "stats/stats.json"),
40
+ )
41
+
42
+
43
+ __all__ = ["NodeRefs"]
44
+
45
+ # EOF
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: 2025-12-20
3
+ # File: /home/ywatanabe/proj/scitex-code/src/scitex/fsb/_dataclasses/SizeMM.py
4
+
5
+ """SizeMM - Physical size in millimeters."""
6
+
7
+ from dataclasses import dataclass
8
+ from typing import Any, Dict, Tuple
9
+
10
+
11
+ @dataclass
12
+ class SizeMM:
13
+ """Physical size in millimeters.
14
+
15
+ Used for print-ready figure dimensions.
16
+ """
17
+
18
+ width: float = 170.0 # Single column default
19
+ height: float = 120.0
20
+
21
+ def to_dict(self) -> Dict[str, float]:
22
+ return {"width": self.width, "height": self.height}
23
+
24
+ @classmethod
25
+ def from_dict(cls, data: Dict[str, Any]) -> "SizeMM":
26
+ return cls(
27
+ width=data.get("width", 170.0),
28
+ height=data.get("height", 120.0),
29
+ )
30
+
31
+ def to_inches(self) -> Tuple[float, float]:
32
+ """Convert to inches (for matplotlib)."""
33
+ return (self.width / 25.4, self.height / 25.4)
34
+
35
+
36
+ __all__ = ["SizeMM"]
37
+
38
+ # EOF
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: 2025-12-21
3
+ # File: /home/ywatanabe/proj/scitex-code/src/scitex/fts/_bundle/_dataclasses/__init__.py
4
+
5
+ """FTS Dataclasses - Core shared data models for bundles."""
6
+
7
+ # Core models (shared between fig and stats)
8
+ from ._Axes import Axes
9
+ from ._BBox import BBox
10
+ from ._ColumnDef import ColumnDef
11
+ from ._DataFormat import DataFormat
12
+ from ._DataInfo import DATA_INFO_VERSION, DataInfo
13
+ from ._DataSource import DataSource
14
+ from ._Node import Node, ShapeParams, TextContent
15
+ from ._NodeRefs import NodeRefs
16
+ from ._SizeMM import SizeMM
17
+
18
+ __all__ = [
19
+ # Core models
20
+ "BBox",
21
+ "SizeMM",
22
+ "Axes",
23
+ "NodeRefs",
24
+ "Node",
25
+ "TextContent",
26
+ "ShapeParams",
27
+ # Data Info
28
+ "DATA_INFO_VERSION",
29
+ "DataSource",
30
+ "DataFormat",
31
+ "ColumnDef",
32
+ "DataInfo",
33
+ ]
34
+
35
+ # EOF
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: 2025-12-20
3
+ # File: /home/ywatanabe/proj/scitex-code/src/scitex/fts/_bundle/_extractors/__init__.py
4
+
5
+ """Plot type extractors for FTS bundle creation.
6
+
7
+ Each extractor handles data extraction and encoding building for a specific plot type.
8
+ """
9
+
10
+ # Line plots
11
+ from ._extract_line import build_line_traces, extract_line_data
12
+
13
+ # Scatter plots (PathCollection)
14
+ from ._extract_scatter import build_scatter_traces, extract_scatter_data
15
+
16
+ # Bar charts (Rectangle patches)
17
+ from ._extract_bar import build_bar_traces, count_valid_bars, extract_bar_data
18
+
19
+ __all__ = [
20
+ # Line
21
+ "extract_line_data",
22
+ "build_line_traces",
23
+ # Scatter
24
+ "extract_scatter_data",
25
+ "build_scatter_traces",
26
+ # Bar
27
+ "extract_bar_data",
28
+ "count_valid_bars",
29
+ "build_bar_traces",
30
+ ]
31
+
32
+ # EOF
@@ -0,0 +1,131 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: 2025-12-20
3
+ # File: /home/ywatanabe/proj/scitex-code/src/scitex/fts/_bundle/_extractors/_extract_bar.py
4
+
5
+ """Bar chart data and encoding extraction."""
6
+
7
+ from typing import TYPE_CHECKING, Dict, List, Tuple
8
+
9
+ import numpy as np
10
+
11
+ if TYPE_CHECKING:
12
+ from matplotlib.axes import Axes
13
+
14
+ from ..._fig._dataclasses import TraceEncoding
15
+
16
+
17
+ def _is_valid_bar(rect, xlim, ylim, axes_width, axes_height) -> bool:
18
+ """Check if a Rectangle is a valid bar (not UI element).
19
+
20
+ Filters out legend boxes, axes frames, and other UI elements.
21
+ """
22
+ w = rect.get_width()
23
+ h = rect.get_height()
24
+ x = rect.get_x()
25
+ y = rect.get_y()
26
+
27
+ # Filter out: zero/negative dimensions
28
+ if w <= 0 or h == 0:
29
+ return False
30
+ # Filter out: full-width elements (likely axes frame)
31
+ if abs(w - axes_width) < 0.01 * axes_width:
32
+ return False
33
+ # Filter out: full-height elements
34
+ if abs(h - axes_height) < 0.01 * axes_height:
35
+ return False
36
+ # Filter out: very thin bars (likely spines)
37
+ if w < 0.01 * axes_width:
38
+ return False
39
+ # Filter out: elements outside data area
40
+ if x < xlim[0] - 0.1 * axes_width or x > xlim[1]:
41
+ return False
42
+ if y < ylim[0] - 0.1 * axes_height:
43
+ return False
44
+
45
+ return True
46
+
47
+
48
+ def extract_bar_data(ax: "Axes", ax_idx: int) -> Dict[str, np.ndarray]:
49
+ """Extract bar chart data from axes (Rectangle patches).
50
+
51
+ Args:
52
+ ax: Matplotlib axes
53
+ ax_idx: Axes index for column naming
54
+
55
+ Returns:
56
+ Dict mapping column names to data arrays
57
+ """
58
+ from matplotlib.patches import Rectangle
59
+
60
+ xlim = ax.get_xlim()
61
+ ylim = ax.get_ylim()
62
+ axes_width = xlim[1] - xlim[0]
63
+ axes_height = ylim[1] - ylim[0]
64
+
65
+ bars_x = []
66
+ bars_height = []
67
+
68
+ for child in ax.get_children():
69
+ if isinstance(child, Rectangle):
70
+ if _is_valid_bar(child, xlim, ylim, axes_width, axes_height):
71
+ w = child.get_width()
72
+ h = child.get_height()
73
+ x = child.get_x()
74
+ bars_x.append(x + w / 2)
75
+ bars_height.append(h)
76
+
77
+ data = {}
78
+ if len(bars_x) >= 2: # At least 2 bars to be a bar chart
79
+ data[f"ax{ax_idx}_bar_x"] = np.array(bars_x, dtype=float)
80
+ data[f"ax{ax_idx}_bar_height"] = np.array(bars_height, dtype=float)
81
+
82
+ return data
83
+
84
+
85
+ def count_valid_bars(ax: "Axes") -> int:
86
+ """Count valid bar rectangles in axes."""
87
+ from matplotlib.patches import Rectangle
88
+
89
+ xlim = ax.get_xlim()
90
+ ylim = ax.get_ylim()
91
+ axes_width = xlim[1] - xlim[0]
92
+ axes_height = ylim[1] - ylim[0]
93
+
94
+ count = 0
95
+ for child in ax.get_children():
96
+ if isinstance(child, Rectangle):
97
+ if _is_valid_bar(child, xlim, ylim, axes_width, axes_height):
98
+ count += 1
99
+
100
+ return count
101
+
102
+
103
+ def build_bar_traces(ax: "Axes", ax_idx: int) -> List["TraceEncoding"]:
104
+ """Build encoding traces for bar charts.
105
+
106
+ Args:
107
+ ax: Matplotlib axes
108
+ ax_idx: Axes index for trace ID
109
+
110
+ Returns:
111
+ List of TraceEncoding objects
112
+ """
113
+ from ..._fig._dataclasses import ChannelEncoding, TraceEncoding
114
+
115
+ traces = []
116
+ bar_count = count_valid_bars(ax)
117
+
118
+ if bar_count >= 2:
119
+ trace = TraceEncoding(
120
+ trace_id=f"bar_{ax_idx}",
121
+ x=ChannelEncoding(column=f"ax{ax_idx}_bar_x"),
122
+ y=ChannelEncoding(column=f"ax{ax_idx}_bar_height"),
123
+ )
124
+ traces.append(trace)
125
+
126
+ return traces
127
+
128
+
129
+ __all__ = ["extract_bar_data", "count_valid_bars", "build_bar_traces"]
130
+
131
+ # EOF