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,366 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: 2025-12-20
3
+ # File: /home/ywatanabe/proj/scitex-code/src/scitex/fsb/editor/edit/bundle_resolver.py
4
+
5
+ """Bundle path resolution for FTS bundles (unified format)."""
6
+
7
+ import json as json_module
8
+ import tempfile
9
+ import zipfile
10
+ from pathlib import Path
11
+ from typing import Tuple
12
+
13
+ __all__ = [
14
+ "resolve_pltz_bundle",
15
+ "resolve_figz_bundle",
16
+ "resolve_layered_pltz_bundle",
17
+ "resolve_stx_bundle",
18
+ ]
19
+
20
+
21
+ def resolve_figz_bundle(path: Path, panel_index: int = 0) -> Tuple:
22
+ """
23
+ Resolve paths from a .figz bundle (multi-panel figure).
24
+
25
+ Uses in-memory zip reading for .pltz panels - no disk extraction.
26
+
27
+ Parameters
28
+ ----------
29
+ path : Path
30
+ Path to .figz bundle (.figz or .figz.d)
31
+ panel_index : int, optional
32
+ Index of panel to open (default: 0 for first panel)
33
+
34
+ Returns
35
+ -------
36
+ tuple
37
+ (json_path, csv_path, png_path, hitmap_path, bundle_spec, panel_info)
38
+ """
39
+ spath = str(path)
40
+ figz_is_zip = False
41
+
42
+ # Handle ZIP vs directory for figz
43
+ if spath.endswith(".figz") and not spath.endswith(".figz.d"):
44
+ figz_is_zip = True
45
+ if not path.exists():
46
+ raise FileNotFoundError(f"Figz bundle not found: {path}")
47
+ # For figz zip, extract to access nested pltz
48
+ temp_dir = tempfile.mkdtemp(prefix="scitex_edit_figz_")
49
+ with zipfile.ZipFile(path, "r") as zf:
50
+ zf.extractall(temp_dir)
51
+ bundle_dir = Path(temp_dir)
52
+ for item in bundle_dir.iterdir():
53
+ if item.is_dir() and str(item).endswith(".figz.d"):
54
+ bundle_dir = item
55
+ break
56
+ else:
57
+ bundle_dir = Path(path)
58
+ if not bundle_dir.exists():
59
+ raise FileNotFoundError(f"Figz bundle directory not found: {bundle_dir}")
60
+
61
+ # Find nested pltz bundles
62
+ panel_paths = []
63
+ panel_is_zip = []
64
+
65
+ for item in sorted(bundle_dir.iterdir(), key=lambda x: x.name):
66
+ if item.is_dir() and str(item).endswith(".pltz.d"):
67
+ panel_paths.append(str(item))
68
+ panel_is_zip.append(False)
69
+ elif item.is_file() and str(item).endswith(".pltz"):
70
+ panel_paths.append(str(item))
71
+ panel_is_zip.append(True)
72
+
73
+ if not panel_paths:
74
+ raise FileNotFoundError(f"No .pltz panels found in figz bundle: {bundle_dir}")
75
+
76
+ # Validate panel index
77
+ if panel_index < 0 or panel_index >= len(panel_paths):
78
+ panel_index = 0
79
+
80
+ selected_panel_path = panel_paths[panel_index]
81
+ panel_name = Path(selected_panel_path).name
82
+ print(f"Opening panel: {panel_name}")
83
+ if len(panel_paths) > 1:
84
+ print(f" (Figz contains {len(panel_paths)} panels)")
85
+
86
+ # Build panel info
87
+ panel_names = [Path(p).name for p in panel_paths]
88
+ panel_info = {
89
+ "panels": panel_names,
90
+ "panel_paths": panel_paths,
91
+ "panel_is_zip": panel_is_zip,
92
+ "current_index": panel_index,
93
+ "figz_dir": str(bundle_dir),
94
+ "figz_is_zip": figz_is_zip,
95
+ "bundle_path": (
96
+ str(path) if figz_is_zip else None
97
+ ), # Original figz zip path for export/download
98
+ }
99
+
100
+ # Resolve the selected panel
101
+ result = resolve_pltz_bundle(Path(selected_panel_path))
102
+ return result + (panel_info,)
103
+
104
+
105
+ def resolve_pltz_bundle(path: Path) -> Tuple:
106
+ """
107
+ Resolve paths from a .pltz bundle (directory or ZIP).
108
+
109
+ Supports both:
110
+ - Legacy format (single {basename}.json)
111
+ - Layered format v2.0 (spec.json + style.json + cache/)
112
+
113
+ Parameters
114
+ ----------
115
+ path : Path
116
+ Path to .pltz bundle (.pltz or .pltz.d)
117
+
118
+ Returns
119
+ -------
120
+ tuple
121
+ (json_path, csv_path, png_path, hitmap_path, bundle_spec)
122
+ """
123
+ spath = str(path)
124
+
125
+ # Handle ZIP vs directory
126
+ if spath.endswith(".pltz") and not spath.endswith(".pltz.d"):
127
+ if not path.exists():
128
+ raise FileNotFoundError(f"Bundle not found: {path}")
129
+ temp_dir = tempfile.mkdtemp(prefix="scitex_edit_")
130
+ with zipfile.ZipFile(path, "r") as zf:
131
+ zf.extractall(temp_dir)
132
+ bundle_dir = Path(temp_dir)
133
+ for item in bundle_dir.iterdir():
134
+ if item.is_dir() and str(item).endswith(".pltz.d"):
135
+ bundle_dir = item
136
+ break
137
+ else:
138
+ bundle_dir = Path(path)
139
+ if not bundle_dir.exists():
140
+ raise FileNotFoundError(f"Bundle directory not found: {bundle_dir}")
141
+
142
+ # Check if this is a layered bundle (v2.0)
143
+ spec_path = bundle_dir / "spec.json"
144
+ if spec_path.exists():
145
+ return resolve_layered_pltz_bundle(bundle_dir)
146
+
147
+ # === Legacy format ===
148
+ json_path = None
149
+ csv_path = None
150
+ png_path = None
151
+ svg_path = None
152
+ hitmap_path = None
153
+ bundle_spec = None
154
+
155
+ for f in bundle_dir.iterdir():
156
+ name = f.name
157
+ if name.endswith(".json") and not name.endswith(".manual.json"):
158
+ json_path = f
159
+ elif name.endswith(".csv"):
160
+ csv_path = f
161
+ elif name.endswith("_hitmap.png"):
162
+ hitmap_path = f
163
+ elif name.endswith(".svg") and "_hitmap" not in name:
164
+ svg_path = f
165
+ elif (
166
+ name.endswith(".png") and "_hitmap" not in name and "_overview" not in name
167
+ ):
168
+ png_path = f
169
+
170
+ # Prefer SVG for display
171
+ if svg_path:
172
+ png_path = svg_path
173
+
174
+ if json_path and json_path.exists():
175
+ with open(json_path) as f:
176
+ bundle_spec = json_module.load(f)
177
+
178
+ return (
179
+ json_path,
180
+ csv_path if csv_path and csv_path.exists() else None,
181
+ png_path if png_path and png_path.exists() else None,
182
+ hitmap_path if hitmap_path and hitmap_path.exists() else None,
183
+ bundle_spec,
184
+ )
185
+
186
+
187
+ def resolve_layered_pltz_bundle(bundle_dir: Path) -> Tuple:
188
+ """
189
+ Resolve paths from a layered FTS bundle.
190
+
191
+ FTS format structure:
192
+ bundle.zip (or bundle.d/)
193
+ canonical/
194
+ spec.json # Main specification
195
+ data.csv # Source data
196
+ encoding.json # Data-to-visual mappings
197
+ theme.json # Visual aesthetics
198
+ artifacts/
199
+ exports/ # PNG, SVG, PDF
200
+ cache/ # geometry_px.json
201
+
202
+ Parameters
203
+ ----------
204
+ bundle_dir : Path
205
+ Path to bundle directory.
206
+
207
+ Returns
208
+ -------
209
+ tuple
210
+ (json_path, csv_path, png_path, hitmap_path, bundle_spec)
211
+ """
212
+ # Load FTS bundle directly
213
+ bundle_data = {}
214
+ spec_path = bundle_dir / "canonical" / "spec.json"
215
+ if not spec_path.exists():
216
+ spec_path = bundle_dir / "spec.json" # Legacy location
217
+
218
+ if spec_path.exists():
219
+ with open(spec_path) as f:
220
+ bundle_data["spec"] = json_module.load(f)
221
+
222
+ # Try to load encoding and theme
223
+ for fname in ["encoding.json", "theme.json"]:
224
+ fpath = bundle_dir / "canonical" / fname
225
+ if not fpath.exists():
226
+ fpath = bundle_dir / fname
227
+ if fpath.exists():
228
+ with open(fpath) as f:
229
+ bundle_data[fname.replace(".json", "")] = json_module.load(f)
230
+
231
+ csv_path = None
232
+ png_path = None
233
+ hitmap_path = None
234
+
235
+ # Find CSV (check canonical/ first, then root)
236
+ for search_dir in [bundle_dir / "canonical", bundle_dir]:
237
+ for f in search_dir.glob("*.csv"):
238
+ csv_path = f
239
+ break
240
+ if csv_path:
241
+ break
242
+
243
+ # Find exports (check artifacts/exports first, then exports/)
244
+ for exports_dir in [bundle_dir / "artifacts" / "exports", bundle_dir / "exports"]:
245
+ if exports_dir.exists():
246
+ for f in exports_dir.iterdir():
247
+ name = f.name
248
+ if name.endswith("_hitmap.png"):
249
+ hitmap_path = f
250
+ elif name.endswith(".svg") and "_hitmap" not in name:
251
+ png_path = f
252
+ elif (
253
+ name.endswith(".png") and "_hitmap" not in name and png_path is None
254
+ ):
255
+ png_path = f
256
+
257
+ # Merge spec with encoding and theme
258
+ bundle_spec = bundle_data.get("spec", {})
259
+ if "encoding" in bundle_data:
260
+ bundle_spec["encoding"] = bundle_data["encoding"]
261
+ if "theme" in bundle_data:
262
+ bundle_spec["theme"] = bundle_data["theme"]
263
+
264
+ if hitmap_path and "hit_regions" in bundle_spec:
265
+ bundle_spec["hit_regions"]["hit_map"] = str(hitmap_path.name)
266
+
267
+ return (
268
+ spec_path,
269
+ csv_path if csv_path and csv_path.exists() else None,
270
+ png_path if png_path and png_path.exists() else None,
271
+ hitmap_path if hitmap_path and hitmap_path.exists() else None,
272
+ bundle_spec,
273
+ )
274
+
275
+
276
+ def resolve_stx_bundle(path: Path) -> Tuple:
277
+ """
278
+ Resolve paths from a .stx bundle (unified FTS format).
279
+
280
+ Parameters
281
+ ----------
282
+ path : Path
283
+ Path to .stx bundle (.stx, .stx.d, .zip, or directory)
284
+
285
+ Returns
286
+ -------
287
+ tuple
288
+ (json_path, csv_path, png_path, hitmap_path, bundle_spec, element_info)
289
+ """
290
+ from scitex.fts import FTS
291
+
292
+ spath = str(path)
293
+
294
+ # Handle ZIP vs directory
295
+ if spath.endswith((".stx", ".zip")) and not spath.endswith(".stx.d"):
296
+ if not path.exists():
297
+ raise FileNotFoundError(f"Bundle not found: {path}")
298
+ temp_dir = tempfile.mkdtemp(prefix="scitex_edit_stx_")
299
+ with zipfile.ZipFile(path, "r") as zf:
300
+ zf.extractall(temp_dir)
301
+ bundle_dir = Path(temp_dir)
302
+ for item in bundle_dir.iterdir():
303
+ if item.is_dir() and str(item).endswith((".stx.d", ".d")):
304
+ bundle_dir = item
305
+ break
306
+ else:
307
+ bundle_dir = Path(path)
308
+ if not bundle_dir.exists():
309
+ raise FileNotFoundError(f"Bundle directory not found: {bundle_dir}")
310
+
311
+ # Load using FTS class
312
+ fts_bundle = FTS(bundle_dir)
313
+
314
+ # Find spec path (canonical/spec.json or spec.json)
315
+ spec_path = bundle_dir / "canonical" / "spec.json"
316
+ if not spec_path.exists():
317
+ spec_path = bundle_dir / "spec.json"
318
+
319
+ # Find exports (artifacts/exports or exports/)
320
+ png_path = None
321
+ hitmap_path = None
322
+ csv_path = None
323
+ for exports_dir in [bundle_dir / "artifacts" / "exports", bundle_dir / "exports"]:
324
+ if exports_dir.exists():
325
+ for f in exports_dir.iterdir():
326
+ name = f.name
327
+ if name.endswith("_hitmap.png"):
328
+ hitmap_path = f
329
+ elif name.endswith(".svg") and "_hitmap" not in name:
330
+ png_path = f
331
+ elif (
332
+ name.endswith(".png") and "_hitmap" not in name and png_path is None
333
+ ):
334
+ png_path = f
335
+
336
+ # Find CSV (canonical/data.csv or root)
337
+ for search_dir in [bundle_dir / "canonical", bundle_dir]:
338
+ for f in search_dir.glob("*.csv"):
339
+ csv_path = f
340
+ break
341
+ if csv_path:
342
+ break
343
+
344
+ # Build bundle spec from FTS
345
+ bundle_spec = fts_bundle.to_dict() if hasattr(fts_bundle, "to_dict") else {}
346
+
347
+ # Build element info for editor
348
+ element_info = {
349
+ "elements": getattr(fts_bundle, "elements", []),
350
+ "size_mm": fts_bundle.node.size_mm if fts_bundle.node else None,
351
+ "bundle_dir": str(bundle_dir),
352
+ "bundle_path": str(path),
353
+ "is_directory": bundle_dir.is_dir(),
354
+ }
355
+
356
+ return (
357
+ spec_path,
358
+ csv_path if csv_path and csv_path.exists() else None,
359
+ png_path if png_path and png_path.exists() else None,
360
+ hitmap_path if hitmap_path and hitmap_path.exists() else None,
361
+ bundle_spec,
362
+ element_info,
363
+ )
364
+
365
+
366
+ # EOF
@@ -0,0 +1,175 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: 2025-12-20
3
+ # File: /home/ywatanabe/proj/scitex-code/src/scitex/fsb/_fig/_editor/_cui/_editor_launcher.py
4
+
5
+ """Main edit function for launching Flask visual editor."""
6
+
7
+ from pathlib import Path
8
+ from typing import Union
9
+
10
+ from ._backend_detector import detect_best_backend, print_available_backends
11
+ from ._bundle_resolver import (
12
+ resolve_figz_bundle,
13
+ resolve_pltz_bundle,
14
+ )
15
+ from ._path_resolver import resolve_figure_paths
16
+
17
+ __all__ = ["edit"]
18
+
19
+
20
+ def edit(
21
+ path: Union[str, Path],
22
+ apply_manual: bool = True,
23
+ port: int = 5050,
24
+ ) -> None:
25
+ """
26
+ Launch interactive Flask editor for figure style/annotation editing.
27
+
28
+ Parameters
29
+ ----------
30
+ path : str or Path
31
+ Path to figure file. Can be:
32
+ - .stx or .stx.d unified element bundle
33
+ - .pltz.d directory bundle (recommended for hitmap selection)
34
+ - .pltz ZIP bundle
35
+ - .figz or .figz.d multi-panel bundle
36
+ - JSON file (figure.json or figure.manual.json)
37
+ - CSV file (figure.csv) - for data-only start
38
+ - PNG file (figure.png)
39
+ apply_manual : bool, optional
40
+ If True, load .manual.json overrides if exists (default: True)
41
+ port : int, optional
42
+ Port number for Flask editor. Default: 5050.
43
+
44
+ Returns
45
+ -------
46
+ None
47
+ Editor runs in Flask event loop. Changes saved to .manual.json.
48
+ """
49
+ path = Path(path)
50
+ spath = str(path)
51
+ parent_str = str(path.parent) if path.is_file() else ""
52
+
53
+ # Panel info for multi-panel figures
54
+ panel_info = None
55
+
56
+ # Resolve paths based on input type
57
+ json_path, csv_path, png_path, hitmap_path, bundle_spec, panel_info = (
58
+ _resolve_paths(path, spath, parent_str)
59
+ )
60
+
61
+ if not json_path.exists():
62
+ raise FileNotFoundError(f"JSON file not found: {json_path}")
63
+
64
+ # Load data
65
+ import scitex as stx
66
+
67
+ metadata = bundle_spec if bundle_spec else stx.io.load(json_path)
68
+ csv_data = None
69
+ if csv_path and csv_path.exists():
70
+ csv_data = stx.io.load(csv_path)
71
+
72
+ # Load manual overrides if exists
73
+ manual_path = json_path.with_suffix(".manual.json")
74
+ manual_overrides = None
75
+ if apply_manual and manual_path.exists():
76
+ manual_data = stx.io.load(manual_path)
77
+ manual_overrides = manual_data.get("overrides", {})
78
+
79
+ # Verify Flask is available
80
+ detect_best_backend()
81
+
82
+ # Print status
83
+ print_available_backends()
84
+ print(f"Launching Flask editor for: {json_path}")
85
+
86
+ # Launch Flask editor
87
+ _launch_flask(
88
+ json_path=json_path,
89
+ metadata=metadata,
90
+ csv_data=csv_data,
91
+ png_path=png_path,
92
+ hitmap_path=hitmap_path,
93
+ manual_overrides=manual_overrides,
94
+ port=port,
95
+ panel_info=panel_info,
96
+ )
97
+
98
+
99
+ def _resolve_paths(path: Path, spath: str, parent_str: str) -> tuple:
100
+ """Resolve paths based on input type."""
101
+ panel_info = None
102
+ hitmap_path = None
103
+ bundle_spec = None
104
+
105
+ # Check if this is a .stx bundle (unified element format)
106
+ if spath.endswith(".stx.d") or spath.endswith(".stx"):
107
+ from ._bundle_resolver import resolve_stx_bundle
108
+
109
+ json_path, csv_path, png_path, hitmap_path, bundle_spec, panel_info = (
110
+ resolve_stx_bundle(path)
111
+ )
112
+ # Check if this is a .figz bundle (multi-panel figure)
113
+ elif spath.endswith(".figz.d") or spath.endswith(".figz"):
114
+ json_path, csv_path, png_path, hitmap_path, bundle_spec, panel_info = (
115
+ resolve_figz_bundle(path)
116
+ )
117
+ # Check if this is a .pltz bundle
118
+ elif (
119
+ spath.endswith(".pltz.d")
120
+ or spath.endswith(".pltz")
121
+ or parent_str.endswith(".pltz.d")
122
+ ):
123
+ bundle_path = path.parent if parent_str.endswith(".pltz.d") else path
124
+ json_path, csv_path, png_path, hitmap_path, bundle_spec = resolve_pltz_bundle(
125
+ bundle_path
126
+ )
127
+ # Check if file is inside a .figz.d
128
+ elif parent_str.endswith(".figz.d") or (
129
+ path.parent.parent and str(path.parent.parent).endswith(".figz.d")
130
+ ):
131
+ figz_path = (
132
+ path.parent if parent_str.endswith(".figz.d") else path.parent.parent
133
+ )
134
+ json_path, csv_path, png_path, hitmap_path, bundle_spec, panel_info = (
135
+ resolve_figz_bundle(figz_path)
136
+ )
137
+ else:
138
+ # Standard file paths
139
+ json_path, csv_path, png_path = resolve_figure_paths(path)
140
+
141
+ return json_path, csv_path, png_path, hitmap_path, bundle_spec, panel_info
142
+
143
+
144
+ def _launch_flask(
145
+ json_path,
146
+ metadata,
147
+ csv_data,
148
+ png_path,
149
+ hitmap_path,
150
+ manual_overrides,
151
+ port,
152
+ panel_info,
153
+ ):
154
+ """Launch Flask web editor."""
155
+ try:
156
+ from .._gui._flask_editor import WebEditor
157
+
158
+ editor = WebEditor(
159
+ json_path=json_path,
160
+ metadata=metadata,
161
+ csv_data=csv_data,
162
+ png_path=png_path,
163
+ hitmap_path=hitmap_path,
164
+ manual_overrides=manual_overrides,
165
+ port=port,
166
+ panel_info=panel_info,
167
+ )
168
+ editor.run()
169
+ except ImportError as e:
170
+ raise ImportError(
171
+ "Flask backend requires Flask. Install with: pip install flask"
172
+ ) from e
173
+
174
+
175
+ # EOF
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: "2025-12-14 (ywatanabe)"
3
+ # File: /home/ywatanabe/proj/scitex-code/src/scitex/fig/editor/edit/manual_handler.py
4
+
5
+ """Manual override handling for figure editor."""
6
+
7
+ import hashlib
8
+ from pathlib import Path
9
+
10
+ __all__ = ["compute_file_hash", "save_manual_overrides"]
11
+
12
+
13
+ def compute_file_hash(path: Path) -> str:
14
+ """Compute SHA256 hash of file contents."""
15
+ with open(path, "rb") as f:
16
+ return hashlib.sha256(f.read()).hexdigest()
17
+
18
+
19
+ def save_manual_overrides(json_path: Path, overrides: dict) -> Path:
20
+ """
21
+ Save manual overrides to .manual.json file.
22
+
23
+ Parameters
24
+ ----------
25
+ json_path : Path
26
+ Path to base JSON file
27
+ overrides : dict
28
+ Override settings (styles, annotations, etc.)
29
+
30
+ Returns
31
+ -------
32
+ Path
33
+ Path to saved manual.json file
34
+ """
35
+ import scitex as stx
36
+
37
+ manual_path = json_path.with_suffix(".manual.json")
38
+
39
+ # Compute hash of base JSON for staleness detection
40
+ base_hash = compute_file_hash(json_path)
41
+
42
+ manual_data = {
43
+ "base_file": json_path.name,
44
+ "base_hash": base_hash,
45
+ "overrides": overrides,
46
+ }
47
+
48
+ stx.io.save(manual_data, manual_path)
49
+ return manual_path
50
+
51
+
52
+ # EOF