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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (415) hide show
  1. scitex/__init__.py +15 -7
  2. scitex/__version__.py +1 -2
  3. scitex/_install_guide.py +250 -0
  4. scitex/_optional_deps.py +206 -39
  5. scitex/ai/_gen_ai/_Groq.py +2 -4
  6. scitex/ai/_gen_ai/_OpenAI.py +5 -2
  7. scitex/ai/_gen_ai/_Perplexity.py +20 -6
  8. scitex/audio/__init__.py +24 -15
  9. scitex/audio/_cross_process_lock.py +139 -0
  10. scitex/audio/_mcp_handlers.py +256 -0
  11. scitex/audio/_mcp_tool_schemas.py +203 -0
  12. scitex/audio/engines/elevenlabs_engine.py +5 -2
  13. scitex/audio/mcp_server.py +98 -457
  14. scitex/bridge/__init__.py +30 -19
  15. scitex/bridge/_figrecipe.py +245 -0
  16. scitex/bridge/_helpers.py +2 -1
  17. scitex/bridge/_plt_vis.py +23 -10
  18. scitex/bridge/_stats_plt.py +18 -5
  19. scitex/bridge/_stats_vis.py +16 -2
  20. scitex/browser/__init__.py +84 -44
  21. scitex/browser/automation/__init__.py +5 -1
  22. scitex/browser/core/BrowserMixin.py +17 -4
  23. scitex/browser/core/__init__.py +11 -2
  24. scitex/browser/remote/CaptchaHandler.py +1 -1
  25. scitex/browser/remote/ZenRowsAPIClient.py +1 -1
  26. scitex/capture/grid.py +487 -0
  27. scitex/capture/mcp_handlers.py +401 -0
  28. scitex/capture/mcp_tool_defs.py +192 -0
  29. scitex/capture/mcp_tools.py +241 -0
  30. scitex/capture/mcp_utils.py +30 -0
  31. scitex/cli/convert.py +421 -0
  32. scitex/cli/main.py +6 -4
  33. scitex/datetime/__init__.py +46 -0
  34. scitex/datetime/_linspace.py +100 -0
  35. scitex/datetime/_normalize_timestamp.py +306 -0
  36. scitex/db/_delete_duplicates.py +4 -4
  37. scitex/db/_sqlite3/_delete_duplicates.py +11 -2
  38. scitex/dev/plt/__init__.py +61 -62
  39. scitex/dev/plt/demo_plotters/__init__.py +0 -0
  40. scitex/dev/plt/demo_plotters/plot_mpl_axhline.py +28 -0
  41. scitex/dev/plt/demo_plotters/plot_mpl_axhspan.py +28 -0
  42. scitex/dev/plt/demo_plotters/plot_mpl_axvline.py +28 -0
  43. scitex/dev/plt/demo_plotters/plot_mpl_axvspan.py +28 -0
  44. scitex/dev/plt/demo_plotters/plot_mpl_bar.py +29 -0
  45. scitex/dev/plt/demo_plotters/plot_mpl_barh.py +29 -0
  46. scitex/dev/plt/demo_plotters/plot_mpl_boxplot.py +28 -0
  47. scitex/dev/plt/demo_plotters/plot_mpl_contour.py +31 -0
  48. scitex/dev/plt/demo_plotters/plot_mpl_contourf.py +31 -0
  49. scitex/dev/plt/demo_plotters/plot_mpl_errorbar.py +30 -0
  50. scitex/dev/plt/demo_plotters/plot_mpl_eventplot.py +28 -0
  51. scitex/dev/plt/demo_plotters/plot_mpl_fill.py +30 -0
  52. scitex/dev/plt/demo_plotters/plot_mpl_fill_between.py +31 -0
  53. scitex/dev/plt/demo_plotters/plot_mpl_hexbin.py +28 -0
  54. scitex/dev/plt/demo_plotters/plot_mpl_hist.py +28 -0
  55. scitex/dev/plt/demo_plotters/plot_mpl_hist2d.py +28 -0
  56. scitex/dev/plt/demo_plotters/plot_mpl_imshow.py +29 -0
  57. scitex/dev/plt/demo_plotters/plot_mpl_pcolormesh.py +31 -0
  58. scitex/dev/plt/demo_plotters/plot_mpl_pie.py +29 -0
  59. scitex/dev/plt/demo_plotters/plot_mpl_plot.py +29 -0
  60. scitex/dev/plt/demo_plotters/plot_mpl_quiver.py +31 -0
  61. scitex/dev/plt/demo_plotters/plot_mpl_scatter.py +28 -0
  62. scitex/dev/plt/demo_plotters/plot_mpl_stackplot.py +31 -0
  63. scitex/dev/plt/demo_plotters/plot_mpl_stem.py +29 -0
  64. scitex/dev/plt/demo_plotters/plot_mpl_step.py +29 -0
  65. scitex/dev/plt/demo_plotters/plot_mpl_violinplot.py +28 -0
  66. scitex/dev/plt/demo_plotters/plot_sns_barplot.py +29 -0
  67. scitex/dev/plt/demo_plotters/plot_sns_boxplot.py +29 -0
  68. scitex/dev/plt/demo_plotters/plot_sns_heatmap.py +28 -0
  69. scitex/dev/plt/demo_plotters/plot_sns_histplot.py +29 -0
  70. scitex/dev/plt/demo_plotters/plot_sns_kdeplot.py +29 -0
  71. scitex/dev/plt/demo_plotters/plot_sns_lineplot.py +31 -0
  72. scitex/dev/plt/demo_plotters/plot_sns_scatterplot.py +29 -0
  73. scitex/dev/plt/demo_plotters/plot_sns_stripplot.py +29 -0
  74. scitex/dev/plt/demo_plotters/plot_sns_swarmplot.py +29 -0
  75. scitex/dev/plt/demo_plotters/plot_sns_violinplot.py +29 -0
  76. scitex/dev/plt/demo_plotters/plot_stx_bar.py +29 -0
  77. scitex/dev/plt/demo_plotters/plot_stx_barh.py +29 -0
  78. scitex/dev/plt/demo_plotters/plot_stx_box.py +28 -0
  79. scitex/dev/plt/demo_plotters/plot_stx_boxplot.py +28 -0
  80. scitex/dev/plt/demo_plotters/plot_stx_conf_mat.py +28 -0
  81. scitex/dev/plt/demo_plotters/plot_stx_contour.py +31 -0
  82. scitex/dev/plt/demo_plotters/plot_stx_ecdf.py +28 -0
  83. scitex/dev/plt/demo_plotters/plot_stx_errorbar.py +30 -0
  84. scitex/dev/plt/demo_plotters/plot_stx_fill_between.py +31 -0
  85. scitex/dev/plt/demo_plotters/plot_stx_fillv.py +28 -0
  86. scitex/dev/plt/demo_plotters/plot_stx_heatmap.py +28 -0
  87. scitex/dev/plt/demo_plotters/plot_stx_image.py +28 -0
  88. scitex/dev/plt/demo_plotters/plot_stx_imshow.py +28 -0
  89. scitex/dev/plt/demo_plotters/plot_stx_joyplot.py +28 -0
  90. scitex/dev/plt/demo_plotters/plot_stx_kde.py +28 -0
  91. scitex/dev/plt/demo_plotters/plot_stx_line.py +28 -0
  92. scitex/dev/plt/demo_plotters/plot_stx_mean_ci.py +28 -0
  93. scitex/dev/plt/demo_plotters/plot_stx_mean_std.py +28 -0
  94. scitex/dev/plt/demo_plotters/plot_stx_median_iqr.py +28 -0
  95. scitex/dev/plt/demo_plotters/plot_stx_raster.py +28 -0
  96. scitex/dev/plt/demo_plotters/plot_stx_rectangle.py +28 -0
  97. scitex/dev/plt/demo_plotters/plot_stx_scatter.py +29 -0
  98. scitex/dev/plt/demo_plotters/plot_stx_shaded_line.py +29 -0
  99. scitex/dev/plt/demo_plotters/plot_stx_violin.py +28 -0
  100. scitex/dev/plt/demo_plotters/plot_stx_violinplot.py +28 -0
  101. scitex/dev/plt/mpl/get_dir_ax.py +46 -0
  102. scitex/dev/plt/mpl/get_signatures.py +176 -0
  103. scitex/dev/plt/mpl/get_signatures_details.py +522 -0
  104. scitex/dict/_pop_keys.py +1 -7
  105. scitex/dsp/__init__.py +15 -10
  106. scitex/dsp/add_noise.py +5 -2
  107. scitex/dsp/example.py +35 -22
  108. scitex/dsp/filt.py +8 -3
  109. scitex/dsp/reference.py +3 -2
  110. scitex/dsp/utils/__init__.py +2 -1
  111. scitex/dsp/utils/_differential_bandpass_filters.py +14 -4
  112. scitex/dt/__init__.py +39 -2
  113. scitex/errors.py +82 -521
  114. scitex/fig/__init__.py +4 -4
  115. scitex/fig/editor/edit/panel_loader.py +1 -1
  116. scitex/fig/io/_bundle.py +7 -7
  117. scitex/fts/README.md +262 -0
  118. scitex/fts/TODO.md +66 -0
  119. scitex/fts/__init__.py +90 -0
  120. scitex/fts/_bundle/README_IN_BUNDLE.md +102 -0
  121. scitex/fts/_bundle/_FTS.py +657 -0
  122. scitex/fts/_bundle/__init__.py +38 -0
  123. scitex/fts/_bundle/_children.py +216 -0
  124. scitex/fts/_bundle/_conversion/__init__.py +15 -0
  125. scitex/fts/_bundle/_conversion/_bundle2dict.py +44 -0
  126. scitex/fts/_bundle/_conversion/_dict2bundle.py +50 -0
  127. scitex/fts/_bundle/_dataclasses/_Axes.py +57 -0
  128. scitex/fts/_bundle/_dataclasses/_BBox.py +54 -0
  129. scitex/fts/_bundle/_dataclasses/_ColumnDef.py +72 -0
  130. scitex/fts/_bundle/_dataclasses/_DataFormat.py +40 -0
  131. scitex/fts/_bundle/_dataclasses/_DataInfo.py +135 -0
  132. scitex/fts/_bundle/_dataclasses/_DataSource.py +44 -0
  133. scitex/fts/_bundle/_dataclasses/_Node.py +319 -0
  134. scitex/fts/_bundle/_dataclasses/_NodeRefs.py +45 -0
  135. scitex/fts/_bundle/_dataclasses/_SizeMM.py +38 -0
  136. scitex/fts/_bundle/_dataclasses/__init__.py +35 -0
  137. scitex/fts/_bundle/_extractors/__init__.py +32 -0
  138. scitex/fts/_bundle/_extractors/_extract_bar.py +131 -0
  139. scitex/fts/_bundle/_extractors/_extract_line.py +71 -0
  140. scitex/fts/_bundle/_extractors/_extract_scatter.py +79 -0
  141. scitex/fts/_bundle/_loader.py +134 -0
  142. scitex/fts/_bundle/_mpl_helpers.py +389 -0
  143. scitex/fts/_bundle/_saver.py +269 -0
  144. scitex/fts/_bundle/_storage.py +200 -0
  145. scitex/fts/_bundle/_utils/__init__.py +55 -0
  146. scitex/fts/_bundle/_utils/_const.py +26 -0
  147. scitex/fts/_bundle/_utils/_errors.py +73 -0
  148. scitex/fts/_bundle/_utils/_generate.py +21 -0
  149. scitex/fts/_bundle/_utils/_types.py +76 -0
  150. scitex/fts/_bundle/_validation.py +434 -0
  151. scitex/fts/_bundle/_zipbundle.py +165 -0
  152. scitex/fts/_fig/__init__.py +22 -0
  153. scitex/fts/_fig/_backend/__init__.py +53 -0
  154. scitex/fts/_fig/_backend/_export.py +165 -0
  155. scitex/fts/_fig/_backend/_parser.py +188 -0
  156. scitex/fts/_fig/_backend/_render.py +538 -0
  157. scitex/fts/_fig/_composite.py +345 -0
  158. scitex/fts/_fig/_dataclasses/_ChannelEncoding.py +46 -0
  159. scitex/fts/_fig/_dataclasses/_Encoding.py +82 -0
  160. scitex/fts/_fig/_dataclasses/_Theme.py +441 -0
  161. scitex/fts/_fig/_dataclasses/_TraceEncoding.py +52 -0
  162. scitex/fts/_fig/_dataclasses/__init__.py +47 -0
  163. scitex/fts/_fig/_editor/__init__.py +14 -0
  164. scitex/fts/_fig/_editor/_cui/__init__.py +33 -0
  165. scitex/fts/_fig/_editor/_cui/_backend_detector.py +39 -0
  166. scitex/fts/_fig/_editor/_cui/_bundle_resolver.py +366 -0
  167. scitex/fts/_fig/_editor/_cui/_editor_launcher.py +175 -0
  168. scitex/fts/_fig/_editor/_cui/_manual_handler.py +52 -0
  169. scitex/fts/_fig/_editor/_cui/_panel_loader.py +246 -0
  170. scitex/fts/_fig/_editor/_cui/_path_resolver.py +66 -0
  171. scitex/fts/_fig/_editor/_defaults.py +300 -0
  172. scitex/fts/_fig/_editor/_gui/__init__.py +11 -0
  173. scitex/fts/_fig/_editor/_gui/_flask_editor/__init__.py +20 -0
  174. scitex/fts/_fig/_editor/_gui/_flask_editor/_bbox.py +1339 -0
  175. scitex/fts/_fig/_editor/_gui/_flask_editor/_core.py +1688 -0
  176. scitex/fts/_fig/_editor/_gui/_flask_editor/_plotter.py +664 -0
  177. scitex/fts/_fig/_editor/_gui/_flask_editor/_renderer.py +853 -0
  178. scitex/fts/_fig/_editor/_gui/_flask_editor/_utils.py +79 -0
  179. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/reset.css +41 -0
  180. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/typography.css +16 -0
  181. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/variables.css +85 -0
  182. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/buttons.css +217 -0
  183. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/context-menu.css +93 -0
  184. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/dropdown.css +57 -0
  185. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/forms.css +112 -0
  186. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/modal.css +59 -0
  187. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/sections.css +212 -0
  188. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/canvas.css +176 -0
  189. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/element-inspector.css +190 -0
  190. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/loading.css +59 -0
  191. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/overlay.css +45 -0
  192. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/panel-grid.css +95 -0
  193. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/selection.css +101 -0
  194. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/statistics.css +138 -0
  195. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/index.css +31 -0
  196. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/container.css +7 -0
  197. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/controls.css +56 -0
  198. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/preview.css +78 -0
  199. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/axis.js +314 -0
  200. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/basic.js +107 -0
  201. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/distribute.js +54 -0
  202. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/canvas.js +172 -0
  203. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/dragging.js +258 -0
  204. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/resize.js +48 -0
  205. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/selection.js +71 -0
  206. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/api.js +288 -0
  207. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/state.js +143 -0
  208. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/utils.js +245 -0
  209. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/dev/element-inspector.js +992 -0
  210. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/bbox.js +339 -0
  211. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/element-drag.js +286 -0
  212. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/overlay.js +371 -0
  213. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/preview.js +293 -0
  214. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/main.js +426 -0
  215. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/shortcuts/context-menu.js +152 -0
  216. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/shortcuts/keyboard.js +265 -0
  217. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/controls.js +184 -0
  218. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/download.js +57 -0
  219. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/help.js +100 -0
  220. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/theme.js +34 -0
  221. scitex/fts/_fig/_editor/_gui/_flask_editor/templates/__init__.py +124 -0
  222. scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_html.py +851 -0
  223. scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_scripts.py +4932 -0
  224. scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_styles.py +1657 -0
  225. scitex/fts/_fig/_editor/_gui/_flask_editor.py +36 -0
  226. scitex/fts/_fig/_models/_Annotations.py +115 -0
  227. scitex/fts/_fig/_models/_Axes.py +152 -0
  228. scitex/fts/_fig/_models/_Figure.py +138 -0
  229. scitex/fts/_fig/_models/_Guides.py +104 -0
  230. scitex/fts/_fig/_models/_Plot.py +123 -0
  231. scitex/fts/_fig/_models/_Styles.py +245 -0
  232. scitex/fts/_fig/_models/__init__.py +80 -0
  233. scitex/fts/_fig/_models/_plot_types/__init__.py +156 -0
  234. scitex/fts/_fig/_models/_plot_types/_bar.py +43 -0
  235. scitex/fts/_fig/_models/_plot_types/_box.py +38 -0
  236. scitex/fts/_fig/_models/_plot_types/_distribution.py +36 -0
  237. scitex/fts/_fig/_models/_plot_types/_errorbar.py +60 -0
  238. scitex/fts/_fig/_models/_plot_types/_histogram.py +30 -0
  239. scitex/fts/_fig/_models/_plot_types/_image.py +61 -0
  240. scitex/fts/_fig/_models/_plot_types/_line.py +57 -0
  241. scitex/fts/_fig/_models/_plot_types/_scatter.py +30 -0
  242. scitex/fts/_fig/_models/_plot_types/_seaborn.py +121 -0
  243. scitex/fts/_fig/_models/_plot_types/_violin.py +36 -0
  244. scitex/fts/_fig/_utils/__init__.py +129 -0
  245. scitex/fts/_fig/_utils/_auto_layout.py +127 -0
  246. scitex/fts/_fig/_utils/_calc_bounds.py +111 -0
  247. scitex/fts/_fig/_utils/_const_sizes.py +48 -0
  248. scitex/fts/_fig/_utils/_convert_coords.py +77 -0
  249. scitex/fts/_fig/_utils/_get_template.py +178 -0
  250. scitex/fts/_fig/_utils/_normalize.py +73 -0
  251. scitex/fts/_fig/_utils/_plot_layout.py +397 -0
  252. scitex/fts/_fig/_utils/_validate.py +197 -0
  253. scitex/fts/_kinds/__init__.py +45 -0
  254. scitex/fts/_kinds/_figure/__init__.py +19 -0
  255. scitex/fts/_kinds/_figure/_composite.py +345 -0
  256. scitex/fts/_kinds/_plot/__init__.py +25 -0
  257. scitex/fts/_kinds/_plot/_backend/__init__.py +53 -0
  258. scitex/fts/_kinds/_plot/_backend/_export.py +165 -0
  259. scitex/fts/_kinds/_plot/_backend/_parser.py +188 -0
  260. scitex/fts/_kinds/_plot/_backend/_render.py +538 -0
  261. scitex/fts/_kinds/_plot/_dataclasses/_ChannelEncoding.py +46 -0
  262. scitex/fts/_kinds/_plot/_dataclasses/_Encoding.py +82 -0
  263. scitex/fts/_kinds/_plot/_dataclasses/_Theme.py +441 -0
  264. scitex/fts/_kinds/_plot/_dataclasses/_TraceEncoding.py +52 -0
  265. scitex/fts/_kinds/_plot/_dataclasses/__init__.py +47 -0
  266. scitex/fts/_kinds/_plot/_models/_Annotations.py +115 -0
  267. scitex/fts/_kinds/_plot/_models/_Axes.py +152 -0
  268. scitex/fts/_kinds/_plot/_models/_Figure.py +138 -0
  269. scitex/fts/_kinds/_plot/_models/_Guides.py +104 -0
  270. scitex/fts/_kinds/_plot/_models/_Plot.py +123 -0
  271. scitex/fts/_kinds/_plot/_models/_Styles.py +245 -0
  272. scitex/fts/_kinds/_plot/_models/__init__.py +80 -0
  273. scitex/fts/_kinds/_plot/_models/_plot_types/__init__.py +156 -0
  274. scitex/fts/_kinds/_plot/_models/_plot_types/_bar.py +43 -0
  275. scitex/fts/_kinds/_plot/_models/_plot_types/_box.py +38 -0
  276. scitex/fts/_kinds/_plot/_models/_plot_types/_distribution.py +36 -0
  277. scitex/fts/_kinds/_plot/_models/_plot_types/_errorbar.py +60 -0
  278. scitex/fts/_kinds/_plot/_models/_plot_types/_histogram.py +30 -0
  279. scitex/fts/_kinds/_plot/_models/_plot_types/_image.py +61 -0
  280. scitex/fts/_kinds/_plot/_models/_plot_types/_line.py +57 -0
  281. scitex/fts/_kinds/_plot/_models/_plot_types/_scatter.py +30 -0
  282. scitex/fts/_kinds/_plot/_models/_plot_types/_seaborn.py +121 -0
  283. scitex/fts/_kinds/_plot/_models/_plot_types/_violin.py +36 -0
  284. scitex/fts/_kinds/_plot/_utils/__init__.py +129 -0
  285. scitex/fts/_kinds/_plot/_utils/_auto_layout.py +127 -0
  286. scitex/fts/_kinds/_plot/_utils/_calc_bounds.py +111 -0
  287. scitex/fts/_kinds/_plot/_utils/_const_sizes.py +48 -0
  288. scitex/fts/_kinds/_plot/_utils/_convert_coords.py +77 -0
  289. scitex/fts/_kinds/_plot/_utils/_get_template.py +178 -0
  290. scitex/fts/_kinds/_plot/_utils/_normalize.py +73 -0
  291. scitex/fts/_kinds/_plot/_utils/_plot_layout.py +397 -0
  292. scitex/fts/_kinds/_plot/_utils/_validate.py +197 -0
  293. scitex/fts/_kinds/_shape/__init__.py +141 -0
  294. scitex/fts/_kinds/_stats/__init__.py +56 -0
  295. scitex/fts/_kinds/_stats/_dataclasses/_Stats.py +423 -0
  296. scitex/fts/_kinds/_stats/_dataclasses/__init__.py +48 -0
  297. scitex/fts/_kinds/_table/__init__.py +72 -0
  298. scitex/fts/_kinds/_table/_latex/__init__.py +93 -0
  299. scitex/fts/_kinds/_table/_latex/_editor/__init__.py +11 -0
  300. scitex/fts/_kinds/_table/_latex/_editor/_app.py +725 -0
  301. scitex/fts/_kinds/_table/_latex/_export.py +279 -0
  302. scitex/fts/_kinds/_table/_latex/_figure_exporter.py +153 -0
  303. scitex/fts/_kinds/_table/_latex/_stats_formatter.py +274 -0
  304. scitex/fts/_kinds/_table/_latex/_table_exporter.py +362 -0
  305. scitex/fts/_kinds/_table/_latex/_utils.py +369 -0
  306. scitex/fts/_kinds/_table/_latex/_validator.py +445 -0
  307. scitex/fts/_kinds/_text/__init__.py +77 -0
  308. scitex/fts/_schemas/data_info.schema.json +75 -0
  309. scitex/fts/_schemas/encoding.schema.json +90 -0
  310. scitex/fts/_schemas/node.schema.json +145 -0
  311. scitex/fts/_schemas/render_manifest.schema.json +62 -0
  312. scitex/fts/_schemas/stats.schema.json +132 -0
  313. scitex/fts/_schemas/theme.schema.json +141 -0
  314. scitex/fts/_stats/__init__.py +48 -0
  315. scitex/fts/_stats/_dataclasses/_Stats.py +423 -0
  316. scitex/fts/_stats/_dataclasses/__init__.py +48 -0
  317. scitex/fts/_tables/__init__.py +65 -0
  318. scitex/fts/_tables/_latex/__init__.py +93 -0
  319. scitex/fts/_tables/_latex/_editor/__init__.py +11 -0
  320. scitex/fts/_tables/_latex/_editor/_app.py +725 -0
  321. scitex/fts/_tables/_latex/_export.py +279 -0
  322. scitex/fts/_tables/_latex/_figure_exporter.py +153 -0
  323. scitex/fts/_tables/_latex/_stats_formatter.py +274 -0
  324. scitex/fts/_tables/_latex/_table_exporter.py +362 -0
  325. scitex/fts/_tables/_latex/_utils.py +369 -0
  326. scitex/fts/_tables/_latex/_validator.py +445 -0
  327. scitex/gen/__init__.py +66 -25
  328. scitex/gen/misc.py +28 -0
  329. scitex/io/__init__.py +47 -32
  330. scitex/io/_load.py +87 -36
  331. scitex/io/_load_modules/__init__.py +10 -7
  332. scitex/io/_load_modules/_pandas.py +6 -1
  333. scitex/io/_save.py +299 -1556
  334. scitex/io/_save_modules/__init__.py +76 -19
  335. scitex/io/_save_modules/_figure_utils.py +90 -0
  336. scitex/io/_save_modules/_image_csv.py +497 -0
  337. scitex/io/_save_modules/_legends.py +91 -0
  338. scitex/io/_save_modules/_pltz_bundle.py +356 -0
  339. scitex/io/_save_modules/_pltz_stx.py +536 -0
  340. scitex/io/_save_modules/_stx_bundle.py +104 -0
  341. scitex/io/_save_modules/_symlink.py +96 -0
  342. scitex/io/_save_modules/_yaml.py +1 -1
  343. scitex/io/_save_modules/_zarr.py +64 -18
  344. scitex/io/bundle/README.md +212 -0
  345. scitex/io/bundle/__init__.py +110 -0
  346. scitex/io/{_bundle.py → bundle/_core.py} +168 -97
  347. scitex/io/bundle/_nested.py +713 -0
  348. scitex/io/bundle/_types.py +74 -0
  349. scitex/io/{_zip_bundle.py → bundle/_zip.py} +93 -45
  350. scitex/io/utils/h5_to_zarr.py +1 -1
  351. scitex/logging/__init__.py +108 -13
  352. scitex/logging/_errors.py +508 -0
  353. scitex/logging/_formatters.py +30 -6
  354. scitex/logging/_warnings.py +261 -0
  355. scitex/plt/__init__.py +4 -1
  356. scitex/plt/_figrecipe.py +236 -0
  357. scitex/plt/_subplots/_AxisWrapper.py +6 -0
  358. scitex/plt/_subplots/_AxisWrapperMixins/_UnitAwareMixin.py +112 -1
  359. scitex/plt/_subplots/_FigWrapper.py +15 -0
  360. scitex/plt/_subplots/_SubplotsWrapper.py +125 -489
  361. scitex/plt/_subplots/_export_as_csv.py +11 -0
  362. scitex/plt/_subplots/_export_as_csv_formatters/__init__.py +2 -0
  363. scitex/plt/_subplots/_export_as_csv_formatters/_format_pcolormesh.py +66 -0
  364. scitex/plt/_subplots/_export_as_csv_formatters/_format_stackplot.py +62 -0
  365. scitex/plt/_subplots/_export_as_csv_formatters/test_formatters.py +208 -0
  366. scitex/plt/_subplots/_fonts.py +71 -0
  367. scitex/plt/_subplots/_mm_layout.py +282 -0
  368. scitex/plt/gallery/__init__.py +99 -2
  369. scitex/plt/styles/_plot_postprocess.py +3 -1
  370. scitex/plt/utils/_configure_mpl.py +16 -19
  371. scitex/repro/_RandomStateManager.py +13 -8
  372. scitex/resource/__init__.py +19 -1
  373. scitex/resource/_utils/_get_env_info.py +13 -25
  374. scitex/schema/__init__.py +149 -160
  375. scitex/schema/_encoding.py +273 -0
  376. scitex/schema/_figure_elements.py +406 -0
  377. scitex/schema/_theme.py +360 -0
  378. scitex/schema/_validation.py +0 -98
  379. scitex/scholar/__init__.py +56 -14
  380. scitex/scholar/auth/ScholarAuthManager.py +1 -1
  381. scitex/scholar/auth/__init__.py +11 -2
  382. scitex/scholar/auth/providers/BaseAuthenticator.py +1 -1
  383. scitex/scholar/auth/providers/EZProxyAuthenticator.py +1 -1
  384. scitex/scholar/auth/providers/OpenAthensAuthenticator.py +1 -1
  385. scitex/scholar/auth/providers/ShibbolethAuthenticator.py +1 -1
  386. scitex/scholar/config/ScholarConfig.py +1 -1
  387. scitex/scholar/core/Scholar.py +1 -1
  388. scitex/session/_decorator.py +18 -16
  389. scitex/session/_lifecycle.py +9 -11
  390. scitex/session/template.py +9 -8
  391. scitex/sh/test_sh.py +72 -0
  392. scitex/sh/test_sh_simple.py +61 -0
  393. scitex/stats/__init__.py +221 -97
  394. scitex/stats/_schema.py +21 -22
  395. scitex/stats/descriptive/_circular.py +212 -351
  396. scitex/stats/descriptive/_describe.py +81 -132
  397. scitex/stats/descriptive/_nan.py +205 -433
  398. scitex/stats/descriptive/_real.py +127 -141
  399. scitex/str/_format_plot_text.py +5 -5
  400. scitex/str/_latex.py +26 -84
  401. scitex/str/_latex_fallback.py +53 -47
  402. scitex/web/_search_pubmed.py +5 -4
  403. scitex/writer/tests/test_diff_between.py +451 -0
  404. scitex/writer/tests/test_document_section.py +311 -0
  405. scitex/writer/tests/test_document_workflow.py +393 -0
  406. scitex/writer/tests/test_writer.py +361 -0
  407. scitex/writer/tests/test_writer_integration.py +303 -0
  408. {scitex-2.8.1.dist-info → scitex-2.10.2.dist-info}/METADATA +368 -183
  409. {scitex-2.8.1.dist-info → scitex-2.10.2.dist-info}/RECORD +412 -97
  410. scitex/scholar/docs/to_claude/guidelines/examples/mgmt/ARCHITECTURE_EXAMPLE.md +0 -905
  411. scitex/scholar/docs/to_claude/guidelines/examples/mgmt/BULLETIN_BOARD_EXAMPLE.md +0 -99
  412. scitex/scholar/docs/to_claude/guidelines/examples/mgmt/PROJECT_DESCRIPTION_EXAMPLE.md +0 -96
  413. {scitex-2.8.1.dist-info → scitex-2.10.2.dist-info}/WHEEL +0 -0
  414. {scitex-2.8.1.dist-info → scitex-2.10.2.dist-info}/entry_points.txt +0 -0
  415. {scitex-2.8.1.dist-info → scitex-2.10.2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,345 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: 2025-12-20
3
+ # File: /home/ywatanabe/proj/scitex-code/src/scitex/fts/_fig/_composite.py
4
+
5
+ """Composite figure renderer for FTS bundles.
6
+
7
+ Renders composite figures (kind=figure) containing multiple children.
8
+ ALWAYS re-renders from child's canonical (exports = optional cache only).
9
+
10
+ Design principles:
11
+ - Re-render children from canonical/encoding.json + payload/data.csv
12
+ - Recursively render nested figures (NOT from exports/)
13
+ - Apply container's theme for unified styling
14
+ - Generate geometry_px.json with flattened child geometry
15
+ - Cache key includes canonical_hash + effective_theme_hash + renderer_version
16
+ """
17
+
18
+ import io
19
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple
20
+
21
+ if TYPE_CHECKING:
22
+ from matplotlib.axes import Axes as MplAxes
23
+ from matplotlib.figure import Figure as MplFigure
24
+
25
+ from .._bundle._FTS import FTS
26
+ from ._dataclasses import Theme
27
+
28
+
29
+ def render_composite(
30
+ children: Dict[str, "FTS"],
31
+ layout: Dict,
32
+ size_mm: Optional[Dict[str, float]] = None,
33
+ theme: Optional["Theme"] = None,
34
+ dpi: int = 300,
35
+ ) -> Tuple["MplFigure", Dict]:
36
+ """Render composite figure with children in grid layout.
37
+
38
+ ALWAYS re-renders from child's canonical (exports = optional cache only).
39
+
40
+ Args:
41
+ children: Dict mapping child_name -> FTS object
42
+ layout: Layout specification {rows, cols, panels: [...]}
43
+ size_mm: Figure size in mm (default: 170x85 for two-column)
44
+ theme: Theme to apply to all children
45
+ dpi: Output DPI
46
+
47
+ Returns:
48
+ (figure, geometry_data)
49
+ """
50
+ import matplotlib.pyplot as plt
51
+ from matplotlib.gridspec import GridSpec
52
+
53
+ # Default size
54
+ if size_mm is None:
55
+ size_mm = {"width": 170, "height": 85}
56
+
57
+ # Convert mm to inches (matplotlib uses inches)
58
+ width_in = size_mm.get("width", 170) / 25.4
59
+ height_in = size_mm.get("height", 85) / 25.4
60
+
61
+ # Create figure with gridspec
62
+ rows = layout.get("rows", 1)
63
+ cols = layout.get("cols", 1)
64
+
65
+ fig = plt.figure(figsize=(width_in, height_in), dpi=dpi)
66
+ gs = GridSpec(rows, cols, figure=fig)
67
+
68
+ # Collect geometry data for all elements
69
+ geometry = {
70
+ "elements": [],
71
+ "panels": [],
72
+ }
73
+
74
+ # Render each panel
75
+ panels = layout.get("panels", [])
76
+ for panel_info in panels:
77
+ child_name = panel_info.get("child")
78
+ row = panel_info.get("row", 0)
79
+ col = panel_info.get("col", 0)
80
+ row_span = panel_info.get("row_span", 1)
81
+ col_span = panel_info.get("col_span", 1)
82
+ label = panel_info.get("label")
83
+
84
+ if child_name not in children:
85
+ continue
86
+
87
+ child = children[child_name]
88
+
89
+ # Create axes for this panel
90
+ ax = fig.add_subplot(gs[row : row + row_span, col : col + col_span])
91
+
92
+ # Render child into axes (ALWAYS re-render from canonical)
93
+ child_geometry = render_child_in_axes(ax, child, theme)
94
+
95
+ # Add panel label if specified
96
+ if label:
97
+ _add_panel_label(ax, label)
98
+
99
+ # Record panel geometry (flatten into parent's figure_px space)
100
+ panel_geometry = {
101
+ "child": child_name,
102
+ "child_id": panel_info.get("child_id"),
103
+ "label": label,
104
+ "position": {"row": row, "col": col, "row_span": row_span, "col_span": col_span},
105
+ "bbox_figure": _get_axes_bbox(ax, fig),
106
+ "child_elements": child_geometry.get("elements", []),
107
+ }
108
+ geometry["panels"].append(panel_geometry)
109
+
110
+ plt.tight_layout()
111
+
112
+ return fig, geometry
113
+
114
+
115
+ def render_child_in_axes(
116
+ ax: "MplAxes",
117
+ child: "FTS",
118
+ theme: Optional["Theme"] = None,
119
+ ) -> Dict:
120
+ """Render child FTS into axes.
121
+
122
+ ALWAYS re-renders from canonical (exports = optional cache only):
123
+ - kind=plot: Re-render from canonical/spec.json + payload/data.csv
124
+ - kind=figure: Recursively render children (NOT use exports/figure.png)
125
+
126
+ Args:
127
+ ax: Matplotlib axes to render into
128
+ child: Child FTS bundle
129
+ theme: Theme to apply (overrides child's theme)
130
+
131
+ Returns:
132
+ Geometry data for this child
133
+ """
134
+ geometry = {"elements": []}
135
+
136
+ if child.node.is_leaf_kind():
137
+ # Leaf node: render from encoding + payload
138
+ geometry = _render_leaf_in_axes(ax, child, theme)
139
+ elif child.node.is_composite_kind():
140
+ # Composite: recursive render (nested figure)
141
+ geometry = _render_composite_in_axes(ax, child, theme)
142
+
143
+ return geometry
144
+
145
+
146
+ def _render_leaf_in_axes(
147
+ ax: "MplAxes",
148
+ child: "FTS",
149
+ theme: Optional["Theme"] = None,
150
+ ) -> Dict:
151
+ """Render leaf FTS (plot/table/stats) into axes.
152
+
153
+ Uses child's pre-rendered export (artifacts/exports/figure.png) to embed
154
+ the visualization. This approach ensures visual consistency with the
155
+ original rendered plot.
156
+ """
157
+ import io
158
+
159
+ import matplotlib.pyplot as plt
160
+
161
+ geometry = {"elements": []}
162
+
163
+ # Try to use pre-rendered export image
164
+ try:
165
+ storage = child.storage
166
+
167
+ # Check for exported image
168
+ if storage.exists("artifacts/exports/figure.png"):
169
+ img_data = storage.read("artifacts/exports/figure.png")
170
+ img = plt.imread(io.BytesIO(img_data), format="png")
171
+
172
+ # Display image in axes, filling the entire axes
173
+ ax.imshow(img, aspect="auto", extent=[0, 1, 0, 1])
174
+ ax.set_xlim(0, 1)
175
+ ax.set_ylim(0, 1)
176
+ ax.axis("off")
177
+
178
+ geometry["elements"].append({
179
+ "type": "embedded_image",
180
+ "source": "artifacts/exports/figure.png",
181
+ })
182
+ return geometry
183
+
184
+ except Exception as e:
185
+ pass # Fall through to placeholder
186
+
187
+ # Fallback: draw placeholder with child name
188
+ ax.text(
189
+ 0.5,
190
+ 0.5,
191
+ f"[{child.node.name or child.node.id[:8]}]",
192
+ ha="center",
193
+ va="center",
194
+ transform=ax.transAxes,
195
+ fontsize=10,
196
+ )
197
+ ax.set_xlim(0, 1)
198
+ ax.set_ylim(0, 1)
199
+
200
+ return geometry
201
+
202
+
203
+ def _render_composite_in_axes(
204
+ ax: "MplAxes",
205
+ child: "FTS",
206
+ theme: Optional["Theme"] = None,
207
+ ) -> Dict:
208
+ """Render composite FTS (figure) into axes.
209
+
210
+ Creates a nested gridspec for the child's children.
211
+ """
212
+ from .._bundle._children import load_embedded_children
213
+
214
+ geometry = {"elements": [], "nested_panels": []}
215
+
216
+ # Load child's children
217
+ child_children = child.load_children()
218
+ if not child_children:
219
+ ax.text(
220
+ 0.5,
221
+ 0.5,
222
+ f"[Empty: {child.node.name or child.node.id[:8]}]",
223
+ ha="center",
224
+ va="center",
225
+ transform=ax.transAxes,
226
+ )
227
+ return geometry
228
+
229
+ # Get child's layout
230
+ child_layout = child.node.layout or {"rows": 1, "cols": 1, "panels": []}
231
+
232
+ # For nested figures, we need to subdivide the axes
233
+ # This is a simplified approach - full implementation would use inset axes
234
+ from mpl_toolkits.axes_grid1 import make_axes_locatable
235
+
236
+ ax.axis("off") # Hide the parent axes
237
+
238
+ # Create a figure-in-figure effect using inset axes
239
+ # For simplicity, render children side by side
240
+ n_children = len(child_children)
241
+ width = 1.0 / max(n_children, 1)
242
+
243
+ for i, (grandchild_name, grandchild) in enumerate(child_children.items()):
244
+ # Create inset axes
245
+ inset_bounds = [width * i, 0, width * 0.95, 1]
246
+ inset_ax = ax.inset_axes(inset_bounds)
247
+
248
+ # Recursively render grandchild
249
+ grandchild_geometry = render_child_in_axes(inset_ax, grandchild, theme)
250
+
251
+ geometry["nested_panels"].append(
252
+ {
253
+ "child": grandchild_name,
254
+ "child_id": grandchild.node.id if grandchild.node else None,
255
+ "elements": grandchild_geometry.get("elements", []),
256
+ }
257
+ )
258
+
259
+ return geometry
260
+
261
+
262
+ def _add_panel_label(ax: "MplAxes", label: str) -> None:
263
+ """Add panel label (A, B, C...) to axes."""
264
+ ax.text(
265
+ -0.1,
266
+ 1.05,
267
+ label,
268
+ transform=ax.transAxes,
269
+ fontsize=14,
270
+ fontweight="bold",
271
+ va="bottom",
272
+ ha="right",
273
+ )
274
+
275
+
276
+ def _get_axes_bbox(ax: "MplAxes", fig: "MplFigure") -> Dict[str, float]:
277
+ """Get axes bounding box in figure coordinates."""
278
+ bbox = ax.get_position()
279
+ return {
280
+ "x": bbox.x0,
281
+ "y": bbox.y0,
282
+ "width": bbox.width,
283
+ "height": bbox.height,
284
+ }
285
+
286
+
287
+ def check_cache_valid(
288
+ child: "FTS",
289
+ current_theme_hash: str,
290
+ renderer_version: str = "1.0.0",
291
+ ) -> bool:
292
+ """Check if child's cached exports are still valid.
293
+
294
+ Cache is valid only if ALL match:
295
+ 1. artifacts/exports/figure.png exists
296
+ 2. artifacts/cache/render_manifest.json exists
297
+ 3. render_manifest.canonical_hash matches current hash
298
+ 4. render_manifest.effective_theme_hash matches current theme
299
+ 5. render_manifest.renderer_version matches current renderer
300
+
301
+ Args:
302
+ child: Child FTS bundle
303
+ current_theme_hash: Hash of current effective theme
304
+ renderer_version: Current renderer version
305
+
306
+ Returns:
307
+ True if cache is valid and can be used
308
+ """
309
+ from .._bundle._storage import get_storage
310
+ from .._bundle._saver import compute_canonical_hash
311
+
312
+ storage = get_storage(child.path)
313
+
314
+ # Check exports exist
315
+ if not storage.exists("artifacts/exports/figure.png"):
316
+ return False
317
+
318
+ # Check manifest exists
319
+ manifest = storage.read_json("artifacts/cache/render_manifest.json")
320
+ if manifest is None:
321
+ return False
322
+
323
+ # Check canonical hash
324
+ current_canonical_hash = compute_canonical_hash(storage)
325
+ if manifest.get("canonical_hash") != current_canonical_hash:
326
+ return False
327
+
328
+ # Check theme hash
329
+ if manifest.get("effective_theme_hash") != current_theme_hash:
330
+ return False
331
+
332
+ # Check renderer version
333
+ if manifest.get("renderer_version") != renderer_version:
334
+ return False
335
+
336
+ return True
337
+
338
+
339
+ __all__ = [
340
+ "render_composite",
341
+ "render_child_in_axes",
342
+ "check_cache_valid",
343
+ ]
344
+
345
+ # EOF
@@ -0,0 +1,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