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,216 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # Timestamp: "2025-12-21 06:10:24 (ywatanabe)"
4
+ # File: /home/ywatanabe/proj/scitex-code/src/scitex/fts/_bundle/_children.py
5
+
6
+ # Timestamp: 2025-12-20
7
+
8
+ """Child embedding for FTS composite bundles.
9
+
10
+ This module handles embedding child bundles into composite FTS containers.
11
+ Children are stored in the children/ directory with UUID-based names to avoid
12
+ collisions, while labels and positions are stored in layout.panels.
13
+
14
+ Design principles:
15
+ - Use child's own node.id (from canonical/spec.json) for naming
16
+ - Handle collisions with _2, _3, etc. suffixes
17
+ - Return (child_name, child_id) tuple for identity tracking
18
+ - Always validate child is valid FTS bundle
19
+ """
20
+
21
+ import tempfile
22
+ import zipfile
23
+ from pathlib import Path
24
+ from typing import TYPE_CHECKING
25
+ from typing import Dict, Tuple, Union
26
+
27
+ from ._storage import Storage
28
+ from ._storage import get_storage
29
+
30
+ if TYPE_CHECKING:
31
+ from ._FTS import FTS
32
+
33
+
34
+ class ValidationError(Exception):
35
+ """Error raised when bundle validation fails."""
36
+
37
+ pass
38
+
39
+
40
+ def embed_child(
41
+ container_storage: Storage,
42
+ child_path: Union[str, Path],
43
+ validate: bool = False, # Default False for WIP workflows
44
+ ) -> Tuple[str, str]:
45
+ """Embed a child bundle into container's children/ directory.
46
+
47
+ Uses the child's own node.id (from canonical/spec.json) for naming to
48
+ preserve identity. Handles collisions with _2, _3, etc. suffixes.
49
+
50
+ Args:
51
+ container_storage: Storage for the container bundle
52
+ child_path: Path to child bundle (.zip or directory)
53
+ validate: Validate child is valid FTS bundle (default False for WIP)
54
+
55
+ Returns:
56
+ (child_name, child_id):
57
+ child_name: Filename in children/ (e.g., "690b1931.zip" or "690b1931_2.zip")
58
+ child_id: Full UUID from child's spec.json (for identity tracking)
59
+
60
+ Raises:
61
+ ValidationError: If validate=True and child is invalid FTS bundle
62
+ FileNotFoundError: If child path doesn't exist
63
+ """
64
+ child_path = Path(child_path)
65
+ if not child_path.exists():
66
+ raise FileNotFoundError(f"Child bundle not found: {child_path}")
67
+
68
+ # Load child bundle to get its node.id and optionally validate
69
+ from ._FTS import FTS
70
+
71
+ child_fts = FTS(child_path)
72
+
73
+ if validate:
74
+ errors = child_fts.validate()
75
+ if errors:
76
+ raise ValidationError(f"Invalid child bundle: {errors}")
77
+
78
+ # Get child's identity
79
+ full_child_id = child_fts.node.id
80
+ short_id = full_child_id[:8]
81
+
82
+ # Determine child filename with collision handling
83
+ child_name = f"{short_id}.zip"
84
+ suffix = 2
85
+ while container_storage.exists(f"children/{child_name}"):
86
+ child_name = f"{short_id}_{suffix}.zip"
87
+ suffix += 1
88
+
89
+ # Copy child bundle into children/ directory
90
+ _copy_child_to_storage(container_storage, child_path, child_name)
91
+
92
+ return child_name, full_child_id
93
+
94
+
95
+ def _copy_child_to_storage(
96
+ container_storage: Storage,
97
+ child_path: Path,
98
+ child_name: str,
99
+ ) -> None:
100
+ """Copy child bundle into container's children/ directory.
101
+
102
+ Handles both ZIP and directory child bundles, always storing as ZIP.
103
+ The child ZIP uses a prefix matching its name (for proper unzip behavior).
104
+ """
105
+ import io
106
+
107
+ dest_path = f"children/{child_name}"
108
+ # Use child_name (e.g., "6352df06.zip") stem as prefix
109
+ child_prefix = child_name.replace(".zip", "") + "/"
110
+
111
+ if child_path.suffix == ".zip":
112
+ # Read ZIP contents and copy to container
113
+ with zipfile.ZipFile(child_path, "r") as zf:
114
+ buf = io.BytesIO()
115
+ with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as out_zf:
116
+ # Get original prefix (e.g., "my_plot/" from "my_plot.zip")
117
+ orig_prefix = child_path.stem + "/"
118
+ for item in zf.namelist():
119
+ if item.endswith("/"): # Skip directory entries
120
+ continue
121
+ data = zf.read(item)
122
+ # Strip original prefix and add new prefix
123
+ if item.startswith(orig_prefix):
124
+ rel_path = item[len(orig_prefix) :]
125
+ else:
126
+ rel_path = item
127
+ out_zf.writestr(child_prefix + rel_path, data)
128
+
129
+ container_storage.write(dest_path, buf.getvalue())
130
+ else:
131
+ # Directory bundle: create ZIP from directory
132
+ buf = io.BytesIO()
133
+ with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as out_zf:
134
+ for item in child_path.rglob("*"):
135
+ if item.is_file():
136
+ rel_path = item.relative_to(child_path)
137
+ out_zf.writestr(
138
+ child_prefix + str(rel_path), item.read_bytes()
139
+ )
140
+
141
+ container_storage.write(dest_path, buf.getvalue())
142
+
143
+
144
+ def load_embedded_children(
145
+ container_path: Union[str, Path],
146
+ ) -> Dict[str, "FTS"]:
147
+ """Load all embedded children from children/ directory.
148
+
149
+ Args:
150
+ container_path: Path to container bundle
151
+
152
+ Returns:
153
+ Dict mapping child_name -> FTS object
154
+ """
155
+ from ._FTS import FTS
156
+
157
+ container_path = Path(container_path)
158
+ storage = get_storage(container_path)
159
+
160
+ children = {}
161
+ for item in storage.list():
162
+ if item.startswith("children/") and item.endswith(".zip"):
163
+ child_name = item.split("/", 1)[1]
164
+ # Extract child to temp and load
165
+ child_fts = _load_child_from_storage(storage, child_name)
166
+ if child_fts:
167
+ children[child_name] = child_fts
168
+
169
+ return children
170
+
171
+
172
+ def _load_child_from_storage(
173
+ storage: Storage,
174
+ child_name: str,
175
+ ) -> "FTS":
176
+ """Load a single child bundle from storage.
177
+
178
+ Extracts child ZIP to temp directory and loads as FTS.
179
+ The temp file name matches the prefix inside the ZIP.
180
+
181
+ NOTE: Temp files are kept alive because FTS.validate() needs to
182
+ access storage for file existence checks. The OS will clean up
183
+ temp files eventually, or they can be cleaned up explicitly.
184
+ """
185
+ from ._FTS import FTS
186
+
187
+ child_path = f"children/{child_name}"
188
+
189
+ # Check if child exists
190
+ if not storage.exists(child_path):
191
+ return None
192
+
193
+ # Read child ZIP data
194
+ child_data = storage.read(child_path)
195
+
196
+ # Create temp directory and use matching filename
197
+ # The child ZIP has files prefixed with child_name stem (e.g., "6352df06/canonical/...")
198
+ # ZipStorage uses the file stem as prefix, so temp file must match
199
+ tmp_dir = Path(tempfile.mkdtemp())
200
+ tmp_path = tmp_dir / child_name # Use same name to match internal prefix
201
+
202
+ tmp_path.write_bytes(child_data)
203
+ fts = FTS(tmp_path)
204
+ # Store temp path reference on FTS object for potential cleanup
205
+ fts._temp_path = tmp_path
206
+ fts._temp_dir = tmp_dir
207
+ return fts
208
+
209
+
210
+ __all__ = [
211
+ "embed_child",
212
+ "load_embedded_children",
213
+ "ValidationError",
214
+ ]
215
+
216
+ # EOF
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: 2025-12-20
3
+ # File: /home/ywatanabe/proj/scitex-code/src/scitex/fsb/_conversion/__init__.py
4
+
5
+ """FTS Conversion Utilities."""
6
+
7
+ from .bundle2dict import bundle_to_dict
8
+ from .dict2bundle import dict_to_bundle
9
+
10
+ __all__ = [
11
+ "bundle_to_dict",
12
+ "dict_to_bundle",
13
+ ]
14
+
15
+ # EOF
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: 2025-12-20
3
+ # File: /home/ywatanabe/proj/scitex-code/src/scitex/fsb/_conversion/bundle2dict.py
4
+
5
+ """Convert FTS bundle to dictionary."""
6
+
7
+ from typing import TYPE_CHECKING, Any, Dict
8
+
9
+ if TYPE_CHECKING:
10
+ from .._FSB import FTS
11
+
12
+
13
+ def bundle_to_dict(bundle: "FTS") -> Dict[str, Any]:
14
+ """Convert Figure-Statistics Bundle to a flat dictionary.
15
+
16
+ Args:
17
+ bundle: Figure-Statistics Bundle instance.
18
+
19
+ Returns:
20
+ Dictionary with all bundle data.
21
+ """
22
+ result = {
23
+ "path": str(bundle.path),
24
+ "is_zip": bundle.path.suffix == ".zip",
25
+ "type": bundle.bundle_type,
26
+ }
27
+
28
+ if bundle.node:
29
+ result["node"] = bundle.node.to_dict()
30
+ if bundle._encoding:
31
+ result["encoding"] = bundle._encoding.to_dict()
32
+ if bundle._theme:
33
+ result["theme"] = bundle._theme.to_dict()
34
+ if bundle._stats:
35
+ result["stats"] = bundle._stats.to_dict()
36
+ if bundle._data_info:
37
+ result["data_info"] = bundle._data_info.to_dict()
38
+
39
+ return result
40
+
41
+
42
+ __all__ = ["bundle_to_dict"]
43
+
44
+ # EOF
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: 2025-12-20
3
+ # File: /home/ywatanabe/proj/scitex-code/src/scitex/fsb/_conversion/dict2bundle.py
4
+
5
+ """Convert dictionary to FTS bundle."""
6
+
7
+ from pathlib import Path
8
+ from typing import TYPE_CHECKING, Any, Dict, Union
9
+
10
+ if TYPE_CHECKING:
11
+ from .._FSB import FTS
12
+
13
+
14
+ def dict_to_bundle(
15
+ data: Dict[str, Any],
16
+ path: Union[str, Path],
17
+ ) -> "FTS":
18
+ """Create Figure-Statistics Bundle from dictionary data.
19
+
20
+ Args:
21
+ data: Dictionary with 'node', 'encoding', 'theme', etc.
22
+ path: Path for the new bundle.
23
+
24
+ Returns:
25
+ Figure-Statistics Bundle instance.
26
+ """
27
+ from .._FSB import FTS
28
+
29
+ node_data = data.get("node", {})
30
+ node_type = node_data.get("type", "plot")
31
+ name = node_data.get("name")
32
+ size_mm = node_data.get("size_mm")
33
+
34
+ bundle = FTS(path, create=True, node_type=node_type, name=name, size_mm=size_mm)
35
+
36
+ if "encoding" in data:
37
+ bundle.encoding = data["encoding"]
38
+ if "theme" in data:
39
+ bundle.theme = data["theme"]
40
+ if "stats" in data:
41
+ bundle.stats = data["stats"]
42
+ if "data_info" in data:
43
+ bundle.data_info = data["data_info"]
44
+
45
+ return bundle
46
+
47
+
48
+ __all__ = ["dict_to_bundle"]
49
+
50
+ # EOF
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: 2025-12-20
3
+ # File: /home/ywatanabe/proj/scitex-code/src/scitex/fsb/_dataclasses/Axes.py
4
+
5
+ """Axes - Axis configuration for plot nodes."""
6
+
7
+ from dataclasses import dataclass
8
+ from typing import Any, Dict, Optional, Tuple
9
+
10
+
11
+ @dataclass
12
+ class Axes:
13
+ """Axis configuration for plot nodes.
14
+
15
+ Defines axis limits, scales, and labels.
16
+ """
17
+
18
+ xlim: Optional[Tuple[float, float]] = None
19
+ ylim: Optional[Tuple[float, float]] = None
20
+ xscale: str = "linear" # linear, log, symlog
21
+ yscale: str = "linear"
22
+ xlabel: Optional[str] = None
23
+ ylabel: Optional[str] = None
24
+
25
+ def to_dict(self) -> Dict[str, Any]:
26
+ result = {}
27
+ if self.xlim is not None:
28
+ result["xlim"] = list(self.xlim)
29
+ if self.ylim is not None:
30
+ result["ylim"] = list(self.ylim)
31
+ if self.xscale != "linear":
32
+ result["xscale"] = self.xscale
33
+ if self.yscale != "linear":
34
+ result["yscale"] = self.yscale
35
+ if self.xlabel:
36
+ result["xlabel"] = self.xlabel
37
+ if self.ylabel:
38
+ result["ylabel"] = self.ylabel
39
+ return result
40
+
41
+ @classmethod
42
+ def from_dict(cls, data: Dict[str, Any]) -> "Axes":
43
+ xlim = data.get("xlim")
44
+ ylim = data.get("ylim")
45
+ return cls(
46
+ xlim=tuple(xlim) if xlim else None,
47
+ ylim=tuple(ylim) if ylim else None,
48
+ xscale=data.get("xscale", "linear"),
49
+ yscale=data.get("yscale", "linear"),
50
+ xlabel=data.get("xlabel"),
51
+ ylabel=data.get("ylabel"),
52
+ )
53
+
54
+
55
+ __all__ = ["Axes"]
56
+
57
+ # EOF
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: 2025-12-20
3
+ # File: /home/ywatanabe/proj/scitex-code/src/scitex/fsb/_dataclasses/BBox.py
4
+
5
+ """BBox - Bounding box in normalized coordinates (0-1)."""
6
+
7
+ from dataclasses import dataclass
8
+ from typing import Any, Dict
9
+
10
+
11
+ @dataclass
12
+ class BBox:
13
+ """Bounding box in normalized coordinates (0-1).
14
+
15
+ Used for positioning elements within parent containers.
16
+ Origin is top-left, y increases downward.
17
+ """
18
+
19
+ x0: float = 0.0
20
+ y0: float = 0.0
21
+ x1: float = 1.0
22
+ y1: float = 1.0
23
+
24
+ def __post_init__(self):
25
+ # Clamp to valid range
26
+ self.x0 = max(0.0, min(1.0, self.x0))
27
+ self.y0 = max(0.0, min(1.0, self.y0))
28
+ self.x1 = max(0.0, min(1.0, self.x1))
29
+ self.y1 = max(0.0, min(1.0, self.y1))
30
+
31
+ @property
32
+ def width(self) -> float:
33
+ return self.x1 - self.x0
34
+
35
+ @property
36
+ def height(self) -> float:
37
+ return self.y1 - self.y0
38
+
39
+ def to_dict(self) -> Dict[str, float]:
40
+ return {"x0": self.x0, "y0": self.y0, "x1": self.x1, "y1": self.y1}
41
+
42
+ @classmethod
43
+ def from_dict(cls, data: Dict[str, Any]) -> "BBox":
44
+ return cls(
45
+ x0=data.get("x0", 0.0),
46
+ y0=data.get("y0", 0.0),
47
+ x1=data.get("x1", 1.0),
48
+ y1=data.get("y1", 1.0),
49
+ )
50
+
51
+
52
+ __all__ = ["BBox"]
53
+
54
+ # EOF
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: 2025-12-20
3
+ # File: /home/ywatanabe/proj/scitex-code/src/scitex/fsb/_dataclasses/ColumnDef.py
4
+
5
+ """ColumnDef - Column definition with metadata."""
6
+
7
+ from dataclasses import dataclass
8
+ from typing import Any, Dict, List, Optional
9
+
10
+
11
+ @dataclass
12
+ class ColumnDef:
13
+ """Column definition with metadata."""
14
+
15
+ name: str
16
+ dtype: str # float64, int64, string, bool, datetime, category
17
+ description: Optional[str] = None
18
+ unit: Optional[str] = None # Physical unit: mV, ms, Hz, etc.
19
+ role: Optional[str] = None # x, y, group, subject, condition, time, error, weight
20
+ missing_count: Optional[int] = None
21
+ unique_count: Optional[int] = None
22
+ min: Optional[float] = None
23
+ max: Optional[float] = None
24
+ mean: Optional[float] = None
25
+ std: Optional[float] = None
26
+ categories: Optional[List[str]] = None
27
+
28
+ def to_dict(self) -> Dict[str, Any]:
29
+ result = {"name": self.name, "dtype": self.dtype}
30
+ for attr in [
31
+ "description",
32
+ "unit",
33
+ "role",
34
+ "missing_count",
35
+ "unique_count",
36
+ "min",
37
+ "max",
38
+ "mean",
39
+ "std",
40
+ "categories",
41
+ ]:
42
+ val = getattr(self, attr)
43
+ if val is not None:
44
+ result[attr] = val
45
+ return result
46
+
47
+ @classmethod
48
+ def from_dict(cls, data: Any) -> "ColumnDef":
49
+ # Handle case where column is just a string (column name)
50
+ if isinstance(data, str):
51
+ return cls(name=data, dtype="unknown")
52
+ if not isinstance(data, dict):
53
+ return cls(name="", dtype="unknown") # Default
54
+ return cls(
55
+ name=data.get("name", ""),
56
+ dtype=data.get("dtype", "float64"),
57
+ description=data.get("description"),
58
+ unit=data.get("unit"),
59
+ role=data.get("role"),
60
+ missing_count=data.get("missing_count"),
61
+ unique_count=data.get("unique_count"),
62
+ min=data.get("min"),
63
+ max=data.get("max"),
64
+ mean=data.get("mean"),
65
+ std=data.get("std"),
66
+ categories=data.get("categories"),
67
+ )
68
+
69
+
70
+ __all__ = ["ColumnDef"]
71
+
72
+ # EOF
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: 2025-12-20
3
+ # File: /home/ywatanabe/proj/scitex-code/src/scitex/fsb/_dataclasses/DataFormat.py
4
+
5
+ """DataFormat - Data file format specification."""
6
+
7
+ from dataclasses import dataclass
8
+ from typing import Any, Dict
9
+
10
+
11
+ @dataclass
12
+ class DataFormat:
13
+ """Data file format specification."""
14
+
15
+ type: str = "csv" # csv, tsv, parquet, json
16
+ encoding: str = "utf-8"
17
+ delimiter: str = ","
18
+ header_row: int = 0
19
+
20
+ def to_dict(self) -> Dict[str, Any]:
21
+ return {
22
+ "type": self.type,
23
+ "encoding": self.encoding,
24
+ "delimiter": self.delimiter,
25
+ "header_row": self.header_row,
26
+ }
27
+
28
+ @classmethod
29
+ def from_dict(cls, data: Dict[str, Any]) -> "DataFormat":
30
+ return cls(
31
+ type=data.get("type", "csv"),
32
+ encoding=data.get("encoding", "utf-8"),
33
+ delimiter=data.get("delimiter", ","),
34
+ header_row=data.get("header_row", 0),
35
+ )
36
+
37
+
38
+ __all__ = ["DataFormat"]
39
+
40
+ # EOF
@@ -0,0 +1,135 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: 2025-12-20
3
+ # File: /home/ywatanabe/proj/scitex-code/src/scitex/fsb/_dataclasses/DataInfo.py
4
+
5
+ """DataInfo - Complete data info specification for a bundle."""
6
+
7
+ import json
8
+ from dataclasses import dataclass, field
9
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional
10
+
11
+ from ._ColumnDef import ColumnDef
12
+ from ._DataFormat import DataFormat
13
+ from ._DataSource import DataSource
14
+
15
+ if TYPE_CHECKING:
16
+ import pandas
17
+
18
+ DATA_INFO_VERSION = "1.0.0"
19
+
20
+
21
+ @dataclass
22
+ class DataInfo:
23
+ """Complete data info specification for a bundle.
24
+
25
+ Stored in data/data_info.json.
26
+ """
27
+
28
+ columns: List[ColumnDef] = field(default_factory=list)
29
+ source: Optional[DataSource] = None
30
+ format: DataFormat = field(default_factory=DataFormat)
31
+ shape: Optional[Dict[str, int]] = None # rows, columns
32
+
33
+ # Schema metadata
34
+ schema_name: str = "fsb.data_info"
35
+ schema_version: str = DATA_INFO_VERSION
36
+
37
+ def to_dict(self) -> Dict[str, Any]:
38
+ result = {
39
+ "columns": [c.to_dict() for c in self.columns],
40
+ "format": self.format.to_dict(),
41
+ }
42
+ if self.source:
43
+ result["source"] = self.source.to_dict()
44
+ if self.shape:
45
+ result["shape"] = self.shape
46
+ return result
47
+
48
+ def to_json(self, indent: int = 2) -> str:
49
+ return json.dumps(self.to_dict(), indent=indent)
50
+
51
+ @classmethod
52
+ def from_dict(cls, data: Dict[str, Any]) -> "DataInfo":
53
+ source = None
54
+ if "source" in data:
55
+ source = DataSource.from_dict(data["source"])
56
+ return cls(
57
+ columns=[ColumnDef.from_dict(c) for c in data.get("columns", [])],
58
+ source=source,
59
+ format=DataFormat.from_dict(data.get("format", {})),
60
+ shape=data.get("shape"),
61
+ )
62
+
63
+ @classmethod
64
+ def from_json(cls, json_str: str) -> "DataInfo":
65
+ return cls.from_dict(json.loads(json_str))
66
+
67
+ def get_column(self, name: str) -> Optional[ColumnDef]:
68
+ """Get column definition by name."""
69
+ for col in self.columns:
70
+ if col.name == name:
71
+ return col
72
+ return None
73
+
74
+ @classmethod
75
+ def from_dataframe(
76
+ cls, df: "pandas.DataFrame", source_path: Optional[str] = None
77
+ ) -> "DataInfo":
78
+ """Create DataInfo from a pandas DataFrame."""
79
+ import pandas as pd
80
+
81
+ columns = []
82
+ for col_name in df.columns:
83
+ col = df[col_name]
84
+ dtype = str(col.dtype)
85
+
86
+ # Map pandas dtypes to our types
87
+ if "float" in dtype:
88
+ dtype_str = "float64"
89
+ elif "int" in dtype:
90
+ dtype_str = "int64"
91
+ elif "bool" in dtype:
92
+ dtype_str = "bool"
93
+ elif "datetime" in dtype:
94
+ dtype_str = "datetime"
95
+ elif col.dtype.name == "category":
96
+ dtype_str = "category"
97
+ else:
98
+ dtype_str = "string"
99
+
100
+ col_def = ColumnDef(
101
+ name=str(col_name),
102
+ dtype=dtype_str,
103
+ missing_count=int(col.isna().sum()),
104
+ unique_count=int(col.nunique()),
105
+ )
106
+
107
+ # Add numeric stats
108
+ if dtype_str in ("float64", "int64"):
109
+ col_def.min = float(col.min()) if not pd.isna(col.min()) else None
110
+ col_def.max = float(col.max()) if not pd.isna(col.max()) else None
111
+ col_def.mean = float(col.mean()) if not pd.isna(col.mean()) else None
112
+ col_def.std = float(col.std()) if not pd.isna(col.std()) else None
113
+
114
+ # Add categories
115
+ if dtype_str == "category" or (
116
+ dtype_str == "string" and col.nunique() < 20
117
+ ):
118
+ col_def.categories = col.dropna().unique().tolist()
119
+
120
+ columns.append(col_def)
121
+
122
+ source = None
123
+ if source_path:
124
+ source = DataSource(path=source_path)
125
+
126
+ return cls(
127
+ columns=columns,
128
+ source=source,
129
+ shape={"rows": len(df), "columns": len(df.columns)},
130
+ )
131
+
132
+
133
+ __all__ = ["DATA_INFO_VERSION", "DataInfo"]
134
+
135
+ # EOF