scitex 2.8.1__py3-none-any.whl → 2.10.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (415) hide show
  1. scitex/__init__.py +15 -7
  2. scitex/__version__.py +1 -2
  3. scitex/_install_guide.py +250 -0
  4. scitex/_optional_deps.py +206 -39
  5. scitex/ai/_gen_ai/_Groq.py +2 -4
  6. scitex/ai/_gen_ai/_OpenAI.py +5 -2
  7. scitex/ai/_gen_ai/_Perplexity.py +20 -6
  8. scitex/audio/__init__.py +24 -15
  9. scitex/audio/_cross_process_lock.py +139 -0
  10. scitex/audio/_mcp_handlers.py +256 -0
  11. scitex/audio/_mcp_tool_schemas.py +203 -0
  12. scitex/audio/engines/elevenlabs_engine.py +5 -2
  13. scitex/audio/mcp_server.py +98 -457
  14. scitex/bridge/__init__.py +30 -19
  15. scitex/bridge/_figrecipe.py +245 -0
  16. scitex/bridge/_helpers.py +2 -1
  17. scitex/bridge/_plt_vis.py +23 -10
  18. scitex/bridge/_stats_plt.py +18 -5
  19. scitex/bridge/_stats_vis.py +16 -2
  20. scitex/browser/__init__.py +84 -44
  21. scitex/browser/automation/__init__.py +5 -1
  22. scitex/browser/core/BrowserMixin.py +17 -4
  23. scitex/browser/core/__init__.py +11 -2
  24. scitex/browser/remote/CaptchaHandler.py +1 -1
  25. scitex/browser/remote/ZenRowsAPIClient.py +1 -1
  26. scitex/capture/grid.py +487 -0
  27. scitex/capture/mcp_handlers.py +401 -0
  28. scitex/capture/mcp_tool_defs.py +192 -0
  29. scitex/capture/mcp_tools.py +241 -0
  30. scitex/capture/mcp_utils.py +30 -0
  31. scitex/cli/convert.py +421 -0
  32. scitex/cli/main.py +6 -4
  33. scitex/datetime/__init__.py +46 -0
  34. scitex/datetime/_linspace.py +100 -0
  35. scitex/datetime/_normalize_timestamp.py +306 -0
  36. scitex/db/_delete_duplicates.py +4 -4
  37. scitex/db/_sqlite3/_delete_duplicates.py +11 -2
  38. scitex/dev/plt/__init__.py +61 -62
  39. scitex/dev/plt/demo_plotters/__init__.py +0 -0
  40. scitex/dev/plt/demo_plotters/plot_mpl_axhline.py +28 -0
  41. scitex/dev/plt/demo_plotters/plot_mpl_axhspan.py +28 -0
  42. scitex/dev/plt/demo_plotters/plot_mpl_axvline.py +28 -0
  43. scitex/dev/plt/demo_plotters/plot_mpl_axvspan.py +28 -0
  44. scitex/dev/plt/demo_plotters/plot_mpl_bar.py +29 -0
  45. scitex/dev/plt/demo_plotters/plot_mpl_barh.py +29 -0
  46. scitex/dev/plt/demo_plotters/plot_mpl_boxplot.py +28 -0
  47. scitex/dev/plt/demo_plotters/plot_mpl_contour.py +31 -0
  48. scitex/dev/plt/demo_plotters/plot_mpl_contourf.py +31 -0
  49. scitex/dev/plt/demo_plotters/plot_mpl_errorbar.py +30 -0
  50. scitex/dev/plt/demo_plotters/plot_mpl_eventplot.py +28 -0
  51. scitex/dev/plt/demo_plotters/plot_mpl_fill.py +30 -0
  52. scitex/dev/plt/demo_plotters/plot_mpl_fill_between.py +31 -0
  53. scitex/dev/plt/demo_plotters/plot_mpl_hexbin.py +28 -0
  54. scitex/dev/plt/demo_plotters/plot_mpl_hist.py +28 -0
  55. scitex/dev/plt/demo_plotters/plot_mpl_hist2d.py +28 -0
  56. scitex/dev/plt/demo_plotters/plot_mpl_imshow.py +29 -0
  57. scitex/dev/plt/demo_plotters/plot_mpl_pcolormesh.py +31 -0
  58. scitex/dev/plt/demo_plotters/plot_mpl_pie.py +29 -0
  59. scitex/dev/plt/demo_plotters/plot_mpl_plot.py +29 -0
  60. scitex/dev/plt/demo_plotters/plot_mpl_quiver.py +31 -0
  61. scitex/dev/plt/demo_plotters/plot_mpl_scatter.py +28 -0
  62. scitex/dev/plt/demo_plotters/plot_mpl_stackplot.py +31 -0
  63. scitex/dev/plt/demo_plotters/plot_mpl_stem.py +29 -0
  64. scitex/dev/plt/demo_plotters/plot_mpl_step.py +29 -0
  65. scitex/dev/plt/demo_plotters/plot_mpl_violinplot.py +28 -0
  66. scitex/dev/plt/demo_plotters/plot_sns_barplot.py +29 -0
  67. scitex/dev/plt/demo_plotters/plot_sns_boxplot.py +29 -0
  68. scitex/dev/plt/demo_plotters/plot_sns_heatmap.py +28 -0
  69. scitex/dev/plt/demo_plotters/plot_sns_histplot.py +29 -0
  70. scitex/dev/plt/demo_plotters/plot_sns_kdeplot.py +29 -0
  71. scitex/dev/plt/demo_plotters/plot_sns_lineplot.py +31 -0
  72. scitex/dev/plt/demo_plotters/plot_sns_scatterplot.py +29 -0
  73. scitex/dev/plt/demo_plotters/plot_sns_stripplot.py +29 -0
  74. scitex/dev/plt/demo_plotters/plot_sns_swarmplot.py +29 -0
  75. scitex/dev/plt/demo_plotters/plot_sns_violinplot.py +29 -0
  76. scitex/dev/plt/demo_plotters/plot_stx_bar.py +29 -0
  77. scitex/dev/plt/demo_plotters/plot_stx_barh.py +29 -0
  78. scitex/dev/plt/demo_plotters/plot_stx_box.py +28 -0
  79. scitex/dev/plt/demo_plotters/plot_stx_boxplot.py +28 -0
  80. scitex/dev/plt/demo_plotters/plot_stx_conf_mat.py +28 -0
  81. scitex/dev/plt/demo_plotters/plot_stx_contour.py +31 -0
  82. scitex/dev/plt/demo_plotters/plot_stx_ecdf.py +28 -0
  83. scitex/dev/plt/demo_plotters/plot_stx_errorbar.py +30 -0
  84. scitex/dev/plt/demo_plotters/plot_stx_fill_between.py +31 -0
  85. scitex/dev/plt/demo_plotters/plot_stx_fillv.py +28 -0
  86. scitex/dev/plt/demo_plotters/plot_stx_heatmap.py +28 -0
  87. scitex/dev/plt/demo_plotters/plot_stx_image.py +28 -0
  88. scitex/dev/plt/demo_plotters/plot_stx_imshow.py +28 -0
  89. scitex/dev/plt/demo_plotters/plot_stx_joyplot.py +28 -0
  90. scitex/dev/plt/demo_plotters/plot_stx_kde.py +28 -0
  91. scitex/dev/plt/demo_plotters/plot_stx_line.py +28 -0
  92. scitex/dev/plt/demo_plotters/plot_stx_mean_ci.py +28 -0
  93. scitex/dev/plt/demo_plotters/plot_stx_mean_std.py +28 -0
  94. scitex/dev/plt/demo_plotters/plot_stx_median_iqr.py +28 -0
  95. scitex/dev/plt/demo_plotters/plot_stx_raster.py +28 -0
  96. scitex/dev/plt/demo_plotters/plot_stx_rectangle.py +28 -0
  97. scitex/dev/plt/demo_plotters/plot_stx_scatter.py +29 -0
  98. scitex/dev/plt/demo_plotters/plot_stx_shaded_line.py +29 -0
  99. scitex/dev/plt/demo_plotters/plot_stx_violin.py +28 -0
  100. scitex/dev/plt/demo_plotters/plot_stx_violinplot.py +28 -0
  101. scitex/dev/plt/mpl/get_dir_ax.py +46 -0
  102. scitex/dev/plt/mpl/get_signatures.py +176 -0
  103. scitex/dev/plt/mpl/get_signatures_details.py +522 -0
  104. scitex/dict/_pop_keys.py +1 -7
  105. scitex/dsp/__init__.py +15 -10
  106. scitex/dsp/add_noise.py +5 -2
  107. scitex/dsp/example.py +35 -22
  108. scitex/dsp/filt.py +8 -3
  109. scitex/dsp/reference.py +3 -2
  110. scitex/dsp/utils/__init__.py +2 -1
  111. scitex/dsp/utils/_differential_bandpass_filters.py +14 -4
  112. scitex/dt/__init__.py +39 -2
  113. scitex/errors.py +82 -521
  114. scitex/fig/__init__.py +4 -4
  115. scitex/fig/editor/edit/panel_loader.py +1 -1
  116. scitex/fig/io/_bundle.py +7 -7
  117. scitex/fts/README.md +262 -0
  118. scitex/fts/TODO.md +66 -0
  119. scitex/fts/__init__.py +90 -0
  120. scitex/fts/_bundle/README_IN_BUNDLE.md +102 -0
  121. scitex/fts/_bundle/_FTS.py +657 -0
  122. scitex/fts/_bundle/__init__.py +38 -0
  123. scitex/fts/_bundle/_children.py +216 -0
  124. scitex/fts/_bundle/_conversion/__init__.py +15 -0
  125. scitex/fts/_bundle/_conversion/_bundle2dict.py +44 -0
  126. scitex/fts/_bundle/_conversion/_dict2bundle.py +50 -0
  127. scitex/fts/_bundle/_dataclasses/_Axes.py +57 -0
  128. scitex/fts/_bundle/_dataclasses/_BBox.py +54 -0
  129. scitex/fts/_bundle/_dataclasses/_ColumnDef.py +72 -0
  130. scitex/fts/_bundle/_dataclasses/_DataFormat.py +40 -0
  131. scitex/fts/_bundle/_dataclasses/_DataInfo.py +135 -0
  132. scitex/fts/_bundle/_dataclasses/_DataSource.py +44 -0
  133. scitex/fts/_bundle/_dataclasses/_Node.py +319 -0
  134. scitex/fts/_bundle/_dataclasses/_NodeRefs.py +45 -0
  135. scitex/fts/_bundle/_dataclasses/_SizeMM.py +38 -0
  136. scitex/fts/_bundle/_dataclasses/__init__.py +35 -0
  137. scitex/fts/_bundle/_extractors/__init__.py +32 -0
  138. scitex/fts/_bundle/_extractors/_extract_bar.py +131 -0
  139. scitex/fts/_bundle/_extractors/_extract_line.py +71 -0
  140. scitex/fts/_bundle/_extractors/_extract_scatter.py +79 -0
  141. scitex/fts/_bundle/_loader.py +134 -0
  142. scitex/fts/_bundle/_mpl_helpers.py +389 -0
  143. scitex/fts/_bundle/_saver.py +269 -0
  144. scitex/fts/_bundle/_storage.py +200 -0
  145. scitex/fts/_bundle/_utils/__init__.py +55 -0
  146. scitex/fts/_bundle/_utils/_const.py +26 -0
  147. scitex/fts/_bundle/_utils/_errors.py +73 -0
  148. scitex/fts/_bundle/_utils/_generate.py +21 -0
  149. scitex/fts/_bundle/_utils/_types.py +76 -0
  150. scitex/fts/_bundle/_validation.py +434 -0
  151. scitex/fts/_bundle/_zipbundle.py +165 -0
  152. scitex/fts/_fig/__init__.py +22 -0
  153. scitex/fts/_fig/_backend/__init__.py +53 -0
  154. scitex/fts/_fig/_backend/_export.py +165 -0
  155. scitex/fts/_fig/_backend/_parser.py +188 -0
  156. scitex/fts/_fig/_backend/_render.py +538 -0
  157. scitex/fts/_fig/_composite.py +345 -0
  158. scitex/fts/_fig/_dataclasses/_ChannelEncoding.py +46 -0
  159. scitex/fts/_fig/_dataclasses/_Encoding.py +82 -0
  160. scitex/fts/_fig/_dataclasses/_Theme.py +441 -0
  161. scitex/fts/_fig/_dataclasses/_TraceEncoding.py +52 -0
  162. scitex/fts/_fig/_dataclasses/__init__.py +47 -0
  163. scitex/fts/_fig/_editor/__init__.py +14 -0
  164. scitex/fts/_fig/_editor/_cui/__init__.py +33 -0
  165. scitex/fts/_fig/_editor/_cui/_backend_detector.py +39 -0
  166. scitex/fts/_fig/_editor/_cui/_bundle_resolver.py +366 -0
  167. scitex/fts/_fig/_editor/_cui/_editor_launcher.py +175 -0
  168. scitex/fts/_fig/_editor/_cui/_manual_handler.py +52 -0
  169. scitex/fts/_fig/_editor/_cui/_panel_loader.py +246 -0
  170. scitex/fts/_fig/_editor/_cui/_path_resolver.py +66 -0
  171. scitex/fts/_fig/_editor/_defaults.py +300 -0
  172. scitex/fts/_fig/_editor/_gui/__init__.py +11 -0
  173. scitex/fts/_fig/_editor/_gui/_flask_editor/__init__.py +20 -0
  174. scitex/fts/_fig/_editor/_gui/_flask_editor/_bbox.py +1339 -0
  175. scitex/fts/_fig/_editor/_gui/_flask_editor/_core.py +1688 -0
  176. scitex/fts/_fig/_editor/_gui/_flask_editor/_plotter.py +664 -0
  177. scitex/fts/_fig/_editor/_gui/_flask_editor/_renderer.py +853 -0
  178. scitex/fts/_fig/_editor/_gui/_flask_editor/_utils.py +79 -0
  179. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/reset.css +41 -0
  180. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/typography.css +16 -0
  181. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/variables.css +85 -0
  182. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/buttons.css +217 -0
  183. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/context-menu.css +93 -0
  184. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/dropdown.css +57 -0
  185. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/forms.css +112 -0
  186. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/modal.css +59 -0
  187. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/sections.css +212 -0
  188. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/canvas.css +176 -0
  189. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/element-inspector.css +190 -0
  190. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/loading.css +59 -0
  191. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/overlay.css +45 -0
  192. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/panel-grid.css +95 -0
  193. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/selection.css +101 -0
  194. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/statistics.css +138 -0
  195. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/index.css +31 -0
  196. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/container.css +7 -0
  197. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/controls.css +56 -0
  198. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/preview.css +78 -0
  199. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/axis.js +314 -0
  200. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/basic.js +107 -0
  201. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/distribute.js +54 -0
  202. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/canvas.js +172 -0
  203. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/dragging.js +258 -0
  204. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/resize.js +48 -0
  205. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/selection.js +71 -0
  206. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/api.js +288 -0
  207. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/state.js +143 -0
  208. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/utils.js +245 -0
  209. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/dev/element-inspector.js +992 -0
  210. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/bbox.js +339 -0
  211. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/element-drag.js +286 -0
  212. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/overlay.js +371 -0
  213. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/preview.js +293 -0
  214. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/main.js +426 -0
  215. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/shortcuts/context-menu.js +152 -0
  216. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/shortcuts/keyboard.js +265 -0
  217. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/controls.js +184 -0
  218. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/download.js +57 -0
  219. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/help.js +100 -0
  220. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/theme.js +34 -0
  221. scitex/fts/_fig/_editor/_gui/_flask_editor/templates/__init__.py +124 -0
  222. scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_html.py +851 -0
  223. scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_scripts.py +4932 -0
  224. scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_styles.py +1657 -0
  225. scitex/fts/_fig/_editor/_gui/_flask_editor.py +36 -0
  226. scitex/fts/_fig/_models/_Annotations.py +115 -0
  227. scitex/fts/_fig/_models/_Axes.py +152 -0
  228. scitex/fts/_fig/_models/_Figure.py +138 -0
  229. scitex/fts/_fig/_models/_Guides.py +104 -0
  230. scitex/fts/_fig/_models/_Plot.py +123 -0
  231. scitex/fts/_fig/_models/_Styles.py +245 -0
  232. scitex/fts/_fig/_models/__init__.py +80 -0
  233. scitex/fts/_fig/_models/_plot_types/__init__.py +156 -0
  234. scitex/fts/_fig/_models/_plot_types/_bar.py +43 -0
  235. scitex/fts/_fig/_models/_plot_types/_box.py +38 -0
  236. scitex/fts/_fig/_models/_plot_types/_distribution.py +36 -0
  237. scitex/fts/_fig/_models/_plot_types/_errorbar.py +60 -0
  238. scitex/fts/_fig/_models/_plot_types/_histogram.py +30 -0
  239. scitex/fts/_fig/_models/_plot_types/_image.py +61 -0
  240. scitex/fts/_fig/_models/_plot_types/_line.py +57 -0
  241. scitex/fts/_fig/_models/_plot_types/_scatter.py +30 -0
  242. scitex/fts/_fig/_models/_plot_types/_seaborn.py +121 -0
  243. scitex/fts/_fig/_models/_plot_types/_violin.py +36 -0
  244. scitex/fts/_fig/_utils/__init__.py +129 -0
  245. scitex/fts/_fig/_utils/_auto_layout.py +127 -0
  246. scitex/fts/_fig/_utils/_calc_bounds.py +111 -0
  247. scitex/fts/_fig/_utils/_const_sizes.py +48 -0
  248. scitex/fts/_fig/_utils/_convert_coords.py +77 -0
  249. scitex/fts/_fig/_utils/_get_template.py +178 -0
  250. scitex/fts/_fig/_utils/_normalize.py +73 -0
  251. scitex/fts/_fig/_utils/_plot_layout.py +397 -0
  252. scitex/fts/_fig/_utils/_validate.py +197 -0
  253. scitex/fts/_kinds/__init__.py +45 -0
  254. scitex/fts/_kinds/_figure/__init__.py +19 -0
  255. scitex/fts/_kinds/_figure/_composite.py +345 -0
  256. scitex/fts/_kinds/_plot/__init__.py +25 -0
  257. scitex/fts/_kinds/_plot/_backend/__init__.py +53 -0
  258. scitex/fts/_kinds/_plot/_backend/_export.py +165 -0
  259. scitex/fts/_kinds/_plot/_backend/_parser.py +188 -0
  260. scitex/fts/_kinds/_plot/_backend/_render.py +538 -0
  261. scitex/fts/_kinds/_plot/_dataclasses/_ChannelEncoding.py +46 -0
  262. scitex/fts/_kinds/_plot/_dataclasses/_Encoding.py +82 -0
  263. scitex/fts/_kinds/_plot/_dataclasses/_Theme.py +441 -0
  264. scitex/fts/_kinds/_plot/_dataclasses/_TraceEncoding.py +52 -0
  265. scitex/fts/_kinds/_plot/_dataclasses/__init__.py +47 -0
  266. scitex/fts/_kinds/_plot/_models/_Annotations.py +115 -0
  267. scitex/fts/_kinds/_plot/_models/_Axes.py +152 -0
  268. scitex/fts/_kinds/_plot/_models/_Figure.py +138 -0
  269. scitex/fts/_kinds/_plot/_models/_Guides.py +104 -0
  270. scitex/fts/_kinds/_plot/_models/_Plot.py +123 -0
  271. scitex/fts/_kinds/_plot/_models/_Styles.py +245 -0
  272. scitex/fts/_kinds/_plot/_models/__init__.py +80 -0
  273. scitex/fts/_kinds/_plot/_models/_plot_types/__init__.py +156 -0
  274. scitex/fts/_kinds/_plot/_models/_plot_types/_bar.py +43 -0
  275. scitex/fts/_kinds/_plot/_models/_plot_types/_box.py +38 -0
  276. scitex/fts/_kinds/_plot/_models/_plot_types/_distribution.py +36 -0
  277. scitex/fts/_kinds/_plot/_models/_plot_types/_errorbar.py +60 -0
  278. scitex/fts/_kinds/_plot/_models/_plot_types/_histogram.py +30 -0
  279. scitex/fts/_kinds/_plot/_models/_plot_types/_image.py +61 -0
  280. scitex/fts/_kinds/_plot/_models/_plot_types/_line.py +57 -0
  281. scitex/fts/_kinds/_plot/_models/_plot_types/_scatter.py +30 -0
  282. scitex/fts/_kinds/_plot/_models/_plot_types/_seaborn.py +121 -0
  283. scitex/fts/_kinds/_plot/_models/_plot_types/_violin.py +36 -0
  284. scitex/fts/_kinds/_plot/_utils/__init__.py +129 -0
  285. scitex/fts/_kinds/_plot/_utils/_auto_layout.py +127 -0
  286. scitex/fts/_kinds/_plot/_utils/_calc_bounds.py +111 -0
  287. scitex/fts/_kinds/_plot/_utils/_const_sizes.py +48 -0
  288. scitex/fts/_kinds/_plot/_utils/_convert_coords.py +77 -0
  289. scitex/fts/_kinds/_plot/_utils/_get_template.py +178 -0
  290. scitex/fts/_kinds/_plot/_utils/_normalize.py +73 -0
  291. scitex/fts/_kinds/_plot/_utils/_plot_layout.py +397 -0
  292. scitex/fts/_kinds/_plot/_utils/_validate.py +197 -0
  293. scitex/fts/_kinds/_shape/__init__.py +141 -0
  294. scitex/fts/_kinds/_stats/__init__.py +56 -0
  295. scitex/fts/_kinds/_stats/_dataclasses/_Stats.py +423 -0
  296. scitex/fts/_kinds/_stats/_dataclasses/__init__.py +48 -0
  297. scitex/fts/_kinds/_table/__init__.py +72 -0
  298. scitex/fts/_kinds/_table/_latex/__init__.py +93 -0
  299. scitex/fts/_kinds/_table/_latex/_editor/__init__.py +11 -0
  300. scitex/fts/_kinds/_table/_latex/_editor/_app.py +725 -0
  301. scitex/fts/_kinds/_table/_latex/_export.py +279 -0
  302. scitex/fts/_kinds/_table/_latex/_figure_exporter.py +153 -0
  303. scitex/fts/_kinds/_table/_latex/_stats_formatter.py +274 -0
  304. scitex/fts/_kinds/_table/_latex/_table_exporter.py +362 -0
  305. scitex/fts/_kinds/_table/_latex/_utils.py +369 -0
  306. scitex/fts/_kinds/_table/_latex/_validator.py +445 -0
  307. scitex/fts/_kinds/_text/__init__.py +77 -0
  308. scitex/fts/_schemas/data_info.schema.json +75 -0
  309. scitex/fts/_schemas/encoding.schema.json +90 -0
  310. scitex/fts/_schemas/node.schema.json +145 -0
  311. scitex/fts/_schemas/render_manifest.schema.json +62 -0
  312. scitex/fts/_schemas/stats.schema.json +132 -0
  313. scitex/fts/_schemas/theme.schema.json +141 -0
  314. scitex/fts/_stats/__init__.py +48 -0
  315. scitex/fts/_stats/_dataclasses/_Stats.py +423 -0
  316. scitex/fts/_stats/_dataclasses/__init__.py +48 -0
  317. scitex/fts/_tables/__init__.py +65 -0
  318. scitex/fts/_tables/_latex/__init__.py +93 -0
  319. scitex/fts/_tables/_latex/_editor/__init__.py +11 -0
  320. scitex/fts/_tables/_latex/_editor/_app.py +725 -0
  321. scitex/fts/_tables/_latex/_export.py +279 -0
  322. scitex/fts/_tables/_latex/_figure_exporter.py +153 -0
  323. scitex/fts/_tables/_latex/_stats_formatter.py +274 -0
  324. scitex/fts/_tables/_latex/_table_exporter.py +362 -0
  325. scitex/fts/_tables/_latex/_utils.py +369 -0
  326. scitex/fts/_tables/_latex/_validator.py +445 -0
  327. scitex/gen/__init__.py +66 -25
  328. scitex/gen/misc.py +28 -0
  329. scitex/io/__init__.py +47 -32
  330. scitex/io/_load.py +87 -36
  331. scitex/io/_load_modules/__init__.py +10 -7
  332. scitex/io/_load_modules/_pandas.py +6 -1
  333. scitex/io/_save.py +299 -1556
  334. scitex/io/_save_modules/__init__.py +76 -19
  335. scitex/io/_save_modules/_figure_utils.py +90 -0
  336. scitex/io/_save_modules/_image_csv.py +497 -0
  337. scitex/io/_save_modules/_legends.py +91 -0
  338. scitex/io/_save_modules/_pltz_bundle.py +356 -0
  339. scitex/io/_save_modules/_pltz_stx.py +536 -0
  340. scitex/io/_save_modules/_stx_bundle.py +104 -0
  341. scitex/io/_save_modules/_symlink.py +96 -0
  342. scitex/io/_save_modules/_yaml.py +1 -1
  343. scitex/io/_save_modules/_zarr.py +64 -18
  344. scitex/io/bundle/README.md +212 -0
  345. scitex/io/bundle/__init__.py +110 -0
  346. scitex/io/{_bundle.py → bundle/_core.py} +168 -97
  347. scitex/io/bundle/_nested.py +713 -0
  348. scitex/io/bundle/_types.py +74 -0
  349. scitex/io/{_zip_bundle.py → bundle/_zip.py} +93 -45
  350. scitex/io/utils/h5_to_zarr.py +1 -1
  351. scitex/logging/__init__.py +108 -13
  352. scitex/logging/_errors.py +508 -0
  353. scitex/logging/_formatters.py +30 -6
  354. scitex/logging/_warnings.py +261 -0
  355. scitex/plt/__init__.py +4 -1
  356. scitex/plt/_figrecipe.py +236 -0
  357. scitex/plt/_subplots/_AxisWrapper.py +6 -0
  358. scitex/plt/_subplots/_AxisWrapperMixins/_UnitAwareMixin.py +112 -1
  359. scitex/plt/_subplots/_FigWrapper.py +15 -0
  360. scitex/plt/_subplots/_SubplotsWrapper.py +125 -489
  361. scitex/plt/_subplots/_export_as_csv.py +11 -0
  362. scitex/plt/_subplots/_export_as_csv_formatters/__init__.py +2 -0
  363. scitex/plt/_subplots/_export_as_csv_formatters/_format_pcolormesh.py +66 -0
  364. scitex/plt/_subplots/_export_as_csv_formatters/_format_stackplot.py +62 -0
  365. scitex/plt/_subplots/_export_as_csv_formatters/test_formatters.py +208 -0
  366. scitex/plt/_subplots/_fonts.py +71 -0
  367. scitex/plt/_subplots/_mm_layout.py +282 -0
  368. scitex/plt/gallery/__init__.py +99 -2
  369. scitex/plt/styles/_plot_postprocess.py +3 -1
  370. scitex/plt/utils/_configure_mpl.py +16 -19
  371. scitex/repro/_RandomStateManager.py +13 -8
  372. scitex/resource/__init__.py +19 -1
  373. scitex/resource/_utils/_get_env_info.py +13 -25
  374. scitex/schema/__init__.py +149 -160
  375. scitex/schema/_encoding.py +273 -0
  376. scitex/schema/_figure_elements.py +406 -0
  377. scitex/schema/_theme.py +360 -0
  378. scitex/schema/_validation.py +0 -98
  379. scitex/scholar/__init__.py +56 -14
  380. scitex/scholar/auth/ScholarAuthManager.py +1 -1
  381. scitex/scholar/auth/__init__.py +11 -2
  382. scitex/scholar/auth/providers/BaseAuthenticator.py +1 -1
  383. scitex/scholar/auth/providers/EZProxyAuthenticator.py +1 -1
  384. scitex/scholar/auth/providers/OpenAthensAuthenticator.py +1 -1
  385. scitex/scholar/auth/providers/ShibbolethAuthenticator.py +1 -1
  386. scitex/scholar/config/ScholarConfig.py +1 -1
  387. scitex/scholar/core/Scholar.py +1 -1
  388. scitex/session/_decorator.py +18 -16
  389. scitex/session/_lifecycle.py +9 -11
  390. scitex/session/template.py +9 -8
  391. scitex/sh/test_sh.py +72 -0
  392. scitex/sh/test_sh_simple.py +61 -0
  393. scitex/stats/__init__.py +221 -97
  394. scitex/stats/_schema.py +21 -22
  395. scitex/stats/descriptive/_circular.py +212 -351
  396. scitex/stats/descriptive/_describe.py +81 -132
  397. scitex/stats/descriptive/_nan.py +205 -433
  398. scitex/stats/descriptive/_real.py +127 -141
  399. scitex/str/_format_plot_text.py +5 -5
  400. scitex/str/_latex.py +26 -84
  401. scitex/str/_latex_fallback.py +53 -47
  402. scitex/web/_search_pubmed.py +5 -4
  403. scitex/writer/tests/test_diff_between.py +451 -0
  404. scitex/writer/tests/test_document_section.py +311 -0
  405. scitex/writer/tests/test_document_workflow.py +393 -0
  406. scitex/writer/tests/test_writer.py +361 -0
  407. scitex/writer/tests/test_writer_integration.py +303 -0
  408. {scitex-2.8.1.dist-info → scitex-2.10.0.dist-info}/METADATA +364 -181
  409. {scitex-2.8.1.dist-info → scitex-2.10.0.dist-info}/RECORD +412 -97
  410. scitex/scholar/docs/to_claude/guidelines/examples/mgmt/ARCHITECTURE_EXAMPLE.md +0 -905
  411. scitex/scholar/docs/to_claude/guidelines/examples/mgmt/BULLETIN_BOARD_EXAMPLE.md +0 -99
  412. scitex/scholar/docs/to_claude/guidelines/examples/mgmt/PROJECT_DESCRIPTION_EXAMPLE.md +0 -96
  413. {scitex-2.8.1.dist-info → scitex-2.10.0.dist-info}/WHEEL +0 -0
  414. {scitex-2.8.1.dist-info → scitex-2.10.0.dist-info}/entry_points.txt +0 -0
  415. {scitex-2.8.1.dist-info → scitex-2.10.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,397 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: 2025-12-20
3
+ # File: /home/ywatanabe/proj/scitex-code/src/scitex/fsb/_fig/_utils/_plot_layout.py
4
+
5
+ """Blueprint-style visualization for FTS layout and coordinate system.
6
+
7
+ Provides architectural drawing style visualizations with:
8
+ - Canvas boundaries with dimension annotations
9
+ - Element bounding boxes with labels
10
+ - Rulers (horizontal and vertical)
11
+ - Grid lines
12
+ - Before/after comparison for auto-crop
13
+
14
+ Usage:
15
+ from scitex.fts._fig._utils import plot_layout, plot_auto_crop_comparison
16
+
17
+ # Single layout visualization
18
+ fig, ax = plot_layout(elements, canvas_size, title="My Figure")
19
+
20
+ # Before/after auto-crop comparison
21
+ fig = plot_auto_crop_comparison(elements_before, elements_after,
22
+ size_before, size_after)
23
+ """
24
+
25
+ from typing import Any, Dict, List, Optional, Tuple
26
+
27
+ import matplotlib.patches as mpatches
28
+ import matplotlib.pyplot as plt
29
+ from matplotlib.axes import Axes
30
+ from matplotlib.figure import Figure
31
+
32
+ from ._calc_bounds import element_bounds
33
+ from ._normalize import normalize_size
34
+
35
+ __all__ = [
36
+ "plot_layout",
37
+ "plot_auto_crop_comparison",
38
+ "BLUEPRINT_STYLE",
39
+ ]
40
+
41
+ # Blueprint color scheme
42
+ BLUEPRINT_STYLE = {
43
+ "bg_color": "#1a2744", # Dark blue background
44
+ "grid_color": "#2a3f5f", # Subtle grid
45
+ "canvas_color": "#ffffff", # White canvas
46
+ "canvas_edge": "#4a90d9", # Blue canvas border
47
+ "element_fill": "#e8f4fc", # Light blue element fill
48
+ "element_edge": "#2171b5", # Blue element border
49
+ "ruler_color": "#ff6b35", # Orange rulers
50
+ "text_color": "#333333", # Dark text
51
+ "dimension_color": "#d62728", # Red dimensions
52
+ "origin_color": "#2ca02c", # Green origin marker
53
+ }
54
+
55
+
56
+ def plot_layout(
57
+ elements: List[Dict[str, Any]],
58
+ canvas_size: Dict[str, float],
59
+ title: str = "Layout",
60
+ ax: Optional[Axes] = None,
61
+ show_rulers: bool = True,
62
+ show_grid: bool = True,
63
+ show_dimensions: bool = True,
64
+ show_origin: bool = True,
65
+ style: Optional[Dict[str, str]] = None,
66
+ ) -> Tuple[Figure, Axes]:
67
+ """Plot layout with blueprint-style visualization.
68
+
69
+ Args:
70
+ elements: List of element specifications
71
+ canvas_size: Canvas size {"width_mm", "height_mm"}
72
+ title: Plot title
73
+ ax: Existing axes to plot on (creates new if None)
74
+ show_rulers: Show ruler markings
75
+ show_grid: Show background grid
76
+ show_dimensions: Show dimension annotations
77
+ show_origin: Show origin marker
78
+ style: Custom style dict (uses BLUEPRINT_STYLE if None)
79
+
80
+ Returns:
81
+ Tuple of (Figure, Axes)
82
+ """
83
+ s = style or BLUEPRINT_STYLE
84
+ size = normalize_size(canvas_size)
85
+ w, h = size["width_mm"], size["height_mm"]
86
+
87
+ # Create figure if needed
88
+ if ax is None:
89
+ # Add space for rulers
90
+ ruler_margin = 15 if show_rulers else 5
91
+ fig_w = (w + ruler_margin * 2) / 25.4 # Convert mm to inches
92
+ fig_h = (h + ruler_margin * 2) / 25.4
93
+ fig, ax = plt.subplots(figsize=(fig_w * 1.5, fig_h * 1.5))
94
+ else:
95
+ fig = ax.figure
96
+
97
+ # Set background
98
+ ax.set_facecolor(s["bg_color"])
99
+
100
+ # Draw grid
101
+ if show_grid:
102
+ _draw_grid(ax, w, h, s)
103
+
104
+ # Draw canvas
105
+ _draw_canvas(ax, w, h, s)
106
+
107
+ # Draw origin marker
108
+ if show_origin:
109
+ _draw_origin(ax, s)
110
+
111
+ # Draw elements
112
+ for i, elem in enumerate(elements):
113
+ _draw_element(ax, elem, i, s, show_dimensions)
114
+
115
+ # Draw rulers
116
+ if show_rulers:
117
+ _draw_rulers(ax, w, h, s)
118
+
119
+ # Draw canvas dimensions
120
+ if show_dimensions:
121
+ _draw_canvas_dimensions(ax, w, h, s)
122
+
123
+ # Set axis properties
124
+ margin = 20 if show_rulers else 5
125
+ ax.set_xlim(-margin, w + margin)
126
+ ax.set_ylim(h + margin, -margin) # Inverted Y (origin at top-left)
127
+ ax.set_aspect("equal")
128
+ ax.set_title(title, fontsize=12, fontweight="bold", color=s["text_color"])
129
+ ax.axis("off")
130
+
131
+ return fig, ax
132
+
133
+
134
+ def plot_auto_crop_comparison(
135
+ elements_before: List[Dict[str, Any]],
136
+ elements_after: List[Dict[str, Any]],
137
+ size_before: Dict[str, float],
138
+ size_after: Dict[str, float],
139
+ title: str = "Auto-Crop Comparison",
140
+ style: Optional[Dict[str, str]] = None,
141
+ ) -> Figure:
142
+ """Plot before/after comparison for auto-crop.
143
+
144
+ Args:
145
+ elements_before: Elements before auto-crop
146
+ elements_after: Elements after auto-crop
147
+ size_before: Canvas size before
148
+ size_after: Canvas size after
149
+ title: Overall title
150
+ style: Custom style dict
151
+
152
+ Returns:
153
+ Figure with side-by-side comparison
154
+ """
155
+ s = style or BLUEPRINT_STYLE
156
+
157
+ fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(16, 8))
158
+ fig.suptitle(title, fontsize=14, fontweight="bold")
159
+
160
+ # Before
161
+ plot_layout(
162
+ elements_before,
163
+ size_before,
164
+ title="Before Auto-Crop",
165
+ ax=ax1,
166
+ style=s,
167
+ )
168
+
169
+ # After
170
+ plot_layout(
171
+ elements_after,
172
+ size_after,
173
+ title="After Auto-Crop",
174
+ ax=ax2,
175
+ style=s,
176
+ )
177
+
178
+ # Add size annotations
179
+ sb = normalize_size(size_before)
180
+ sa = normalize_size(size_after)
181
+ ax1.text(
182
+ 0.5,
183
+ -0.05,
184
+ f"Canvas: {sb['width_mm']:.1f} x {sb['height_mm']:.1f} mm",
185
+ transform=ax1.transAxes,
186
+ ha="center",
187
+ fontsize=10,
188
+ )
189
+ ax2.text(
190
+ 0.5,
191
+ -0.05,
192
+ f"Canvas: {sa['width_mm']:.1f} x {sa['height_mm']:.1f} mm",
193
+ transform=ax2.transAxes,
194
+ ha="center",
195
+ fontsize=10,
196
+ )
197
+
198
+ plt.tight_layout()
199
+ return fig
200
+
201
+
202
+ def _draw_grid(ax: Axes, w: float, h: float, s: Dict[str, str]) -> None:
203
+ """Draw background grid."""
204
+ # Major grid every 10mm
205
+ for x in range(0, int(w) + 1, 10):
206
+ ax.axvline(x, color=s["grid_color"], linewidth=0.5, alpha=0.5)
207
+ for y in range(0, int(h) + 1, 10):
208
+ ax.axhline(y, color=s["grid_color"], linewidth=0.5, alpha=0.5)
209
+
210
+
211
+ def _draw_canvas(ax: Axes, w: float, h: float, s: Dict[str, str]) -> None:
212
+ """Draw canvas rectangle."""
213
+ rect = mpatches.Rectangle(
214
+ (0, 0),
215
+ w,
216
+ h,
217
+ linewidth=2,
218
+ edgecolor=s["canvas_edge"],
219
+ facecolor=s["canvas_color"],
220
+ alpha=0.95,
221
+ zorder=1,
222
+ )
223
+ ax.add_patch(rect)
224
+
225
+
226
+ def _draw_origin(ax: Axes, s: Dict[str, str]) -> None:
227
+ """Draw origin marker at (0,0)."""
228
+ # Origin cross
229
+ ax.plot([-3, 3], [0, 0], color=s["origin_color"], linewidth=2, zorder=10)
230
+ ax.plot([0, 0], [-3, 3], color=s["origin_color"], linewidth=2, zorder=10)
231
+ # Origin label
232
+ ax.text(
233
+ -5,
234
+ -5,
235
+ "(0,0)",
236
+ fontsize=8,
237
+ color=s["origin_color"],
238
+ ha="right",
239
+ va="bottom",
240
+ fontweight="bold",
241
+ )
242
+
243
+
244
+ def _draw_element(
245
+ ax: Axes,
246
+ elem: Dict[str, Any],
247
+ index: int,
248
+ s: Dict[str, str],
249
+ show_dimensions: bool,
250
+ ) -> None:
251
+ """Draw a single element with bounding box."""
252
+ bounds = element_bounds(elem)
253
+ x, y = bounds["x_mm"], bounds["y_mm"]
254
+ w, h = bounds["width_mm"], bounds["height_mm"]
255
+
256
+ # Element rectangle
257
+ rect = mpatches.Rectangle(
258
+ (x, y),
259
+ w,
260
+ h,
261
+ linewidth=1.5,
262
+ edgecolor=s["element_edge"],
263
+ facecolor=s["element_fill"],
264
+ alpha=0.8,
265
+ zorder=5,
266
+ )
267
+ ax.add_patch(rect)
268
+
269
+ # Element label
270
+ elem_id = elem.get("id", f"E{index}")
271
+ elem_type = elem.get("type", "unknown")
272
+ label = f"{elem_id}\n({elem_type})"
273
+ ax.text(
274
+ x + w / 2,
275
+ y + h / 2,
276
+ label,
277
+ ha="center",
278
+ va="center",
279
+ fontsize=9,
280
+ color=s["text_color"],
281
+ fontweight="bold",
282
+ zorder=6,
283
+ )
284
+
285
+ # Position annotation
286
+ if show_dimensions:
287
+ ax.text(
288
+ x,
289
+ y - 2,
290
+ f"({x:.0f}, {y:.0f})",
291
+ fontsize=7,
292
+ color=s["dimension_color"],
293
+ ha="left",
294
+ va="bottom",
295
+ )
296
+ # Size annotation
297
+ ax.text(
298
+ x + w,
299
+ y + h + 2,
300
+ f"{w:.0f}x{h:.0f}",
301
+ fontsize=7,
302
+ color=s["dimension_color"],
303
+ ha="right",
304
+ va="top",
305
+ )
306
+
307
+
308
+ def _draw_rulers(ax: Axes, w: float, h: float, s: Dict[str, str]) -> None:
309
+ """Draw rulers along edges."""
310
+ ruler_offset = -12
311
+
312
+ # Horizontal ruler (top)
313
+ ax.plot([0, w], [ruler_offset, ruler_offset], color=s["ruler_color"], linewidth=1)
314
+ for x in range(0, int(w) + 1, 10):
315
+ tick_len = 3 if x % 50 == 0 else 1.5
316
+ ax.plot(
317
+ [x, x],
318
+ [ruler_offset, ruler_offset + tick_len],
319
+ color=s["ruler_color"],
320
+ linewidth=1,
321
+ )
322
+ if x % 50 == 0:
323
+ ax.text(
324
+ x,
325
+ ruler_offset - 2,
326
+ str(x),
327
+ fontsize=7,
328
+ ha="center",
329
+ va="bottom",
330
+ color=s["ruler_color"],
331
+ )
332
+
333
+ # Vertical ruler (left)
334
+ ax.plot([ruler_offset, ruler_offset], [0, h], color=s["ruler_color"], linewidth=1)
335
+ for y in range(0, int(h) + 1, 10):
336
+ tick_len = 3 if y % 50 == 0 else 1.5
337
+ ax.plot(
338
+ [ruler_offset, ruler_offset + tick_len],
339
+ [y, y],
340
+ color=s["ruler_color"],
341
+ linewidth=1,
342
+ )
343
+ if y % 50 == 0:
344
+ ax.text(
345
+ ruler_offset - 2,
346
+ y,
347
+ str(y),
348
+ fontsize=7,
349
+ ha="right",
350
+ va="center",
351
+ color=s["ruler_color"],
352
+ )
353
+
354
+
355
+ def _draw_canvas_dimensions(ax: Axes, w: float, h: float, s: Dict[str, str]) -> None:
356
+ """Draw canvas dimension annotations."""
357
+ # Width dimension (bottom)
358
+ y_pos = h + 8
359
+ ax.annotate(
360
+ "",
361
+ xy=(w, y_pos),
362
+ xytext=(0, y_pos),
363
+ arrowprops=dict(arrowstyle="<->", color=s["dimension_color"], lw=1.5),
364
+ )
365
+ ax.text(
366
+ w / 2,
367
+ y_pos + 3,
368
+ f"{w:.0f} mm",
369
+ ha="center",
370
+ va="bottom",
371
+ fontsize=9,
372
+ color=s["dimension_color"],
373
+ fontweight="bold",
374
+ )
375
+
376
+ # Height dimension (right)
377
+ x_pos = w + 8
378
+ ax.annotate(
379
+ "",
380
+ xy=(x_pos, h),
381
+ xytext=(x_pos, 0),
382
+ arrowprops=dict(arrowstyle="<->", color=s["dimension_color"], lw=1.5),
383
+ )
384
+ ax.text(
385
+ x_pos + 3,
386
+ h / 2,
387
+ f"{h:.0f} mm",
388
+ ha="left",
389
+ va="center",
390
+ fontsize=9,
391
+ color=s["dimension_color"],
392
+ fontweight="bold",
393
+ rotation=90,
394
+ )
395
+
396
+
397
+ # EOF
@@ -0,0 +1,197 @@
1
+ #!/usr/bin/env python3
2
+ # File: ./src/scitex/vis/utils/validate.py
3
+ """Validation utilities for figure JSON specifications."""
4
+
5
+ from typing import Any, Dict
6
+
7
+
8
+ def validate_json_structure(fig_json: Dict[str, Any]) -> bool:
9
+ """
10
+ Validate basic JSON structure requirements.
11
+
12
+ Parameters
13
+ ----------
14
+ fig_json : Dict[str, Any]
15
+ Figure JSON to validate
16
+
17
+ Returns
18
+ -------
19
+ bool
20
+ True if valid, raises ValueError otherwise
21
+
22
+ Raises
23
+ ------
24
+ ValueError
25
+ If JSON structure is invalid
26
+ """
27
+ # Required fields
28
+ required_fields = ["width_mm", "height_mm"]
29
+
30
+ for field in required_fields:
31
+ if field not in fig_json:
32
+ raise ValueError(f"Missing required field: {field}")
33
+
34
+ # Type validation
35
+ if not isinstance(fig_json["width_mm"], (int, float)):
36
+ raise ValueError("width_mm must be a number")
37
+
38
+ if not isinstance(fig_json["height_mm"], (int, float)):
39
+ raise ValueError("height_mm must be a number")
40
+
41
+ # Value validation
42
+ if fig_json["width_mm"] <= 0:
43
+ raise ValueError(f"width_mm must be positive, got {fig_json['width_mm']}")
44
+
45
+ if fig_json["height_mm"] <= 0:
46
+ raise ValueError(f"height_mm must be positive, got {fig_json['height_mm']}")
47
+
48
+ # Optional fields validation
49
+ if "nrows" in fig_json:
50
+ if not isinstance(fig_json["nrows"], int) or fig_json["nrows"] <= 0:
51
+ raise ValueError("nrows must be a positive integer")
52
+
53
+ if "ncols" in fig_json:
54
+ if not isinstance(fig_json["ncols"], int) or fig_json["ncols"] <= 0:
55
+ raise ValueError("ncols must be a positive integer")
56
+
57
+ if "dpi" in fig_json:
58
+ if not isinstance(fig_json["dpi"], int) or fig_json["dpi"] <= 0:
59
+ raise ValueError("dpi must be a positive integer")
60
+
61
+ if "axes" in fig_json:
62
+ if not isinstance(fig_json["axes"], list):
63
+ raise ValueError("axes must be a list")
64
+
65
+ return True
66
+
67
+
68
+ def validate_plot_data(plot_data: Dict[str, Any]) -> bool:
69
+ """
70
+ Validate plot data contains required fields for plot type.
71
+
72
+ Parameters
73
+ ----------
74
+ plot_data : Dict[str, Any]
75
+ Plot configuration with data
76
+
77
+ Returns
78
+ -------
79
+ bool
80
+ True if valid, raises ValueError otherwise
81
+
82
+ Raises
83
+ ------
84
+ ValueError
85
+ If plot data is invalid
86
+ """
87
+ if "plot_type" not in plot_data:
88
+ raise ValueError("Plot must specify plot_type")
89
+
90
+ plot_type = plot_data["plot_type"]
91
+ data = plot_data.get("data", {})
92
+
93
+ # Type-specific requirements
94
+ if plot_type in ["line", "scatter", "errorbar"]:
95
+ if "x" not in data or "y" not in data:
96
+ raise ValueError(f"{plot_type} requires 'x' and 'y' data")
97
+
98
+ # Validate arrays have same length
99
+ x_len = len(data["x"]) if hasattr(data["x"], "__len__") else 1
100
+ y_len = len(data["y"]) if hasattr(data["y"], "__len__") else 1
101
+
102
+ if x_len != y_len:
103
+ raise ValueError(f"x and y data must have same length: {x_len} != {y_len}")
104
+
105
+ elif plot_type in ["bar", "barh"]:
106
+ if "x" not in data:
107
+ raise ValueError(f"{plot_type} requires 'x' data")
108
+
109
+ if "height" not in data and "y" not in data:
110
+ raise ValueError(f"{plot_type} requires 'height' or 'y' data")
111
+
112
+ elif plot_type == "hist":
113
+ if "x" not in data:
114
+ raise ValueError("hist requires 'x' data")
115
+
116
+ elif plot_type in ["heatmap", "imshow"]:
117
+ if "z" not in data and "img" not in data:
118
+ raise ValueError(f"{plot_type} requires 'z' or 'img' data")
119
+
120
+ elif plot_type in ["contour", "contourf"]:
121
+ if "x" not in data or "y" not in data or "z" not in data:
122
+ raise ValueError(f"{plot_type} requires 'x', 'y', and 'z' data")
123
+
124
+ return True
125
+
126
+
127
+ def check_schema_version(fig_json: Dict[str, Any]) -> str:
128
+ """
129
+ Check and return schema version.
130
+
131
+ Parameters
132
+ ----------
133
+ fig_json : Dict[str, Any]
134
+ Figure JSON
135
+
136
+ Returns
137
+ -------
138
+ str
139
+ Schema version (defaults to "1.0.0" if not specified)
140
+ """
141
+ return fig_json.get("schema_version", "1.0.0")
142
+
143
+
144
+ def validate_color(color: str) -> bool:
145
+ """
146
+ Validate color specification.
147
+
148
+ Parameters
149
+ ----------
150
+ color : str
151
+ Color specification (name, hex, rgb, etc.)
152
+
153
+ Returns
154
+ -------
155
+ bool
156
+ True if valid, raises ValueError otherwise
157
+ """
158
+ if not isinstance(color, str):
159
+ raise ValueError(f"Color must be a string, got {type(color)}")
160
+
161
+ # Basic validation - matplotlib will do deeper validation
162
+ if not color:
163
+ raise ValueError("Color cannot be empty string")
164
+
165
+ return True
166
+
167
+
168
+ def validate_axes_layout(nrows: int, ncols: int, num_axes: int) -> bool:
169
+ """
170
+ Validate axes layout is consistent.
171
+
172
+ Parameters
173
+ ----------
174
+ nrows : int
175
+ Number of rows
176
+ ncols : int
177
+ Number of columns
178
+ num_axes : int
179
+ Number of axes configurations
180
+
181
+ Returns
182
+ -------
183
+ bool
184
+ True if valid, raises ValueError otherwise
185
+ """
186
+ max_axes = nrows * ncols
187
+
188
+ if num_axes > max_axes:
189
+ raise ValueError(
190
+ f"Too many axes: {num_axes} axes for {nrows}x{ncols} layout "
191
+ f"(max {max_axes})"
192
+ )
193
+
194
+ return True
195
+
196
+
197
+ # EOF
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: 2025-12-21
3
+ # File: /home/ywatanabe/proj/scitex-code/src/scitex/fts/_kinds/__init__.py
4
+
5
+ """FTS Kind-specific modules.
6
+
7
+ Bundle kinds:
8
+ - figure: Composite container (has children, no payload)
9
+ - plot: Data visualization (has payload/data.csv)
10
+ - table: Tabular data (has payload/table.csv)
11
+ - stats: Statistical results (has payload/stats.json)
12
+ - text: Text annotation (no payload)
13
+ - shape: Shape annotation (no payload)
14
+ - image: Embedded image (has payload/image.*)
15
+ """
16
+
17
+ from ._figure import render_composite
18
+ from ._plot import Encoding, Theme, render_traces
19
+ from ._stats import Stats
20
+ from ._table import export_to_latex
21
+ from ._text import render_text
22
+ from ._shape import render_shape
23
+ from ._image import render_image, load_image
24
+
25
+ __all__ = [
26
+ # Figure (composite)
27
+ "render_composite",
28
+ # Plot
29
+ "Encoding",
30
+ "Theme",
31
+ "render_traces",
32
+ # Stats
33
+ "Stats",
34
+ # Table
35
+ "export_to_latex",
36
+ # Text
37
+ "render_text",
38
+ # Shape
39
+ "render_shape",
40
+ # Image
41
+ "render_image",
42
+ "load_image",
43
+ ]
44
+
45
+ # EOF
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: 2025-12-21
3
+ # File: /home/ywatanabe/proj/scitex-code/src/scitex/fts/_kinds/_figure/__init__.py
4
+
5
+ """Figure kind - Composite container for multiple elements.
6
+
7
+ A figure is a container that holds other bundles (plots, tables, text, etc.)
8
+ arranged in a layout. It has no payload data of its own.
9
+
10
+ Structure:
11
+ - children/: Contains embedded child bundles
12
+ - layout: Defines arrangement (rows, cols, panels)
13
+ """
14
+
15
+ from ._composite import render_composite
16
+
17
+ __all__ = ["render_composite"]
18
+
19
+ # EOF