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,46 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: 2025-12-20
3
+ # File: /home/ywatanabe/proj/scitex-code/src/scitex/fsb/_dataclasses/ChannelEncoding.py
4
+
5
+ """ChannelEncoding - Single channel encoding."""
6
+
7
+ from dataclasses import dataclass
8
+ from typing import Any, Dict, Optional
9
+
10
+
11
+ @dataclass
12
+ class ChannelEncoding:
13
+ """Encoding for a single visual channel (x, y, color, size, etc.)."""
14
+
15
+ column: Optional[str] = None
16
+ scale: str = "linear" # linear, log, categorical, ordinal, time
17
+ domain: Optional[tuple] = None # Min/max or category list
18
+ range: Optional[tuple] = None # Output range for mapping
19
+
20
+ def to_dict(self) -> Dict[str, Any]:
21
+ result = {}
22
+ if self.column:
23
+ result["column"] = self.column
24
+ if self.scale != "linear":
25
+ result["scale"] = self.scale
26
+ if self.domain:
27
+ result["domain"] = list(self.domain)
28
+ if self.range:
29
+ result["range"] = list(self.range)
30
+ return result
31
+
32
+ @classmethod
33
+ def from_dict(cls, data: Dict[str, Any]) -> "ChannelEncoding":
34
+ domain = data.get("domain")
35
+ range_ = data.get("range")
36
+ return cls(
37
+ column=data.get("column"),
38
+ scale=data.get("scale", "linear"),
39
+ domain=tuple(domain) if domain else None,
40
+ range=tuple(range_) if range_ else None,
41
+ )
42
+
43
+
44
+ __all__ = ["ChannelEncoding"]
45
+
46
+ # EOF
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: 2025-12-20
3
+ # File: /home/ywatanabe/proj/scitex-code/src/scitex/fsb/_dataclasses/Encoding.py
4
+
5
+ """Encoding - Complete encoding specification for a bundle."""
6
+
7
+ import json
8
+ from dataclasses import dataclass, field
9
+ from typing import Any, Dict, List
10
+
11
+ from ._TraceEncoding import TraceEncoding
12
+
13
+ ENCODING_VERSION = "1.0.0"
14
+
15
+
16
+ @dataclass
17
+ class AxesConfig:
18
+ """Axis configuration for encoding."""
19
+
20
+ title: str = ""
21
+ type: str = "quantitative"
22
+
23
+ def to_dict(self) -> Dict[str, Any]:
24
+ result = {}
25
+ if self.title:
26
+ result["title"] = self.title
27
+ if self.type != "quantitative":
28
+ result["type"] = self.type
29
+ return result
30
+
31
+ @classmethod
32
+ def from_dict(cls, data: Dict[str, Any]) -> "AxesConfig":
33
+ return cls(
34
+ title=data.get("title", ""),
35
+ type=data.get("type", "quantitative"),
36
+ )
37
+
38
+
39
+ @dataclass
40
+ class Encoding:
41
+ """Complete encoding specification for a bundle.
42
+
43
+ Stored in encoding.json at bundle root.
44
+ """
45
+
46
+ traces: List[TraceEncoding] = field(default_factory=list)
47
+ axes: Dict[str, AxesConfig] = field(default_factory=dict) # {"x": AxesConfig, "y": AxesConfig}
48
+
49
+ # Schema metadata
50
+ schema_name: str = "fsb.encoding"
51
+ schema_version: str = ENCODING_VERSION
52
+
53
+ def to_dict(self) -> Dict[str, Any]:
54
+ result = {
55
+ "traces": [t.to_dict() for t in self.traces],
56
+ }
57
+ if self.axes:
58
+ result["axes"] = {k: v.to_dict() for k, v in self.axes.items()}
59
+ return result
60
+
61
+ def to_json(self, indent: int = 2) -> str:
62
+ return json.dumps(self.to_dict(), indent=indent)
63
+
64
+ @classmethod
65
+ def from_dict(cls, data: Dict[str, Any]) -> "Encoding":
66
+ axes = {}
67
+ axes_data = data.get("axes", {})
68
+ for key, val in axes_data.items():
69
+ axes[key] = AxesConfig.from_dict(val) if isinstance(val, dict) else AxesConfig()
70
+ return cls(
71
+ traces=[TraceEncoding.from_dict(t) for t in data.get("traces", [])],
72
+ axes=axes,
73
+ )
74
+
75
+ @classmethod
76
+ def from_json(cls, json_str: str) -> "Encoding":
77
+ return cls.from_dict(json.loads(json_str))
78
+
79
+
80
+ __all__ = ["ENCODING_VERSION", "AxesConfig", "Encoding"]
81
+
82
+ # EOF
@@ -0,0 +1,441 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: 2025-12-21
3
+ # File: /home/ywatanabe/proj/scitex-code/src/scitex/fts/_fig/_dataclasses/_Theme.py
4
+
5
+ """Theme - Visual aesthetics for FTS bundles."""
6
+
7
+ import json
8
+ from dataclasses import dataclass, field
9
+ from typing import Any, Dict, List, Optional
10
+
11
+ THEME_VERSION = "1.0.0"
12
+
13
+
14
+ @dataclass
15
+ class Colors:
16
+ """Color palette configuration."""
17
+
18
+ palette: List[str] = field(
19
+ default_factory=lambda: ["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd"]
20
+ )
21
+ primary: Optional[str] = None
22
+ secondary: Optional[str] = None
23
+ background: str = "#ffffff"
24
+ text: str = "#000000"
25
+ axis: str = "#333333"
26
+ grid: str = "#e0e0e0"
27
+
28
+ def to_dict(self) -> Dict[str, Any]:
29
+ result = {
30
+ "palette": self.palette,
31
+ "background": self.background,
32
+ "text": self.text,
33
+ "axis": self.axis,
34
+ "grid": self.grid,
35
+ }
36
+ if self.primary:
37
+ result["primary"] = self.primary
38
+ if self.secondary:
39
+ result["secondary"] = self.secondary
40
+ return result
41
+
42
+ @classmethod
43
+ def from_dict(cls, data: Any) -> "Colors":
44
+ # Return if already a Colors instance
45
+ if isinstance(data, cls):
46
+ return data
47
+ if not isinstance(data, dict):
48
+ return cls()
49
+ return cls(
50
+ palette=data.get("palette", ["#1f77b4", "#ff7f0e", "#2ca02c"]),
51
+ primary=data.get("primary"),
52
+ secondary=data.get("secondary"),
53
+ background=data.get("background", "#ffffff"),
54
+ text=data.get("text", "#000000"),
55
+ axis=data.get("axis", "#333333"),
56
+ grid=data.get("grid", "#e0e0e0"),
57
+ )
58
+
59
+
60
+ @dataclass
61
+ class Typography:
62
+ """Typography configuration."""
63
+
64
+ family: str = "sans-serif"
65
+ size_pt: float = 8.0
66
+ title_size_pt: float = 10.0
67
+ label_size_pt: float = 8.0
68
+ tick_size_pt: float = 7.0
69
+
70
+ def to_dict(self) -> Dict[str, Any]:
71
+ return {
72
+ "family": self.family,
73
+ "size_pt": self.size_pt,
74
+ "title_size_pt": self.title_size_pt,
75
+ "label_size_pt": self.label_size_pt,
76
+ "tick_size_pt": self.tick_size_pt,
77
+ }
78
+
79
+ @classmethod
80
+ def from_dict(cls, data: Any) -> "Typography":
81
+ # Return if already a Typography instance
82
+ if isinstance(data, cls):
83
+ return data
84
+ if not isinstance(data, dict):
85
+ return cls()
86
+ return cls(
87
+ family=data.get("family", "sans-serif"),
88
+ size_pt=data.get("size_pt", 8.0),
89
+ title_size_pt=data.get("title_size_pt", data.get("title_size", 10.0)),
90
+ label_size_pt=data.get("label_size_pt", data.get("label_size", 8.0)),
91
+ tick_size_pt=data.get("tick_size_pt", data.get("tick_size", 7.0)),
92
+ )
93
+
94
+
95
+ @dataclass
96
+ class Lines:
97
+ """Line style configuration."""
98
+
99
+ width_pt: float = 1.0
100
+ style: str = "solid" # solid, dashed, dotted, dashdot
101
+
102
+ def to_dict(self) -> Dict[str, Any]:
103
+ return {"width_pt": self.width_pt, "style": self.style}
104
+
105
+ @classmethod
106
+ def from_dict(cls, data: Any) -> "Lines":
107
+ if isinstance(data, cls):
108
+ return data
109
+ if not isinstance(data, dict):
110
+ return cls()
111
+ return cls(
112
+ width_pt=data.get("width_pt", 1.0),
113
+ style=data.get("style", "solid"),
114
+ )
115
+
116
+
117
+ @dataclass
118
+ class Markers:
119
+ """Marker style configuration."""
120
+
121
+ size_pt: float = 4.0
122
+ symbol: str = "circle" # circle, square, triangle, cross, diamond
123
+
124
+ def to_dict(self) -> Dict[str, Any]:
125
+ return {"size_pt": self.size_pt, "symbol": self.symbol}
126
+
127
+ @classmethod
128
+ def from_dict(cls, data: Any) -> "Markers":
129
+ if isinstance(data, cls):
130
+ return data
131
+ if not isinstance(data, dict):
132
+ return cls()
133
+ return cls(
134
+ size_pt=data.get("size_pt", 4.0),
135
+ symbol=data.get("symbol", "circle"),
136
+ )
137
+
138
+
139
+ @dataclass
140
+ class Grid:
141
+ """Grid line configuration."""
142
+
143
+ show: bool = True
144
+ major_width_pt: float = 0.5
145
+ minor_width_pt: float = 0.25
146
+ major_alpha: float = 0.3
147
+ minor_alpha: float = 0.1
148
+
149
+ def to_dict(self) -> Dict[str, Any]:
150
+ return {
151
+ "show": self.show,
152
+ "major_width_pt": self.major_width_pt,
153
+ "minor_width_pt": self.minor_width_pt,
154
+ "major_alpha": self.major_alpha,
155
+ "minor_alpha": self.minor_alpha,
156
+ }
157
+
158
+ @classmethod
159
+ def from_dict(cls, data: Any) -> "Grid":
160
+ if isinstance(data, cls):
161
+ return data
162
+ if isinstance(data, bool):
163
+ return cls(show=data)
164
+ if not isinstance(data, dict):
165
+ return cls()
166
+ return cls(
167
+ show=data.get("show", True),
168
+ major_width_pt=data.get("major_width_pt", 0.5),
169
+ minor_width_pt=data.get("minor_width_pt", 0.25),
170
+ major_alpha=data.get("major_alpha", 0.3),
171
+ minor_alpha=data.get("minor_alpha", 0.1),
172
+ )
173
+
174
+
175
+ @dataclass
176
+ class TraceTheme:
177
+ """Per-trace theme overrides."""
178
+
179
+ trace_id: str
180
+ color: Optional[str] = None
181
+ line_width_pt: Optional[float] = None
182
+ marker_size_pt: Optional[float] = None
183
+ alpha: Optional[float] = None
184
+
185
+ def to_dict(self) -> Dict[str, Any]:
186
+ result = {"trace_id": self.trace_id}
187
+ if self.color:
188
+ result["color"] = self.color
189
+ if self.line_width_pt is not None:
190
+ result["line_width_pt"] = self.line_width_pt
191
+ if self.marker_size_pt is not None:
192
+ result["marker_size_pt"] = self.marker_size_pt
193
+ if self.alpha is not None:
194
+ result["alpha"] = self.alpha
195
+ return result
196
+
197
+ @classmethod
198
+ def from_dict(cls, data: Any) -> "TraceTheme":
199
+ if isinstance(data, cls):
200
+ return data
201
+ if not isinstance(data, dict):
202
+ return cls(trace_id="trace_0")
203
+ return cls(
204
+ trace_id=data.get("trace_id", "trace_0"),
205
+ color=data.get("color"),
206
+ line_width_pt=data.get("line_width_pt"),
207
+ marker_size_pt=data.get("marker_size_pt"),
208
+ alpha=data.get("alpha"),
209
+ )
210
+
211
+
212
+ @dataclass
213
+ class FigureTitle:
214
+ """Figure title configuration for publications."""
215
+
216
+ text: str = ""
217
+ prefix: str = "Figure"
218
+ number: Optional[int] = None
219
+ fontsize: Optional[float] = None
220
+ fontweight: str = "bold"
221
+
222
+ def to_dict(self) -> Dict[str, Any]:
223
+ result = {"text": self.text, "prefix": self.prefix, "fontweight": self.fontweight}
224
+ if self.number is not None:
225
+ result["number"] = self.number
226
+ if self.fontsize is not None:
227
+ result["fontsize"] = self.fontsize
228
+ return result
229
+
230
+ @classmethod
231
+ def from_dict(cls, data: Any) -> "FigureTitle":
232
+ if isinstance(data, cls):
233
+ return data
234
+ if not isinstance(data, dict):
235
+ return cls()
236
+ return cls(
237
+ text=data.get("text", ""),
238
+ prefix=data.get("prefix", "Figure"),
239
+ number=data.get("number"),
240
+ fontsize=data.get("fontsize"),
241
+ fontweight=data.get("fontweight", "bold"),
242
+ )
243
+
244
+
245
+ @dataclass
246
+ class PanelDescription:
247
+ """Single panel description for captions."""
248
+
249
+ label: str
250
+ description: str
251
+
252
+ def to_dict(self) -> Dict[str, Any]:
253
+ return {"label": self.label, "description": self.description}
254
+
255
+ @classmethod
256
+ def from_dict(cls, data: Any) -> "PanelDescription":
257
+ if isinstance(data, cls):
258
+ return data
259
+ if not isinstance(data, dict):
260
+ return cls(label="", description="")
261
+ return cls(
262
+ label=data.get("label", ""),
263
+ description=data.get("description", ""),
264
+ )
265
+
266
+
267
+ @dataclass
268
+ class Caption:
269
+ """Figure caption for publications."""
270
+
271
+ text: str = ""
272
+ panels: List[PanelDescription] = field(default_factory=list)
273
+
274
+ def to_dict(self) -> Dict[str, Any]:
275
+ result = {"text": self.text}
276
+ if self.panels:
277
+ result["panels"] = [p.to_dict() for p in self.panels]
278
+ return result
279
+
280
+ @classmethod
281
+ def from_dict(cls, data: Any) -> "Caption":
282
+ if isinstance(data, cls):
283
+ return data
284
+ if not isinstance(data, dict):
285
+ return cls()
286
+ panels = [PanelDescription.from_dict(p) for p in data.get("panels", [])]
287
+ return cls(text=data.get("text", ""), panels=panels)
288
+
289
+
290
+ @dataclass
291
+ class PanelLabels:
292
+ """Panel label styling (A, B, C, etc.)."""
293
+
294
+ style: str = "uppercase" # uppercase, lowercase, numeric
295
+ fontsize: float = 12.0
296
+ fontweight: str = "bold"
297
+ position: str = "top-left" # top-left, top-right, bottom-left, bottom-right
298
+ offset_x: float = 0.02
299
+ offset_y: float = 0.98
300
+
301
+ def to_dict(self) -> Dict[str, Any]:
302
+ return {
303
+ "style": self.style,
304
+ "fontsize": self.fontsize,
305
+ "fontweight": self.fontweight,
306
+ "position": self.position,
307
+ "offset_x": self.offset_x,
308
+ "offset_y": self.offset_y,
309
+ }
310
+
311
+ @classmethod
312
+ def from_dict(cls, data: Any) -> "PanelLabels":
313
+ if isinstance(data, cls):
314
+ return data
315
+ if not isinstance(data, dict):
316
+ return cls()
317
+ return cls(
318
+ style=data.get("style", "uppercase"),
319
+ fontsize=data.get("fontsize", 12.0),
320
+ fontweight=data.get("fontweight", "bold"),
321
+ position=data.get("position", "top-left"),
322
+ offset_x=data.get("offset_x", 0.02),
323
+ offset_y=data.get("offset_y", 0.98),
324
+ )
325
+
326
+
327
+ @dataclass
328
+ class Theme:
329
+ """Complete theme specification for an FTS bundle.
330
+
331
+ Stored in canonical/theme.json.
332
+ """
333
+
334
+ # Core mode
335
+ mode: str = "light" # "light" | "dark"
336
+
337
+ # Styling components
338
+ colors: Colors = field(default_factory=Colors)
339
+ typography: Typography = field(default_factory=Typography)
340
+ lines: Lines = field(default_factory=Lines)
341
+ markers: Markers = field(default_factory=Markers)
342
+ grid: Grid = field(default_factory=Grid)
343
+
344
+ # Per-trace overrides
345
+ traces: List[TraceTheme] = field(default_factory=list)
346
+
347
+ # Publication metadata
348
+ preset: Optional[str] = None # nature, science, cell, ieee, acs, minimal, presentation
349
+ figure_title: Optional[FigureTitle] = None
350
+ caption: Optional[Caption] = None
351
+ panel_labels: Optional[PanelLabels] = None
352
+
353
+ # Alias for typography (for convenience)
354
+ fonts: Optional[Typography] = None
355
+
356
+ # Schema metadata
357
+ schema_name: str = "fts.theme"
358
+ schema_version: str = THEME_VERSION
359
+
360
+ def to_dict(self) -> Dict[str, Any]:
361
+ result = {
362
+ "mode": self.mode,
363
+ "colors": self.colors.to_dict(),
364
+ "typography": self.typography.to_dict(),
365
+ "lines": self.lines.to_dict(),
366
+ "markers": self.markers.to_dict(),
367
+ "grid": self.grid.to_dict(),
368
+ }
369
+ if self.traces:
370
+ result["traces"] = [t.to_dict() for t in self.traces]
371
+ if self.preset:
372
+ result["preset"] = self.preset
373
+ if self.figure_title:
374
+ result["figure_title"] = self.figure_title.to_dict()
375
+ if self.caption:
376
+ result["caption"] = self.caption.to_dict()
377
+ if self.panel_labels:
378
+ result["panel_labels"] = self.panel_labels.to_dict()
379
+ return result
380
+
381
+ def to_json(self, indent: int = 2) -> str:
382
+ return json.dumps(self.to_dict(), indent=indent)
383
+
384
+ @classmethod
385
+ def from_dict(cls, data: Dict[str, Any]) -> "Theme":
386
+ # Handle fonts as alias for typography
387
+ typography_data = data.get("typography", {})
388
+ fonts_data = data.get("fonts")
389
+ if fonts_data and not typography_data:
390
+ typography_data = fonts_data
391
+
392
+ # Parse figure_title
393
+ figure_title = None
394
+ if "figure_title" in data and data["figure_title"]:
395
+ figure_title = FigureTitle.from_dict(data["figure_title"])
396
+
397
+ # Parse caption
398
+ caption = None
399
+ if "caption" in data and data["caption"]:
400
+ caption = Caption.from_dict(data["caption"])
401
+
402
+ # Parse panel_labels
403
+ panel_labels = None
404
+ if "panel_labels" in data and data["panel_labels"]:
405
+ panel_labels = PanelLabels.from_dict(data["panel_labels"])
406
+
407
+ return cls(
408
+ mode=data.get("mode", "light"),
409
+ colors=Colors.from_dict(data.get("colors", {})),
410
+ typography=Typography.from_dict(typography_data),
411
+ lines=Lines.from_dict(data.get("lines", {})),
412
+ markers=Markers.from_dict(data.get("markers", {})),
413
+ grid=Grid.from_dict(data.get("grid", {})),
414
+ traces=[TraceTheme.from_dict(t) for t in data.get("traces", [])],
415
+ preset=data.get("preset"),
416
+ figure_title=figure_title,
417
+ caption=caption,
418
+ panel_labels=panel_labels,
419
+ )
420
+
421
+ @classmethod
422
+ def from_json(cls, json_str: str) -> "Theme":
423
+ return cls.from_dict(json.loads(json_str))
424
+
425
+
426
+ __all__ = [
427
+ "THEME_VERSION",
428
+ "Colors",
429
+ "Typography",
430
+ "Lines",
431
+ "Markers",
432
+ "Grid",
433
+ "TraceTheme",
434
+ "FigureTitle",
435
+ "PanelDescription",
436
+ "Caption",
437
+ "PanelLabels",
438
+ "Theme",
439
+ ]
440
+
441
+ # EOF
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: 2025-12-20
3
+ # File: /home/ywatanabe/proj/scitex-code/src/scitex/fsb/_dataclasses/TraceEncoding.py
4
+
5
+ """TraceEncoding - Encoding for a single trace/data series."""
6
+
7
+ from dataclasses import dataclass
8
+ from typing import Any, Dict, Optional
9
+
10
+ from ._ChannelEncoding import ChannelEncoding
11
+
12
+
13
+ @dataclass
14
+ class TraceEncoding:
15
+ """Encoding specification for a single trace/data series."""
16
+
17
+ trace_id: str
18
+ data_ref: Optional[str] = None # Path to data file within bundle
19
+ x: Optional[ChannelEncoding] = None
20
+ y: Optional[ChannelEncoding] = None
21
+ color: Optional[ChannelEncoding] = None
22
+ size: Optional[ChannelEncoding] = None
23
+ group: Optional[ChannelEncoding] = None
24
+ label: Optional[ChannelEncoding] = None
25
+
26
+ def to_dict(self) -> Dict[str, Any]:
27
+ result = {"trace_id": self.trace_id}
28
+ if self.data_ref:
29
+ result["data_ref"] = self.data_ref
30
+ for channel in ["x", "y", "color", "size", "group", "label"]:
31
+ enc = getattr(self, channel)
32
+ if enc:
33
+ result[channel] = enc.to_dict()
34
+ return result
35
+
36
+ @classmethod
37
+ def from_dict(cls, data: Dict[str, Any]) -> "TraceEncoding":
38
+ return cls(
39
+ trace_id=data.get("trace_id", "trace_0"),
40
+ data_ref=data.get("data_ref"),
41
+ x=ChannelEncoding.from_dict(data["x"]) if "x" in data else None,
42
+ y=ChannelEncoding.from_dict(data["y"]) if "y" in data else None,
43
+ color=ChannelEncoding.from_dict(data["color"]) if "color" in data else None,
44
+ size=ChannelEncoding.from_dict(data["size"]) if "size" in data else None,
45
+ group=ChannelEncoding.from_dict(data["group"]) if "group" in data else None,
46
+ label=ChannelEncoding.from_dict(data["label"]) if "label" in data else None,
47
+ )
48
+
49
+
50
+ __all__ = ["TraceEncoding"]
51
+
52
+ # EOF
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: 2025-12-21
3
+ # File: /home/ywatanabe/proj/scitex-code/src/scitex/fts/_fig/_dataclasses/__init__.py
4
+
5
+ """Figure-specific dataclasses for FTS."""
6
+
7
+ from ._ChannelEncoding import ChannelEncoding
8
+ from ._Encoding import ENCODING_VERSION, AxesConfig, Encoding
9
+ from ._Theme import (
10
+ THEME_VERSION,
11
+ Caption,
12
+ Colors,
13
+ FigureTitle,
14
+ Grid,
15
+ Lines,
16
+ Markers,
17
+ PanelDescription,
18
+ PanelLabels,
19
+ Theme,
20
+ TraceTheme,
21
+ Typography,
22
+ )
23
+ from ._TraceEncoding import TraceEncoding
24
+
25
+ __all__ = [
26
+ # Encoding
27
+ "ENCODING_VERSION",
28
+ "AxesConfig",
29
+ "ChannelEncoding",
30
+ "TraceEncoding",
31
+ "Encoding",
32
+ # Theme
33
+ "THEME_VERSION",
34
+ "Colors",
35
+ "Typography",
36
+ "Lines",
37
+ "Markers",
38
+ "Grid",
39
+ "TraceTheme",
40
+ "FigureTitle",
41
+ "PanelDescription",
42
+ "Caption",
43
+ "PanelLabels",
44
+ "Theme",
45
+ ]
46
+
47
+ # EOF