scitex 2.10.2__py3-none-any.whl → 2.11.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 (363) hide show
  1. scitex/__init__.py +1 -4
  2. scitex/__version__.py +1 -1
  3. scitex/_install_guide.py +14 -2
  4. scitex/bridge/_figrecipe.py +1 -1
  5. scitex/bridge/_helpers.py +1 -1
  6. scitex/bridge/_plt_vis.py +1 -1
  7. scitex/bridge/_stats_plt.py +1 -1
  8. scitex/bridge/_stats_vis.py +2 -2
  9. scitex/{fig → canvas}/__init__.py +84 -96
  10. scitex/{fig → canvas}/backend/_parser.py +1 -1
  11. scitex/{fig → canvas}/canvas.py +13 -14
  12. scitex/{fts/_fig/_editor → canvas/editor}/_defaults.py +2 -2
  13. scitex/{fig → canvas}/editor/edit/__init__.py +11 -14
  14. scitex/{fig → canvas}/editor/edit/bundle_resolver.py +56 -48
  15. scitex/{fig → canvas}/editor/edit/editor_launcher.py +79 -26
  16. scitex/{fts/_fig/_editor/_cui/_panel_loader.py → canvas/editor/edit/panel_loader.py} +8 -8
  17. scitex/{fts/_fig/_editor/_gui/_flask_editor → canvas/editor/flask_editor}/_bbox.py +2 -1
  18. scitex/{fts/_fig/_editor/_gui/_flask_editor → canvas/editor/flask_editor}/_core.py +84 -84
  19. scitex/{fts/_fig/_editor/_gui/_flask_editor → canvas/editor/flask_editor}/_renderer.py +7 -6
  20. scitex/{fts/_fig/_editor/_gui/_flask_editor → canvas/editor/flask_editor}/static/css/features/canvas.css +2 -2
  21. scitex/{fig → canvas}/editor/flask_editor/static/css/features/panel-grid.css +1 -1
  22. scitex/{fig → canvas}/editor/flask_editor/static/js/core/api.js +3 -4
  23. scitex/{fig → canvas}/editor/flask_editor/static/js/editor/preview.js +5 -5
  24. scitex/{fig → canvas}/editor/flask_editor/templates/_html.py +3 -3
  25. scitex/{fig → canvas}/editor/flask_editor/templates/_scripts.py +10 -10
  26. scitex/{fig → canvas}/editor/flask_editor/templates/_styles.py +3 -3
  27. scitex/{fig → canvas}/io/__init__.py +32 -38
  28. scitex/{fig → canvas}/io/_bundle.py +217 -154
  29. scitex/{fig → canvas}/io/_canvas.py +1 -1
  30. scitex/{fig → canvas}/io/_data.py +1 -1
  31. scitex/{fig → canvas}/io/_export.py +1 -1
  32. scitex/{fig → canvas}/io/_load.py +1 -1
  33. scitex/{fig → canvas}/io/_panel.py +1 -1
  34. scitex/{fig → canvas}/io/_save.py +1 -1
  35. scitex/{fig → canvas}/model/__init__.py +1 -1
  36. scitex/{fig → canvas}/model/_annotations.py +1 -1
  37. scitex/{fig → canvas}/model/_axes.py +1 -1
  38. scitex/{fig → canvas}/model/_figure.py +1 -1
  39. scitex/{fig → canvas}/model/_guides.py +1 -1
  40. scitex/{fig → canvas}/model/_plot.py +1 -1
  41. scitex/{fig → canvas}/model/_styles.py +1 -1
  42. scitex/{fig → canvas}/utils/__init__.py +1 -1
  43. scitex/cli/convert.py +10 -6
  44. scitex/diagram/README.md +7 -7
  45. scitex/io/__init__.py +7 -19
  46. scitex/io/_load.py +15 -19
  47. scitex/io/_load_modules/_canvas.py +2 -2
  48. scitex/io/_load_modules/_con.py +5 -5
  49. scitex/io/_load_modules/_eeg.py +16 -12
  50. scitex/io/_save.py +11 -16
  51. scitex/io/_save_modules/__init__.py +6 -10
  52. scitex/io/_save_modules/_canvas.py +3 -3
  53. scitex/io/_save_modules/_plot_bundle.py +112 -0
  54. scitex/io/_save_modules/{_pltz_stx.py → _plot_scitex.py} +7 -7
  55. scitex/io/_save_modules/_stx_bundle.py +16 -16
  56. scitex/io/bundle/README.md +89 -80
  57. scitex/{fts/_bundle/_FTS.py → io/bundle/_Bundle.py} +197 -95
  58. scitex/io/bundle/__init__.py +67 -35
  59. scitex/{fts/_bundle → io/bundle}/_children.py +32 -40
  60. scitex/io/bundle/_core.py +184 -97
  61. scitex/{fts/_bundle/_dataclasses/_Node.py → io/bundle/_dataclasses/_Spec.py} +29 -23
  62. scitex/{fts/_bundle/_dataclasses/_NodeRefs.py → io/bundle/_dataclasses/_SpecRefs.py} +6 -6
  63. scitex/{fts/_bundle → io/bundle}/_dataclasses/__init__.py +4 -4
  64. scitex/{fts/_bundle → io/bundle}/_loader.py +19 -19
  65. scitex/io/bundle/_manifest.py +99 -0
  66. scitex/{fts/_bundle → io/bundle}/_mpl_helpers.py +119 -28
  67. scitex/io/bundle/_nested.py +113 -100
  68. scitex/{fts/_bundle → io/bundle}/_saver.py +13 -14
  69. scitex/{fts/_bundle → io/bundle}/_storage.py +3 -3
  70. scitex/io/bundle/_types.py +41 -16
  71. scitex/{fts/_bundle → io/bundle}/_validation.py +20 -18
  72. scitex/io/bundle/_zip.py +21 -31
  73. scitex/{fts/_kinds → io/bundle/kinds}/_plot/_backend/_parser.py +1 -1
  74. scitex/{fts/_kinds → io/bundle/kinds}/_plot/_models/_Annotations.py +1 -1
  75. scitex/{fts/_kinds → io/bundle/kinds}/_plot/_models/_Axes.py +1 -1
  76. scitex/{fts/_kinds → io/bundle/kinds}/_plot/_models/_Figure.py +1 -1
  77. scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_Guides.py +1 -1
  78. scitex/{fts/_kinds → io/bundle/kinds}/_plot/_models/_Plot.py +1 -1
  79. scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_Styles.py +1 -1
  80. scitex/{fts/_kinds → io/bundle/kinds}/_plot/_utils/_plot_layout.py +1 -1
  81. scitex/{fts/_kinds → io/bundle/kinds}/_table/_latex/__init__.py +1 -1
  82. scitex/{fts/_kinds → io/bundle/kinds}/_table/_latex/_editor/_app.py +1 -1
  83. scitex/{fts/_tables → io/bundle/kinds/_table}/_latex/_export.py +1 -1
  84. scitex/{fts/_kinds → io/bundle/kinds}/_table/_latex/_figure_exporter.py +1 -1
  85. scitex/{fts/_kinds → io/bundle/kinds}/_table/_latex/_table_exporter.py +1 -1
  86. scitex/io/bundle/schemas/__init__.py +30 -0
  87. scitex/parallel/_run.py +5 -4
  88. scitex/path/_find.py +60 -83
  89. scitex/path/_get_module_path.py +23 -21
  90. scitex/path/_get_spath.py +6 -27
  91. scitex/path/_getsize.py +23 -9
  92. scitex/path/_increment_version.py +31 -38
  93. scitex/path/_mk_spath.py +26 -29
  94. scitex/path/_path.py +5 -12
  95. scitex/path/_split.py +27 -15
  96. scitex/path/_this_path.py +23 -9
  97. scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/__init__.py +2 -1
  98. scitex/plt/_subplots/_AxisWrapperMixins/__init__.py +2 -2
  99. scitex/plt/gallery/_generate.py +76 -50
  100. scitex/plt/io/__init__.py +17 -19
  101. scitex/plt/io/_bundle.py +99 -52
  102. scitex/plt/io/_layered_bundle.py +303 -168
  103. scitex/plt/utils/_csv_column_naming.py +250 -118
  104. scitex/schema/__init__.py +69 -73
  105. scitex/schema/_canvas.py +1 -1
  106. scitex/schema/_stats.py +2 -2
  107. scitex/stats/__init__.py +30 -33
  108. scitex/stats/_schema.py +1 -1
  109. scitex/stats/io/__init__.py +10 -11
  110. scitex/stats/io/_bundle.py +16 -16
  111. {scitex-2.10.2.dist-info → scitex-2.11.0.dist-info}/METADATA +191 -72
  112. {scitex-2.10.2.dist-info → scitex-2.11.0.dist-info}/RECORD +237 -360
  113. scitex/fig/editor/_defaults.py +0 -300
  114. scitex/fig/editor/edit/panel_loader.py +0 -232
  115. scitex/fig/editor/flask_editor/_bbox.py +0 -1299
  116. scitex/fig/editor/flask_editor/_core.py +0 -1429
  117. scitex/fig/editor/flask_editor/_renderer.py +0 -813
  118. scitex/fig/editor/flask_editor/static/css/features/canvas.css +0 -176
  119. scitex/fts/README.md +0 -262
  120. scitex/fts/TODO.md +0 -66
  121. scitex/fts/__init__.py +0 -90
  122. scitex/fts/_bundle/README_IN_BUNDLE.md +0 -102
  123. scitex/fts/_bundle/__init__.py +0 -38
  124. scitex/fts/_bundle/_utils/__init__.py +0 -55
  125. scitex/fts/_bundle/_utils/_const.py +0 -26
  126. scitex/fts/_bundle/_utils/_errors.py +0 -73
  127. scitex/fts/_bundle/_utils/_generate.py +0 -21
  128. scitex/fts/_bundle/_utils/_types.py +0 -76
  129. scitex/fts/_bundle/_zipbundle.py +0 -165
  130. scitex/fts/_fig/__init__.py +0 -22
  131. scitex/fts/_fig/_backend/_parser.py +0 -188
  132. scitex/fts/_fig/_editor/__init__.py +0 -14
  133. scitex/fts/_fig/_editor/_cui/__init__.py +0 -33
  134. scitex/fts/_fig/_editor/_cui/_backend_detector.py +0 -39
  135. scitex/fts/_fig/_editor/_cui/_bundle_resolver.py +0 -366
  136. scitex/fts/_fig/_editor/_cui/_editor_launcher.py +0 -175
  137. scitex/fts/_fig/_editor/_cui/_manual_handler.py +0 -52
  138. scitex/fts/_fig/_editor/_cui/_path_resolver.py +0 -66
  139. scitex/fts/_fig/_editor/_gui/__init__.py +0 -11
  140. scitex/fts/_fig/_editor/_gui/_flask_editor/__init__.py +0 -20
  141. scitex/fts/_fig/_editor/_gui/_flask_editor/_plotter.py +0 -664
  142. scitex/fts/_fig/_editor/_gui/_flask_editor/_utils.py +0 -79
  143. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/reset.css +0 -41
  144. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/typography.css +0 -16
  145. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/variables.css +0 -85
  146. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/buttons.css +0 -217
  147. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/context-menu.css +0 -93
  148. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/dropdown.css +0 -57
  149. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/forms.css +0 -112
  150. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/modal.css +0 -59
  151. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/sections.css +0 -212
  152. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/element-inspector.css +0 -190
  153. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/loading.css +0 -59
  154. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/overlay.css +0 -45
  155. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/panel-grid.css +0 -95
  156. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/selection.css +0 -101
  157. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/statistics.css +0 -138
  158. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/index.css +0 -31
  159. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/container.css +0 -7
  160. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/controls.css +0 -56
  161. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/preview.css +0 -78
  162. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/axis.js +0 -314
  163. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/basic.js +0 -107
  164. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/distribute.js +0 -54
  165. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/canvas.js +0 -172
  166. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/dragging.js +0 -258
  167. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/resize.js +0 -48
  168. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/selection.js +0 -71
  169. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/api.js +0 -288
  170. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/state.js +0 -143
  171. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/utils.js +0 -245
  172. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/dev/element-inspector.js +0 -992
  173. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/bbox.js +0 -339
  174. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/element-drag.js +0 -286
  175. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/overlay.js +0 -371
  176. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/preview.js +0 -293
  177. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/main.js +0 -426
  178. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/shortcuts/context-menu.js +0 -152
  179. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/shortcuts/keyboard.js +0 -265
  180. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/controls.js +0 -184
  181. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/download.js +0 -57
  182. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/help.js +0 -100
  183. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/theme.js +0 -34
  184. scitex/fts/_fig/_editor/_gui/_flask_editor/templates/__init__.py +0 -124
  185. scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_html.py +0 -851
  186. scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_scripts.py +0 -4932
  187. scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_styles.py +0 -1657
  188. scitex/fts/_fig/_editor/_gui/_flask_editor.py +0 -36
  189. scitex/fts/_fig/_models/_Annotations.py +0 -115
  190. scitex/fts/_fig/_models/_Axes.py +0 -152
  191. scitex/fts/_fig/_models/_Figure.py +0 -138
  192. scitex/fts/_fig/_models/_Plot.py +0 -123
  193. scitex/fts/_fig/_utils/_plot_layout.py +0 -397
  194. scitex/fts/_kinds/_figure/_composite.py +0 -345
  195. scitex/fts/_kinds/_plot/_backend/__init__.py +0 -53
  196. scitex/fts/_kinds/_plot/_backend/_export.py +0 -165
  197. scitex/fts/_kinds/_plot/_backend/_render.py +0 -538
  198. scitex/fts/_kinds/_plot/_dataclasses/_ChannelEncoding.py +0 -46
  199. scitex/fts/_kinds/_plot/_dataclasses/_Encoding.py +0 -82
  200. scitex/fts/_kinds/_plot/_dataclasses/_Theme.py +0 -441
  201. scitex/fts/_kinds/_plot/_dataclasses/_TraceEncoding.py +0 -52
  202. scitex/fts/_kinds/_plot/_dataclasses/__init__.py +0 -47
  203. scitex/fts/_kinds/_plot/_models/_Guides.py +0 -104
  204. scitex/fts/_kinds/_plot/_models/_Styles.py +0 -245
  205. scitex/fts/_kinds/_plot/_models/__init__.py +0 -80
  206. scitex/fts/_kinds/_plot/_models/_plot_types/__init__.py +0 -156
  207. scitex/fts/_kinds/_plot/_models/_plot_types/_bar.py +0 -43
  208. scitex/fts/_kinds/_plot/_models/_plot_types/_box.py +0 -38
  209. scitex/fts/_kinds/_plot/_models/_plot_types/_distribution.py +0 -36
  210. scitex/fts/_kinds/_plot/_models/_plot_types/_errorbar.py +0 -60
  211. scitex/fts/_kinds/_plot/_models/_plot_types/_histogram.py +0 -30
  212. scitex/fts/_kinds/_plot/_models/_plot_types/_image.py +0 -61
  213. scitex/fts/_kinds/_plot/_models/_plot_types/_line.py +0 -57
  214. scitex/fts/_kinds/_plot/_models/_plot_types/_scatter.py +0 -30
  215. scitex/fts/_kinds/_plot/_models/_plot_types/_seaborn.py +0 -121
  216. scitex/fts/_kinds/_plot/_models/_plot_types/_violin.py +0 -36
  217. scitex/fts/_kinds/_plot/_utils/__init__.py +0 -129
  218. scitex/fts/_kinds/_plot/_utils/_auto_layout.py +0 -127
  219. scitex/fts/_kinds/_plot/_utils/_calc_bounds.py +0 -111
  220. scitex/fts/_kinds/_plot/_utils/_const_sizes.py +0 -48
  221. scitex/fts/_kinds/_plot/_utils/_convert_coords.py +0 -77
  222. scitex/fts/_kinds/_plot/_utils/_get_template.py +0 -178
  223. scitex/fts/_kinds/_plot/_utils/_normalize.py +0 -73
  224. scitex/fts/_kinds/_plot/_utils/_validate.py +0 -197
  225. scitex/fts/_kinds/_table/_latex/_export.py +0 -279
  226. scitex/fts/_stats/__init__.py +0 -48
  227. scitex/fts/_stats/_dataclasses/_Stats.py +0 -423
  228. scitex/fts/_stats/_dataclasses/__init__.py +0 -48
  229. scitex/fts/_tables/__init__.py +0 -65
  230. scitex/fts/_tables/_latex/__init__.py +0 -93
  231. scitex/fts/_tables/_latex/_editor/__init__.py +0 -11
  232. scitex/fts/_tables/_latex/_editor/_app.py +0 -725
  233. scitex/fts/_tables/_latex/_figure_exporter.py +0 -153
  234. scitex/fts/_tables/_latex/_stats_formatter.py +0 -274
  235. scitex/fts/_tables/_latex/_table_exporter.py +0 -362
  236. scitex/fts/_tables/_latex/_utils.py +0 -369
  237. scitex/fts/_tables/_latex/_validator.py +0 -445
  238. scitex/io/_save_modules/_pltz_bundle.py +0 -356
  239. /scitex/{fig → canvas}/README.md +0 -0
  240. /scitex/{fig → canvas}/backend/__init__.py +0 -0
  241. /scitex/{fig → canvas}/backend/_export.py +0 -0
  242. /scitex/{fig → canvas}/backend/_render.py +0 -0
  243. /scitex/{fig → canvas}/docs/CANVAS_ARCHITECTURE.md +0 -0
  244. /scitex/{fig → canvas}/editor/__init__.py +0 -0
  245. /scitex/{fig → canvas}/editor/_dearpygui_editor.py +0 -0
  246. /scitex/{fig → canvas}/editor/_flask_editor.py +0 -0
  247. /scitex/{fig → canvas}/editor/_mpl_editor.py +0 -0
  248. /scitex/{fig → canvas}/editor/_qt_editor.py +0 -0
  249. /scitex/{fig → canvas}/editor/_tkinter_editor.py +0 -0
  250. /scitex/{fig → canvas}/editor/edit/backend_detector.py +0 -0
  251. /scitex/{fig → canvas}/editor/edit/manual_handler.py +0 -0
  252. /scitex/{fig → canvas}/editor/edit/path_resolver.py +0 -0
  253. /scitex/{fig → canvas}/editor/flask_editor/__init__.py +0 -0
  254. /scitex/{fig → canvas}/editor/flask_editor/_plotter.py +0 -0
  255. /scitex/{fig → canvas}/editor/flask_editor/_utils.py +0 -0
  256. /scitex/{fig → canvas}/editor/flask_editor/static/css/base/reset.css +0 -0
  257. /scitex/{fig → canvas}/editor/flask_editor/static/css/base/typography.css +0 -0
  258. /scitex/{fig → canvas}/editor/flask_editor/static/css/base/variables.css +0 -0
  259. /scitex/{fig → canvas}/editor/flask_editor/static/css/components/buttons.css +0 -0
  260. /scitex/{fig → canvas}/editor/flask_editor/static/css/components/context-menu.css +0 -0
  261. /scitex/{fig → canvas}/editor/flask_editor/static/css/components/dropdown.css +0 -0
  262. /scitex/{fig → canvas}/editor/flask_editor/static/css/components/forms.css +0 -0
  263. /scitex/{fig → canvas}/editor/flask_editor/static/css/components/modal.css +0 -0
  264. /scitex/{fig → canvas}/editor/flask_editor/static/css/components/sections.css +0 -0
  265. /scitex/{fig → canvas}/editor/flask_editor/static/css/features/element-inspector.css +0 -0
  266. /scitex/{fig → canvas}/editor/flask_editor/static/css/features/loading.css +0 -0
  267. /scitex/{fig → canvas}/editor/flask_editor/static/css/features/overlay.css +0 -0
  268. /scitex/{fig → canvas}/editor/flask_editor/static/css/features/selection.css +0 -0
  269. /scitex/{fig → canvas}/editor/flask_editor/static/css/features/statistics.css +0 -0
  270. /scitex/{fig → canvas}/editor/flask_editor/static/css/index.css +0 -0
  271. /scitex/{fig → canvas}/editor/flask_editor/static/css/layout/container.css +0 -0
  272. /scitex/{fig → canvas}/editor/flask_editor/static/css/layout/controls.css +0 -0
  273. /scitex/{fig → canvas}/editor/flask_editor/static/css/layout/preview.css +0 -0
  274. /scitex/{fig → canvas}/editor/flask_editor/static/js/alignment/axis.js +0 -0
  275. /scitex/{fig → canvas}/editor/flask_editor/static/js/alignment/basic.js +0 -0
  276. /scitex/{fig → canvas}/editor/flask_editor/static/js/alignment/distribute.js +0 -0
  277. /scitex/{fig → canvas}/editor/flask_editor/static/js/canvas/canvas.js +0 -0
  278. /scitex/{fig → canvas}/editor/flask_editor/static/js/canvas/dragging.js +0 -0
  279. /scitex/{fig → canvas}/editor/flask_editor/static/js/canvas/resize.js +0 -0
  280. /scitex/{fig → canvas}/editor/flask_editor/static/js/canvas/selection.js +0 -0
  281. /scitex/{fig → canvas}/editor/flask_editor/static/js/core/state.js +0 -0
  282. /scitex/{fig → canvas}/editor/flask_editor/static/js/core/utils.js +0 -0
  283. /scitex/{fig → canvas}/editor/flask_editor/static/js/dev/element-inspector.js +0 -0
  284. /scitex/{fig → canvas}/editor/flask_editor/static/js/editor/bbox.js +0 -0
  285. /scitex/{fig → canvas}/editor/flask_editor/static/js/editor/element-drag.js +0 -0
  286. /scitex/{fig → canvas}/editor/flask_editor/static/js/editor/overlay.js +0 -0
  287. /scitex/{fig → canvas}/editor/flask_editor/static/js/main.js +0 -0
  288. /scitex/{fig → canvas}/editor/flask_editor/static/js/shortcuts/context-menu.js +0 -0
  289. /scitex/{fig → canvas}/editor/flask_editor/static/js/shortcuts/keyboard.js +0 -0
  290. /scitex/{fig → canvas}/editor/flask_editor/static/js/ui/controls.js +0 -0
  291. /scitex/{fig → canvas}/editor/flask_editor/static/js/ui/download.js +0 -0
  292. /scitex/{fig → canvas}/editor/flask_editor/static/js/ui/help.js +0 -0
  293. /scitex/{fig → canvas}/editor/flask_editor/static/js/ui/theme.js +0 -0
  294. /scitex/{fig → canvas}/editor/flask_editor/templates/__init__.py +0 -0
  295. /scitex/{fig → canvas}/io/_directory.py +0 -0
  296. /scitex/{fig → canvas}/model/_plot_types.py +0 -0
  297. /scitex/{fig → canvas}/utils/_defaults.py +0 -0
  298. /scitex/{fig → canvas}/utils/_validate.py +0 -0
  299. /scitex/{fts/_bundle → io/bundle}/_conversion/__init__.py +0 -0
  300. /scitex/{fts/_bundle → io/bundle}/_conversion/_bundle2dict.py +0 -0
  301. /scitex/{fts/_bundle → io/bundle}/_conversion/_dict2bundle.py +0 -0
  302. /scitex/{fts/_bundle → io/bundle}/_dataclasses/_Axes.py +0 -0
  303. /scitex/{fts/_bundle → io/bundle}/_dataclasses/_BBox.py +0 -0
  304. /scitex/{fts/_bundle → io/bundle}/_dataclasses/_ColumnDef.py +0 -0
  305. /scitex/{fts/_bundle → io/bundle}/_dataclasses/_DataFormat.py +0 -0
  306. /scitex/{fts/_bundle → io/bundle}/_dataclasses/_DataInfo.py +0 -0
  307. /scitex/{fts/_bundle → io/bundle}/_dataclasses/_DataSource.py +0 -0
  308. /scitex/{fts/_bundle → io/bundle}/_dataclasses/_SizeMM.py +0 -0
  309. /scitex/{fts/_bundle → io/bundle}/_extractors/__init__.py +0 -0
  310. /scitex/{fts/_bundle → io/bundle}/_extractors/_extract_bar.py +0 -0
  311. /scitex/{fts/_bundle → io/bundle}/_extractors/_extract_line.py +0 -0
  312. /scitex/{fts/_bundle → io/bundle}/_extractors/_extract_scatter.py +0 -0
  313. /scitex/{fts/_kinds → io/bundle/kinds}/__init__.py +0 -0
  314. /scitex/{fts/_kinds → io/bundle/kinds}/_figure/__init__.py +0 -0
  315. /scitex/{fts/_fig → io/bundle/kinds/_figure}/_composite.py +0 -0
  316. /scitex/{fts/_kinds → io/bundle/kinds}/_plot/__init__.py +0 -0
  317. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_backend/__init__.py +0 -0
  318. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_backend/_export.py +0 -0
  319. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_backend/_render.py +0 -0
  320. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_dataclasses/_ChannelEncoding.py +0 -0
  321. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_dataclasses/_Encoding.py +0 -0
  322. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_dataclasses/_Theme.py +0 -0
  323. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_dataclasses/_TraceEncoding.py +0 -0
  324. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_dataclasses/__init__.py +0 -0
  325. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/__init__.py +0 -0
  326. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_plot_types/__init__.py +0 -0
  327. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_plot_types/_bar.py +0 -0
  328. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_plot_types/_box.py +0 -0
  329. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_plot_types/_distribution.py +0 -0
  330. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_plot_types/_errorbar.py +0 -0
  331. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_plot_types/_histogram.py +0 -0
  332. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_plot_types/_image.py +0 -0
  333. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_plot_types/_line.py +0 -0
  334. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_plot_types/_scatter.py +0 -0
  335. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_plot_types/_seaborn.py +0 -0
  336. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_plot_types/_violin.py +0 -0
  337. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_utils/__init__.py +0 -0
  338. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_utils/_auto_layout.py +0 -0
  339. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_utils/_calc_bounds.py +0 -0
  340. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_utils/_const_sizes.py +0 -0
  341. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_utils/_convert_coords.py +0 -0
  342. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_utils/_get_template.py +0 -0
  343. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_utils/_normalize.py +0 -0
  344. /scitex/{fts/_fig → io/bundle/kinds/_plot}/_utils/_validate.py +0 -0
  345. /scitex/{fts/_kinds → io/bundle/kinds}/_shape/__init__.py +0 -0
  346. /scitex/{fts/_kinds → io/bundle/kinds}/_stats/__init__.py +0 -0
  347. /scitex/{fts/_kinds → io/bundle/kinds}/_stats/_dataclasses/_Stats.py +0 -0
  348. /scitex/{fts/_kinds → io/bundle/kinds}/_stats/_dataclasses/__init__.py +0 -0
  349. /scitex/{fts/_kinds → io/bundle/kinds}/_table/__init__.py +0 -0
  350. /scitex/{fts/_kinds → io/bundle/kinds}/_table/_latex/_editor/__init__.py +0 -0
  351. /scitex/{fts/_kinds → io/bundle/kinds}/_table/_latex/_stats_formatter.py +0 -0
  352. /scitex/{fts/_kinds → io/bundle/kinds}/_table/_latex/_utils.py +0 -0
  353. /scitex/{fts/_kinds → io/bundle/kinds}/_table/_latex/_validator.py +0 -0
  354. /scitex/{fts/_kinds → io/bundle/kinds}/_text/__init__.py +0 -0
  355. /scitex/{fts/_schemas → io/bundle/schemas}/data_info.schema.json +0 -0
  356. /scitex/{fts/_schemas → io/bundle/schemas}/encoding.schema.json +0 -0
  357. /scitex/{fts/_schemas → io/bundle/schemas}/node.schema.json +0 -0
  358. /scitex/{fts/_schemas → io/bundle/schemas}/render_manifest.schema.json +0 -0
  359. /scitex/{fts/_schemas → io/bundle/schemas}/stats.schema.json +0 -0
  360. /scitex/{fts/_schemas → io/bundle/schemas}/theme.schema.json +0 -0
  361. {scitex-2.10.2.dist-info → scitex-2.11.0.dist-info}/WHEEL +0 -0
  362. {scitex-2.10.2.dist-info → scitex-2.11.0.dist-info}/entry_points.txt +0 -0
  363. {scitex-2.10.2.dist-info → scitex-2.11.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env python3
2
2
  # Timestamp: 2025-12-20
3
- # File: /home/ywatanabe/proj/scitex-code/src/scitex/fts/_bundle/_FTS.py
3
+ # File: /home/ywatanabe/proj/scitex-code/src/scitex/io/bundle/_Bundle.py
4
4
 
5
- """FTS Bundle Class - Main entry point for FTS bundles.
5
+ """Bundle Class - Main entry point for scitex bundles.
6
6
 
7
7
  Structure (identical for all kinds):
8
8
  - canonical/: Source of truth (spec.json, encoding.json, theme.json)
@@ -13,26 +13,31 @@ Structure (identical for all kinds):
13
13
 
14
14
  import uuid
15
15
  from pathlib import Path
16
- from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
16
+ from typing import TYPE_CHECKING, Any, Dict, Optional, Union
17
17
 
18
18
  from ._children import ValidationError, embed_child, load_embedded_children
19
- from ._dataclasses import DataInfo, Node, SizeMM
19
+ from ._dataclasses import DataInfo, SizeMM, Spec
20
20
  from ._loader import load_bundle_components
21
- from ._validation import ValidationResult
22
- from ._saver import compute_canonical_hash, compute_theme_hash, save_bundle_components, save_render_outputs
21
+ from ._saver import (
22
+ compute_canonical_hash,
23
+ compute_theme_hash,
24
+ save_bundle_components,
25
+ save_render_outputs,
26
+ )
23
27
  from ._storage import Storage, get_storage
24
- from .._fig import Encoding, Theme
25
- from .._stats import Stats
28
+ from ._validation import ValidationResult
29
+ from .kinds._plot import Encoding, Theme
30
+ from .kinds._stats import Stats
26
31
 
27
32
  if TYPE_CHECKING:
28
33
  from matplotlib.figure import Figure as MplFigure
29
34
 
30
35
 
31
- class FTS:
32
- """Figure-Table-Statistics Bundle - Self-contained figure/plot/stats package.
36
+ class Bundle:
37
+ """Scitex Bundle - Self-contained figure/plot/stats package.
33
38
 
34
39
  Attributes:
35
- node: Node metadata (kind, children, layout, payload_schema, etc.)
40
+ spec: Spec metadata (kind, children, layout, payload_schema, etc.)
36
41
  encoding: Encoding specification (traces, channels)
37
42
  theme: Theme specification (colors, fonts)
38
43
  stats: Statistics (for kind=stats)
@@ -49,7 +54,7 @@ class FTS:
49
54
  # Legacy support
50
55
  node_type: Optional[str] = None,
51
56
  ):
52
- """Initialize FTS bundle.
57
+ """Initialize Bundle.
53
58
 
54
59
  Args:
55
60
  path: Bundle path (directory or .zip file)
@@ -61,7 +66,7 @@ class FTS:
61
66
  """
62
67
  self._path = Path(path)
63
68
  self._is_zip = self._path.suffix == ".zip"
64
- self._node: Optional[Node] = None
69
+ self._spec: Optional[Spec] = None
65
70
  self._encoding: Optional[Encoding] = None
66
71
  self._theme: Optional[Theme] = None
67
72
  self._stats: Optional[Stats] = None
@@ -91,7 +96,7 @@ class FTS:
91
96
  @property
92
97
  def bundle_type(self) -> str:
93
98
  """Bundle kind (figure, plot, table, etc.)."""
94
- return self._node.kind if self._node else "unknown"
99
+ return self._spec.kind if self._spec else "unknown"
95
100
 
96
101
  @property
97
102
  def is_dirty(self) -> bool:
@@ -106,16 +111,16 @@ class FTS:
106
111
  return self._storage
107
112
 
108
113
  @property
109
- def node(self) -> Optional[Node]:
110
- """Node metadata."""
111
- return self._node
114
+ def spec(self) -> Optional[Spec]:
115
+ """Bundle specification metadata."""
116
+ return self._spec
112
117
 
113
- @node.setter
114
- def node(self, value: Union[Node, Dict[str, Any]]):
118
+ @spec.setter
119
+ def spec(self, value: Union[Spec, Dict[str, Any]]):
115
120
  if isinstance(value, dict):
116
- self._node = Node.from_dict(value)
121
+ self._spec = Spec.from_dict(value)
117
122
  else:
118
- self._node = value
123
+ self._spec = value
119
124
  self._dirty = True
120
125
 
121
126
  @property
@@ -195,15 +200,15 @@ class FTS:
195
200
  # Note: payload_schema is optional. For plots without data, it's None.
196
201
  # For plots with data, from_matplotlib will set it.
197
202
  payload_schema = None
198
- if kind in Node.LEAF_KINDS and kind != "plot":
203
+ if kind in Spec.LEAF_KINDS and kind != "plot":
199
204
  # Only auto-set for non-plot leaf kinds
200
205
  payload_schema_map = {
201
- "table": "scitex.fts.payload.table@1",
202
- "stats": "scitex.fts.payload.stats@1",
206
+ "table": "scitex.io.bundle.payload.table@1",
207
+ "stats": "scitex.io.bundle.payload.stats@1",
203
208
  }
204
209
  payload_schema = payload_schema_map.get(kind)
205
210
 
206
- self._node = Node(
211
+ self._spec = Spec(
207
212
  id=bundle_id,
208
213
  kind=kind,
209
214
  name=name,
@@ -218,10 +223,10 @@ class FTS:
218
223
  def _load(self):
219
224
  """Load existing bundle."""
220
225
  if not self._path.exists():
221
- raise FileNotFoundError(f"FTS bundle not found: {self._path}")
226
+ raise FileNotFoundError(f"Bundle not found: {self._path}")
222
227
 
223
228
  (
224
- self._node,
229
+ self._spec,
225
230
  self._encoding,
226
231
  self._theme,
227
232
  self._stats,
@@ -230,7 +235,7 @@ class FTS:
230
235
 
231
236
  def add_child(
232
237
  self,
233
- child: Union[str, Path, "FTS"],
238
+ child: Union[str, Path, "Bundle"],
234
239
  row: int = 0,
235
240
  col: int = 0,
236
241
  label: Optional[str] = None,
@@ -239,11 +244,11 @@ class FTS:
239
244
  **kwargs,
240
245
  ) -> str:
241
246
  """Add and embed a child bundle. Returns child_name in children/."""
242
- if not self.node.is_composite_kind():
243
- raise TypeError(f"kind={self.node.kind} cannot have children")
247
+ if not self.spec.is_composite_kind():
248
+ raise TypeError(f"kind={self.spec.kind} cannot have children")
244
249
 
245
250
  # Get child path
246
- if isinstance(child, FTS):
251
+ if isinstance(child, Bundle):
247
252
  child_path = child.path
248
253
  else:
249
254
  child_path = Path(child)
@@ -252,16 +257,20 @@ class FTS:
252
257
  # Returns (child_name, child_id) tuple
253
258
  child_name, child_id = embed_child(self.storage, child_path)
254
259
 
255
- # Add to node.children
256
- self._node.children.append(child_name)
260
+ # Add to spec.children
261
+ self._spec.children.append(child_name)
257
262
 
258
263
  # Initialize layout if needed
259
- if self._node.layout is None:
260
- self._node.layout = {"rows": 2, "cols": 2, "panels": []}
264
+ if self._spec.layout is None:
265
+ self._spec.layout = {"rows": 2, "cols": 2, "panels": []}
261
266
 
262
267
  # Update grid size if needed
263
- self._node.layout["rows"] = max(self._node.layout.get("rows", 1), row + row_span)
264
- self._node.layout["cols"] = max(self._node.layout.get("cols", 1), col + col_span)
268
+ self._spec.layout["rows"] = max(
269
+ self._spec.layout.get("rows", 1), row + row_span
270
+ )
271
+ self._spec.layout["cols"] = max(
272
+ self._spec.layout.get("cols", 1), col + col_span
273
+ )
265
274
 
266
275
  # Add to layout.panels
267
276
  panel_info = {
@@ -276,29 +285,29 @@ class FTS:
276
285
  if label:
277
286
  panel_info["label"] = label
278
287
 
279
- self._node.layout["panels"].append(panel_info)
288
+ self._spec.layout["panels"].append(panel_info)
280
289
  self._dirty = True
281
290
 
282
291
  return child_name
283
292
 
284
- def load_children(self) -> Dict[str, "FTS"]:
285
- """Load embedded children. Returns dict: child_name -> FTS."""
293
+ def load_children(self) -> Dict[str, "Bundle"]:
294
+ """Load embedded children. Returns dict: child_name -> Bundle."""
286
295
  return load_embedded_children(self._path)
287
296
 
288
297
  def render(self) -> Optional["MplFigure"]:
289
298
  """Render figure. Composite renders children, leaf renders from encoding."""
290
- if self._node is None:
299
+ if self._spec is None:
291
300
  return None
292
301
 
293
- if self._node.is_composite_kind():
302
+ if self._spec.is_composite_kind():
294
303
  return self._render_composite()
295
- elif self._node.is_data_leaf_kind():
304
+ elif self._spec.is_data_leaf_kind():
296
305
  # Data kinds (plot, table, stats) need payload data
297
306
  return self._render_from_encoding()
298
- elif self._node.is_annotation_leaf_kind():
299
- # Annotation kinds (text, shape) render from node params
307
+ elif self._spec.is_annotation_leaf_kind():
308
+ # Annotation kinds (text, shape) render from spec params
300
309
  return self._render_annotation()
301
- elif self._node.is_image_leaf_kind():
310
+ elif self._spec.is_image_leaf_kind():
302
311
  # Image kinds render from payload image
303
312
  return self._render_image()
304
313
 
@@ -308,14 +317,18 @@ class FTS:
308
317
  """Render composite figure with children."""
309
318
  import scitex.plt as splt
310
319
 
311
- size_mm = self._node.size_mm.to_dict() if self._node.size_mm else {"width": 170, "height": 100}
320
+ size_mm = (
321
+ self._spec.size_mm.to_dict()
322
+ if self._spec.size_mm
323
+ else {"width": 170, "height": 100}
324
+ )
312
325
 
313
326
  # Get background color from theme
314
327
  bg_color = "#ffffff"
315
328
  if self._theme and self._theme.colors:
316
329
  bg_color = self._theme.colors.background or "#ffffff"
317
330
 
318
- if not self._node.children:
331
+ if not self._spec.children:
319
332
  # Empty container - render blank figure with specified size and background
320
333
  fig, ax = splt.subplots(
321
334
  figsize_mm=(size_mm.get("width", 170), size_mm.get("height", 100))
@@ -325,13 +338,13 @@ class FTS:
325
338
  ax.set_axis_off()
326
339
  return fig
327
340
 
328
- from .._fig._composite import render_composite
341
+ from .kinds._figure._composite import render_composite
329
342
 
330
343
  children = self.load_children()
331
344
 
332
345
  fig, geometry = render_composite(
333
346
  children=children,
334
- layout=self._node.layout or {"rows": 1, "cols": 1, "panels": []},
347
+ layout=self._spec.layout or {"rows": 1, "cols": 1, "panels": []},
335
348
  size_mm=size_mm,
336
349
  theme=self._theme,
337
350
  )
@@ -345,7 +358,11 @@ class FTS:
345
358
 
346
359
  import scitex.plt as splt
347
360
 
348
- size_mm = self._node.size_mm.to_dict() if self._node.size_mm else {"width": 85, "height": 85}
361
+ size_mm = (
362
+ self._spec.size_mm.to_dict()
363
+ if self._spec.size_mm
364
+ else {"width": 85, "height": 85}
365
+ )
349
366
 
350
367
  # Use scitex.plt for proper styling (3-4 ticks, etc.)
351
368
  fig, ax = splt.subplots(
@@ -356,7 +373,7 @@ class FTS:
356
373
  data = self._load_payload_data()
357
374
 
358
375
  # Render traces
359
- from .._fig._backend._render import render_traces
376
+ from .kinds._plot._backend._render import render_traces
360
377
 
361
378
  traces = self._encoding.traces if self._encoding.traces else []
362
379
  for trace in traces:
@@ -375,9 +392,10 @@ class FTS:
375
392
 
376
393
  def _load_payload_data(self) -> Optional["pd.DataFrame"]:
377
394
  """Load data from payload/data.csv or legacy data/data.csv."""
378
- import pandas as pd
379
395
  from io import StringIO
380
396
 
397
+ import pandas as pd
398
+
381
399
  # Try new path first, then legacy
382
400
  for path in ["payload/data.csv", "data/data.csv"]:
383
401
  if self.storage.exists(path):
@@ -389,10 +407,14 @@ class FTS:
389
407
  return None
390
408
 
391
409
  def _render_annotation(self) -> Optional["MplFigure"]:
392
- """Render annotation (text/shape) from node parameters."""
410
+ """Render annotation (text/shape) from spec parameters."""
393
411
  import scitex.plt as splt
394
412
 
395
- size_mm = self._node.size_mm.to_dict() if self._node.size_mm else {"width": 85, "height": 85}
413
+ size_mm = (
414
+ self._spec.size_mm.to_dict()
415
+ if self._spec.size_mm
416
+ else {"width": 85, "height": 85}
417
+ )
396
418
 
397
419
  fig, ax = splt.subplots(
398
420
  figsize_mm=(size_mm.get("width", 85), size_mm.get("height", 85))
@@ -406,30 +428,34 @@ class FTS:
406
428
  ax.set_facecolor(bg_color)
407
429
  ax.set_axis_off()
408
430
 
409
- if self._node.kind == "text":
431
+ if self._spec.kind == "text":
410
432
  # Render text annotation
411
- text_obj = self._node.text
433
+ text_obj = self._spec.text
412
434
  if text_obj:
413
- text_content = text_obj.content or self._node.name or ""
435
+ text_content = text_obj.content or self._spec.name or ""
414
436
  kwargs = {"ha": text_obj.ha, "va": text_obj.va}
415
437
  if text_obj.fontsize:
416
438
  kwargs["fontsize"] = text_obj.fontsize
417
439
  if text_obj.fontweight:
418
440
  kwargs["fontweight"] = text_obj.fontweight
419
441
  else:
420
- text_content = self._node.name or ""
442
+ text_content = self._spec.name or ""
421
443
  kwargs = {"ha": "center", "va": "center"}
422
444
  ax.text(0.5, 0.5, text_content, transform=ax.transAxes, **kwargs)
423
445
 
424
- elif self._node.kind == "shape":
446
+ elif self._spec.kind == "shape":
425
447
  # Render shape annotation
426
- from .._kinds._shape import render_shape
427
- shape_obj = self._node.shape
448
+ from .kinds._shape import render_shape
449
+
450
+ shape_obj = self._spec.shape
428
451
  if shape_obj:
429
452
  render_shape(
430
453
  ax,
431
454
  shape_type=shape_obj.shape_type,
432
- x=0.2, y=0.2, width=0.6, height=0.6,
455
+ x=0.2,
456
+ y=0.2,
457
+ width=0.6,
458
+ height=0.6,
433
459
  facecolor=shape_obj.color if shape_obj.fill else "none",
434
460
  edgecolor=shape_obj.color,
435
461
  linewidth=shape_obj.linewidth,
@@ -440,10 +466,15 @@ class FTS:
440
466
 
441
467
  def _render_image(self) -> Optional["MplFigure"]:
442
468
  """Render image from payload."""
443
- import scitex.plt as splt
444
469
  import numpy as np
445
470
 
446
- size_mm = self._node.size_mm.to_dict() if self._node.size_mm else {"width": 85, "height": 85}
471
+ import scitex.plt as splt
472
+
473
+ size_mm = (
474
+ self._spec.size_mm.to_dict()
475
+ if self._spec.size_mm
476
+ else {"width": 85, "height": 85}
477
+ )
447
478
 
448
479
  fig, ax = splt.subplots(
449
480
  figsize_mm=(size_mm.get("width", 85), size_mm.get("height", 85))
@@ -454,8 +485,10 @@ class FTS:
454
485
  for ext in ["png", "jpg", "jpeg", "gif", "bmp"]:
455
486
  path = f"payload/image.{ext}"
456
487
  if self.storage.exists(path):
457
- from PIL import Image
458
488
  from io import BytesIO
489
+
490
+ from PIL import Image
491
+
459
492
  img_bytes = self.storage.read(path)
460
493
  img = Image.open(BytesIO(img_bytes))
461
494
  ax.imshow(np.array(img))
@@ -464,6 +497,62 @@ class FTS:
464
497
  fig.tight_layout()
465
498
  return fig
466
499
 
500
+ def _validate_manifest(self) -> tuple:
501
+ """Validate manifest.json existence and structure.
502
+
503
+ Returns:
504
+ Tuple of (errors: List[str], warnings: List[str])
505
+ """
506
+ import json
507
+
508
+ errors = []
509
+ warnings = []
510
+
511
+ # Check if bundle path exists
512
+ if not self._path.exists():
513
+ return errors, warnings # Can't validate non-existent bundle
514
+
515
+ # Check manifest.json exists (required)
516
+ manifest_path = "manifest.json"
517
+ if not self.storage.exists(manifest_path):
518
+ errors.append("Missing required manifest.json")
519
+ return errors, warnings
520
+
521
+ # Validate manifest structure
522
+ try:
523
+ content = self.storage.read(manifest_path)
524
+ manifest = json.loads(content.decode("utf-8"))
525
+
526
+ if "scitex" not in manifest:
527
+ errors.append("manifest.json missing 'scitex' key")
528
+ else:
529
+ scitex = manifest["scitex"]
530
+ if "type" not in scitex:
531
+ errors.append("manifest.json missing 'scitex.type'")
532
+ if "version" not in scitex:
533
+ errors.append("manifest.json missing 'scitex.version'")
534
+
535
+ # Validate type matches spec kind
536
+ manifest_type = scitex.get("type")
537
+ if manifest_type and self._spec:
538
+ # Normalize both to compare
539
+ from ._types import BundleType
540
+
541
+ normalized_manifest = BundleType.normalize(manifest_type)
542
+ normalized_node = BundleType.normalize(self._spec.kind)
543
+ if normalized_manifest != normalized_node:
544
+ errors.append(
545
+ f"Type mismatch: manifest says '{manifest_type}', "
546
+ f"spec says '{self._spec.kind}'"
547
+ )
548
+
549
+ except json.JSONDecodeError as e:
550
+ errors.append(f"manifest.json is invalid JSON: {e}")
551
+ except Exception as e:
552
+ errors.append(f"Error reading manifest.json: {e}")
553
+
554
+ return errors, warnings
555
+
467
556
  def validate(self, level: str = "schema") -> ValidationResult:
468
557
  """Validate bundle.
469
558
 
@@ -475,16 +564,21 @@ class FTS:
475
564
  """
476
565
  result = ValidationResult(level=level)
477
566
 
478
- # Node logical validation
479
- if self._node:
480
- result.errors.extend(self._node.validate())
567
+ # Manifest validation (returns errors, warnings tuple)
568
+ manifest_errors, manifest_warnings = self._validate_manifest()
569
+ result.errors.extend(manifest_errors)
570
+ result.warnings.extend(manifest_warnings)
571
+
572
+ # Spec logical validation
573
+ if self._spec:
574
+ result.errors.extend(self._spec.validate())
481
575
 
482
576
  # Storage-level validation - check required payload files
483
- if self._node and self._node.is_leaf_kind():
484
- required_file = self._node.get_required_payload_file()
577
+ if self._spec and self._spec.is_leaf_kind():
578
+ required_file = self._spec.get_required_payload_file()
485
579
  if required_file:
486
580
  # Check both new structure (payload/) and legacy structure (data/)
487
- # Legacy sio.save() uses data/data.csv, new FTS uses payload/data.csv
581
+ # Legacy sio.save() uses data/data.csv, new Bundle uses payload/data.csv
488
582
  legacy_paths = {
489
583
  "payload/data.csv": "data/data.csv",
490
584
  "payload/table.csv": "data/table.csv",
@@ -493,25 +587,33 @@ class FTS:
493
587
  legacy_path = legacy_paths.get(required_file)
494
588
  if not self.storage.exists(required_file):
495
589
  if not legacy_path or not self.storage.exists(legacy_path):
496
- result.errors.append(f"Missing required payload file: {required_file}")
590
+ result.errors.append(
591
+ f"Missing required payload file: {required_file}"
592
+ )
497
593
 
498
594
  # NOTE: For composite kinds, do NOT validate payload/ emptiness by listing files.
499
- # Payload prohibition is enforced purely via payload_schema is None (in Node.validate).
595
+ # Payload prohibition is enforced purely via payload_schema is None (in Spec.validate).
500
596
 
501
597
  # Recursively validate embedded children
502
- if self._node and self._node.is_composite_kind() and self._node.children:
598
+ if self._spec and self._spec.is_composite_kind() and self._spec.children:
503
599
  children = self.load_children()
504
600
  for child_name, child in children.items():
505
601
  child_result = child.validate(level)
506
- result.errors.extend([f"{child_name}: {e}" for e in child_result.errors])
507
- result.warnings.extend([f"{child_name}: {w}" for w in child_result.warnings])
602
+ result.errors.extend(
603
+ [f"{child_name}: {e}" for e in child_result.errors]
604
+ )
605
+ result.warnings.extend(
606
+ [f"{child_name}: {w}" for w in child_result.warnings]
607
+ )
508
608
 
509
609
  # Schema validation for other components
510
610
  if level in ("semantic", "strict"):
511
611
  # Additional semantic validation
512
- if self._encoding and self._node:
513
- if self._node.is_composite_kind() and self._encoding.traces:
514
- result.errors.append("Composite kinds should not have encoding traces")
612
+ if self._encoding and self._spec:
613
+ if self._spec.is_composite_kind() and self._encoding.traces:
614
+ result.errors.append(
615
+ "Composite kinds should not have encoding traces"
616
+ )
515
617
 
516
618
  return result
517
619
 
@@ -543,13 +645,13 @@ class FTS:
543
645
  raise ValidationError(f"Validation failed: {result.errors}")
544
646
 
545
647
  # Update modified timestamp
546
- if self._node:
547
- self._node.touch()
648
+ if self._spec:
649
+ self._spec.touch()
548
650
 
549
651
  # Save canonical files
550
652
  save_bundle_components(
551
653
  self._path,
552
- node=self._node,
654
+ spec=self._spec,
553
655
  encoding=self._encoding,
554
656
  theme=self._theme,
555
657
  stats=self._stats,
@@ -597,8 +699,8 @@ class FTS:
597
699
  "is_zip": self._is_zip,
598
700
  "kind": self.bundle_type,
599
701
  }
600
- if self._node:
601
- result["node"] = self._node.to_dict()
702
+ if self._spec:
703
+ result["spec"] = self._spec.to_dict()
602
704
  if self._encoding:
603
705
  result["encoding"] = self._encoding.to_dict()
604
706
  if self._theme:
@@ -609,7 +711,7 @@ class FTS:
609
711
  result["data_info"] = self._data_info.to_dict()
610
712
  return result
611
713
 
612
- def __enter__(self) -> "FTS":
714
+ def __enter__(self) -> "Bundle":
613
715
  """Enter context manager."""
614
716
  return self
615
717
 
@@ -621,8 +723,8 @@ class FTS:
621
723
 
622
724
  def __repr__(self) -> str:
623
725
  dirty_marker = "*" if self._dirty else ""
624
- kind = self._node.kind if self._node else "unknown"
625
- return f"FTS({self._path!r}, kind={kind!r}){dirty_marker}"
726
+ kind = self._spec.kind if self._spec else "unknown"
727
+ return f"Bundle({self._path!r}, kind={kind!r}){dirty_marker}"
626
728
 
627
729
 
628
730
  # =============================================================================
@@ -633,9 +735,9 @@ class FTS:
633
735
  from ._mpl_helpers import from_matplotlib
634
736
 
635
737
 
636
- def load_bundle(path: Union[str, Path]) -> FTS:
637
- """Load an existing FTS bundle."""
638
- return FTS(path)
738
+ def load_bundle(path: Union[str, Path]) -> Bundle:
739
+ """Load an existing Bundle."""
740
+ return Bundle(path)
639
741
 
640
742
 
641
743
  def create_bundle(
@@ -645,13 +747,13 @@ def create_bundle(
645
747
  size_mm: Optional[Dict[str, float]] = None,
646
748
  # Legacy support
647
749
  node_type: Optional[str] = None,
648
- ) -> FTS:
649
- """Create a new FTS bundle."""
750
+ ) -> Bundle:
751
+ """Create a new Bundle."""
650
752
  if node_type is not None:
651
753
  kind = node_type
652
- return FTS(path, create=True, kind=kind, name=name, size_mm=size_mm)
754
+ return Bundle(path, create=True, kind=kind, name=name, size_mm=size_mm)
653
755
 
654
756
 
655
- __all__ = ["FTS", "load_bundle", "create_bundle", "from_matplotlib"]
757
+ __all__ = ["Bundle", "load_bundle", "create_bundle", "from_matplotlib"]
656
758
 
657
759
  # EOF