scitex 2.7.0__py3-none-any.whl → 2.7.3__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 (297) hide show
  1. scitex/__init__.py +6 -2
  2. scitex/__version__.py +1 -1
  3. scitex/audio/README.md +52 -0
  4. scitex/audio/__init__.py +384 -0
  5. scitex/audio/__main__.py +129 -0
  6. scitex/audio/_tts.py +334 -0
  7. scitex/audio/engines/__init__.py +44 -0
  8. scitex/audio/engines/base.py +275 -0
  9. scitex/audio/engines/elevenlabs_engine.py +143 -0
  10. scitex/audio/engines/gtts_engine.py +162 -0
  11. scitex/audio/engines/pyttsx3_engine.py +131 -0
  12. scitex/audio/mcp_server.py +757 -0
  13. scitex/bridge/_helpers.py +1 -1
  14. scitex/bridge/_plt_vis.py +1 -1
  15. scitex/bridge/_stats_vis.py +1 -1
  16. scitex/dev/plt/__init__.py +272 -0
  17. scitex/dev/plt/plot_mpl_axhline.py +28 -0
  18. scitex/dev/plt/plot_mpl_axhspan.py +28 -0
  19. scitex/dev/plt/plot_mpl_axvline.py +28 -0
  20. scitex/dev/plt/plot_mpl_axvspan.py +28 -0
  21. scitex/dev/plt/plot_mpl_bar.py +29 -0
  22. scitex/dev/plt/plot_mpl_barh.py +29 -0
  23. scitex/dev/plt/plot_mpl_boxplot.py +28 -0
  24. scitex/dev/plt/plot_mpl_contour.py +31 -0
  25. scitex/dev/plt/plot_mpl_contourf.py +31 -0
  26. scitex/dev/plt/plot_mpl_errorbar.py +30 -0
  27. scitex/dev/plt/plot_mpl_eventplot.py +28 -0
  28. scitex/dev/plt/plot_mpl_fill.py +30 -0
  29. scitex/dev/plt/plot_mpl_fill_between.py +31 -0
  30. scitex/dev/plt/plot_mpl_hexbin.py +28 -0
  31. scitex/dev/plt/plot_mpl_hist.py +28 -0
  32. scitex/dev/plt/plot_mpl_hist2d.py +28 -0
  33. scitex/dev/plt/plot_mpl_imshow.py +29 -0
  34. scitex/dev/plt/plot_mpl_pcolormesh.py +31 -0
  35. scitex/dev/plt/plot_mpl_pie.py +29 -0
  36. scitex/dev/plt/plot_mpl_plot.py +29 -0
  37. scitex/dev/plt/plot_mpl_quiver.py +31 -0
  38. scitex/dev/plt/plot_mpl_scatter.py +28 -0
  39. scitex/dev/plt/plot_mpl_stackplot.py +31 -0
  40. scitex/dev/plt/plot_mpl_stem.py +29 -0
  41. scitex/dev/plt/plot_mpl_step.py +29 -0
  42. scitex/dev/plt/plot_mpl_violinplot.py +28 -0
  43. scitex/dev/plt/plot_sns_barplot.py +29 -0
  44. scitex/dev/plt/plot_sns_boxplot.py +29 -0
  45. scitex/dev/plt/plot_sns_heatmap.py +28 -0
  46. scitex/dev/plt/plot_sns_histplot.py +29 -0
  47. scitex/dev/plt/plot_sns_kdeplot.py +29 -0
  48. scitex/dev/plt/plot_sns_lineplot.py +31 -0
  49. scitex/dev/plt/plot_sns_scatterplot.py +29 -0
  50. scitex/dev/plt/plot_sns_stripplot.py +29 -0
  51. scitex/dev/plt/plot_sns_swarmplot.py +29 -0
  52. scitex/dev/plt/plot_sns_violinplot.py +29 -0
  53. scitex/dev/plt/plot_stx_bar.py +29 -0
  54. scitex/dev/plt/plot_stx_barh.py +29 -0
  55. scitex/dev/plt/plot_stx_box.py +28 -0
  56. scitex/dev/plt/plot_stx_boxplot.py +28 -0
  57. scitex/dev/plt/plot_stx_conf_mat.py +28 -0
  58. scitex/dev/plt/plot_stx_contour.py +31 -0
  59. scitex/dev/plt/plot_stx_ecdf.py +28 -0
  60. scitex/dev/plt/plot_stx_errorbar.py +30 -0
  61. scitex/dev/plt/plot_stx_fill_between.py +31 -0
  62. scitex/dev/plt/plot_stx_fillv.py +28 -0
  63. scitex/dev/plt/plot_stx_heatmap.py +28 -0
  64. scitex/dev/plt/plot_stx_image.py +28 -0
  65. scitex/dev/plt/plot_stx_imshow.py +28 -0
  66. scitex/dev/plt/plot_stx_joyplot.py +28 -0
  67. scitex/dev/plt/plot_stx_kde.py +28 -0
  68. scitex/dev/plt/plot_stx_line.py +28 -0
  69. scitex/dev/plt/plot_stx_mean_ci.py +28 -0
  70. scitex/dev/plt/plot_stx_mean_std.py +28 -0
  71. scitex/dev/plt/plot_stx_median_iqr.py +28 -0
  72. scitex/dev/plt/plot_stx_raster.py +28 -0
  73. scitex/dev/plt/plot_stx_rectangle.py +28 -0
  74. scitex/dev/plt/plot_stx_scatter.py +29 -0
  75. scitex/dev/plt/plot_stx_shaded_line.py +29 -0
  76. scitex/dev/plt/plot_stx_violin.py +28 -0
  77. scitex/dev/plt/plot_stx_violinplot.py +28 -0
  78. scitex/fig/__init__.py +352 -0
  79. scitex/{vis → fig}/backend/_parser.py +1 -1
  80. scitex/{vis → fig}/canvas.py +1 -1
  81. scitex/{vis → fig}/editor/_defaults.py +70 -5
  82. scitex/fig/editor/_edit.py +751 -0
  83. scitex/{vis → fig}/editor/_qt_editor.py +181 -1
  84. scitex/fig/editor/flask_editor/_bbox.py +1276 -0
  85. scitex/fig/editor/flask_editor/_core.py +624 -0
  86. scitex/{vis → fig}/editor/flask_editor/_plotter.py +38 -4
  87. scitex/fig/editor/flask_editor/_renderer.py +739 -0
  88. scitex/{vis → fig}/editor/flask_editor/templates/__init__.py +1 -1
  89. scitex/fig/editor/flask_editor/templates/_html.py +834 -0
  90. scitex/fig/editor/flask_editor/templates/_scripts.py +3136 -0
  91. scitex/{vis → fig}/editor/flask_editor/templates/_styles.py +625 -18
  92. scitex/{vis → fig}/io/__init__.py +13 -1
  93. scitex/fig/io/_bundle.py +973 -0
  94. scitex/{vis → fig}/io/_canvas.py +1 -1
  95. scitex/{vis → fig}/io/_data.py +1 -1
  96. scitex/{vis → fig}/io/_export.py +1 -1
  97. scitex/{vis → fig}/io/_load.py +1 -1
  98. scitex/{vis → fig}/io/_panel.py +1 -1
  99. scitex/{vis → fig}/io/_save.py +1 -1
  100. scitex/{vis → fig}/model/__init__.py +1 -1
  101. scitex/{vis → fig}/model/_annotations.py +1 -1
  102. scitex/{vis → fig}/model/_axes.py +1 -1
  103. scitex/{vis → fig}/model/_figure.py +1 -1
  104. scitex/{vis → fig}/model/_guides.py +1 -1
  105. scitex/{vis → fig}/model/_plot.py +1 -1
  106. scitex/{vis → fig}/model/_styles.py +1 -1
  107. scitex/{vis → fig}/utils/__init__.py +1 -1
  108. scitex/io/__init__.py +10 -26
  109. scitex/io/_bundle.py +434 -0
  110. scitex/io/_flush.py +5 -2
  111. scitex/io/_load.py +98 -0
  112. scitex/io/_load_modules/_H5Explorer.py +5 -2
  113. scitex/io/_load_modules/_canvas.py +2 -2
  114. scitex/io/_load_modules/_image.py +3 -4
  115. scitex/io/_load_modules/_txt.py +4 -2
  116. scitex/io/_metadata.py +34 -324
  117. scitex/io/_metadata_modules/__init__.py +46 -0
  118. scitex/io/_metadata_modules/_embed.py +70 -0
  119. scitex/io/_metadata_modules/_read.py +64 -0
  120. scitex/io/_metadata_modules/_utils.py +79 -0
  121. scitex/io/_metadata_modules/embed_metadata_jpeg.py +74 -0
  122. scitex/io/_metadata_modules/embed_metadata_pdf.py +53 -0
  123. scitex/io/_metadata_modules/embed_metadata_png.py +26 -0
  124. scitex/io/_metadata_modules/embed_metadata_svg.py +62 -0
  125. scitex/io/_metadata_modules/read_metadata_jpeg.py +57 -0
  126. scitex/io/_metadata_modules/read_metadata_pdf.py +51 -0
  127. scitex/io/_metadata_modules/read_metadata_png.py +39 -0
  128. scitex/io/_metadata_modules/read_metadata_svg.py +44 -0
  129. scitex/io/_qr_utils.py +5 -3
  130. scitex/io/_save.py +548 -30
  131. scitex/io/_save_modules/_canvas.py +3 -3
  132. scitex/io/_save_modules/_image.py +5 -9
  133. scitex/io/_save_modules/_tex.py +7 -4
  134. scitex/io/utils/h5_to_zarr.py +11 -9
  135. scitex/msword/__init__.py +255 -0
  136. scitex/msword/profiles.py +357 -0
  137. scitex/msword/reader.py +753 -0
  138. scitex/msword/utils.py +289 -0
  139. scitex/msword/writer.py +362 -0
  140. scitex/plt/__init__.py +5 -2
  141. scitex/plt/_subplots/_AxesWrapper.py +6 -6
  142. scitex/plt/_subplots/_AxisWrapper.py +15 -9
  143. scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/__init__.py +36 -0
  144. scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/_labels.py +264 -0
  145. scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/_metadata.py +213 -0
  146. scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/_visual.py +128 -0
  147. scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/__init__.py +59 -0
  148. scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_base.py +34 -0
  149. scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_scientific.py +593 -0
  150. scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_statistical.py +654 -0
  151. scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_stx_aliases.py +527 -0
  152. scitex/plt/_subplots/_AxisWrapperMixins/_RawMatplotlibMixin.py +321 -0
  153. scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/__init__.py +33 -0
  154. scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_base.py +152 -0
  155. scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_wrappers.py +600 -0
  156. scitex/plt/_subplots/_AxisWrapperMixins/__init__.py +79 -5
  157. scitex/plt/_subplots/_FigWrapper.py +6 -6
  158. scitex/plt/_subplots/_SubplotsWrapper.py +28 -18
  159. scitex/plt/_subplots/_export_as_csv.py +35 -5
  160. scitex/plt/_subplots/_export_as_csv_formatters/__init__.py +8 -0
  161. scitex/plt/_subplots/_export_as_csv_formatters/_format_annotate.py +10 -21
  162. scitex/plt/_subplots/_export_as_csv_formatters/_format_eventplot.py +18 -7
  163. scitex/plt/_subplots/_export_as_csv_formatters/_format_imshow2d.py +28 -12
  164. scitex/plt/_subplots/_export_as_csv_formatters/_format_matshow.py +10 -4
  165. scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_imshow.py +13 -1
  166. scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_kde.py +12 -2
  167. scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_scatter.py +10 -3
  168. scitex/plt/_subplots/_export_as_csv_formatters/_format_quiver.py +10 -4
  169. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_jointplot.py +18 -3
  170. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_lineplot.py +44 -36
  171. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_pairplot.py +14 -2
  172. scitex/plt/_subplots/_export_as_csv_formatters/_format_streamplot.py +11 -5
  173. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_bar.py +84 -0
  174. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_barh.py +85 -0
  175. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_conf_mat.py +14 -3
  176. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_contour.py +54 -0
  177. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_ecdf.py +14 -2
  178. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_errorbar.py +120 -0
  179. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_heatmap.py +16 -6
  180. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_image.py +29 -19
  181. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_imshow.py +63 -0
  182. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_joyplot.py +22 -5
  183. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_mean_ci.py +18 -14
  184. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_mean_std.py +18 -14
  185. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_median_iqr.py +18 -14
  186. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_raster.py +10 -2
  187. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_scatter.py +51 -0
  188. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_scatter_hist.py +18 -9
  189. scitex/plt/ax/_plot/_stx_ecdf.py +4 -2
  190. scitex/plt/gallery/_generate.py +421 -14
  191. scitex/plt/io/__init__.py +53 -0
  192. scitex/plt/io/_bundle.py +490 -0
  193. scitex/plt/io/_layered_bundle.py +1343 -0
  194. scitex/plt/styles/SCITEX_STYLE.yaml +26 -0
  195. scitex/plt/styles/__init__.py +14 -0
  196. scitex/plt/styles/presets.py +78 -0
  197. scitex/plt/utils/__init__.py +13 -1
  198. scitex/plt/utils/_collect_figure_metadata.py +10 -14
  199. scitex/plt/utils/_configure_mpl.py +6 -18
  200. scitex/plt/utils/_crop.py +32 -14
  201. scitex/plt/utils/_csv_column_naming.py +54 -0
  202. scitex/plt/utils/_figure_mm.py +116 -1
  203. scitex/plt/utils/_hitmap.py +1643 -0
  204. scitex/plt/utils/metadata/__init__.py +25 -0
  205. scitex/plt/utils/metadata/_core.py +9 -10
  206. scitex/plt/utils/metadata/_dimensions.py +6 -3
  207. scitex/plt/utils/metadata/_editable_export.py +405 -0
  208. scitex/plt/utils/metadata/_geometry_extraction.py +570 -0
  209. scitex/schema/__init__.py +109 -16
  210. scitex/schema/_canvas.py +1 -1
  211. scitex/schema/_plot.py +1015 -0
  212. scitex/schema/_stats.py +2 -2
  213. scitex/stats/__init__.py +117 -0
  214. scitex/stats/io/__init__.py +29 -0
  215. scitex/stats/io/_bundle.py +156 -0
  216. scitex/tex/__init__.py +4 -0
  217. scitex/tex/_export.py +890 -0
  218. {scitex-2.7.0.dist-info → scitex-2.7.3.dist-info}/METADATA +11 -1
  219. {scitex-2.7.0.dist-info → scitex-2.7.3.dist-info}/RECORD +238 -170
  220. scitex/io/memo.md +0 -2827
  221. scitex/plt/REQUESTS.md +0 -191
  222. scitex/plt/_subplots/TODO.md +0 -53
  223. scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin.py +0 -559
  224. scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin.py +0 -1609
  225. scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin.py +0 -447
  226. scitex/plt/templates/research-master/scitex/vis/gallery/area/fill_between.json +0 -110
  227. scitex/plt/templates/research-master/scitex/vis/gallery/area/fill_betweenx.json +0 -88
  228. scitex/plt/templates/research-master/scitex/vis/gallery/area/stx_fill_between.json +0 -103
  229. scitex/plt/templates/research-master/scitex/vis/gallery/area/stx_fillv.json +0 -106
  230. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/bar.json +0 -92
  231. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/barh.json +0 -92
  232. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/boxplot.json +0 -92
  233. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_bar.json +0 -84
  234. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_barh.json +0 -84
  235. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_box.json +0 -83
  236. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_boxplot.json +0 -93
  237. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_violin.json +0 -91
  238. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_violinplot.json +0 -91
  239. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/violinplot.json +0 -91
  240. scitex/plt/templates/research-master/scitex/vis/gallery/contour/contour.json +0 -97
  241. scitex/plt/templates/research-master/scitex/vis/gallery/contour/contourf.json +0 -98
  242. scitex/plt/templates/research-master/scitex/vis/gallery/contour/stx_contour.json +0 -84
  243. scitex/plt/templates/research-master/scitex/vis/gallery/distribution/hist.json +0 -101
  244. scitex/plt/templates/research-master/scitex/vis/gallery/distribution/hist2d.json +0 -96
  245. scitex/plt/templates/research-master/scitex/vis/gallery/distribution/stx_ecdf.json +0 -95
  246. scitex/plt/templates/research-master/scitex/vis/gallery/distribution/stx_joyplot.json +0 -95
  247. scitex/plt/templates/research-master/scitex/vis/gallery/distribution/stx_kde.json +0 -93
  248. scitex/plt/templates/research-master/scitex/vis/gallery/grid/imshow.json +0 -95
  249. scitex/plt/templates/research-master/scitex/vis/gallery/grid/matshow.json +0 -95
  250. scitex/plt/templates/research-master/scitex/vis/gallery/grid/stx_conf_mat.json +0 -83
  251. scitex/plt/templates/research-master/scitex/vis/gallery/grid/stx_heatmap.json +0 -92
  252. scitex/plt/templates/research-master/scitex/vis/gallery/grid/stx_image.json +0 -121
  253. scitex/plt/templates/research-master/scitex/vis/gallery/grid/stx_imshow.json +0 -84
  254. scitex/plt/templates/research-master/scitex/vis/gallery/line/plot.json +0 -110
  255. scitex/plt/templates/research-master/scitex/vis/gallery/line/step.json +0 -92
  256. scitex/plt/templates/research-master/scitex/vis/gallery/line/stx_line.json +0 -95
  257. scitex/plt/templates/research-master/scitex/vis/gallery/line/stx_shaded_line.json +0 -96
  258. scitex/plt/templates/research-master/scitex/vis/gallery/scatter/hexbin.json +0 -95
  259. scitex/plt/templates/research-master/scitex/vis/gallery/scatter/scatter.json +0 -95
  260. scitex/plt/templates/research-master/scitex/vis/gallery/scatter/stem.json +0 -92
  261. scitex/plt/templates/research-master/scitex/vis/gallery/scatter/stx_scatter.json +0 -84
  262. scitex/plt/templates/research-master/scitex/vis/gallery/special/pie.json +0 -94
  263. scitex/plt/templates/research-master/scitex/vis/gallery/special/stx_raster.json +0 -109
  264. scitex/plt/templates/research-master/scitex/vis/gallery/special/stx_rectangle.json +0 -108
  265. scitex/plt/templates/research-master/scitex/vis/gallery/statistical/errorbar.json +0 -93
  266. scitex/plt/templates/research-master/scitex/vis/gallery/statistical/stx_errorbar.json +0 -84
  267. scitex/plt/templates/research-master/scitex/vis/gallery/statistical/stx_mean_ci.json +0 -96
  268. scitex/plt/templates/research-master/scitex/vis/gallery/statistical/stx_mean_std.json +0 -96
  269. scitex/plt/templates/research-master/scitex/vis/gallery/statistical/stx_median_iqr.json +0 -96
  270. scitex/plt/templates/research-master/scitex/vis/gallery/vector/quiver.json +0 -99
  271. scitex/plt/templates/research-master/scitex/vis/gallery/vector/streamplot.json +0 -100
  272. scitex/vis/__init__.py +0 -177
  273. scitex/vis/editor/_edit.py +0 -390
  274. scitex/vis/editor/flask_editor/_bbox.py +0 -529
  275. scitex/vis/editor/flask_editor/_core.py +0 -168
  276. scitex/vis/editor/flask_editor/_renderer.py +0 -393
  277. scitex/vis/editor/flask_editor/templates/_html.py +0 -513
  278. scitex/vis/editor/flask_editor/templates/_scripts.py +0 -1261
  279. /scitex/{vis → fig}/README.md +0 -0
  280. /scitex/{vis → fig}/backend/__init__.py +0 -0
  281. /scitex/{vis → fig}/backend/_export.py +0 -0
  282. /scitex/{vis → fig}/backend/_render.py +0 -0
  283. /scitex/{vis → fig}/docs/CANVAS_ARCHITECTURE.md +0 -0
  284. /scitex/{vis → fig}/editor/__init__.py +0 -0
  285. /scitex/{vis → fig}/editor/_dearpygui_editor.py +0 -0
  286. /scitex/{vis → fig}/editor/_flask_editor.py +0 -0
  287. /scitex/{vis → fig}/editor/_mpl_editor.py +0 -0
  288. /scitex/{vis → fig}/editor/_tkinter_editor.py +0 -0
  289. /scitex/{vis → fig}/editor/flask_editor/__init__.py +0 -0
  290. /scitex/{vis → fig}/editor/flask_editor/_utils.py +0 -0
  291. /scitex/{vis → fig}/io/_directory.py +0 -0
  292. /scitex/{vis → fig}/model/_plot_types.py +0 -0
  293. /scitex/{vis → fig}/utils/_defaults.py +0 -0
  294. /scitex/{vis → fig}/utils/_validate.py +0 -0
  295. {scitex-2.7.0.dist-info → scitex-2.7.3.dist-info}/WHEEL +0 -0
  296. {scitex-2.7.0.dist-info → scitex-2.7.3.dist-info}/entry_points.txt +0 -0
  297. {scitex-2.7.0.dist-info → scitex-2.7.3.dist-info}/licenses/LICENSE +0 -0
@@ -1,529 +0,0 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
- # File: ./src/scitex/vis/editor/flask_editor/bbox.py
4
- """Bounding box extraction for figure elements."""
5
-
6
- from typing import Dict, Any
7
-
8
-
9
- def extract_bboxes(
10
- fig, ax, renderer, img_width: int, img_height: int
11
- ) -> Dict[str, Any]:
12
- """Extract bounding boxes for all figure elements (single-axis)."""
13
- from matplotlib.transforms import Bbox
14
-
15
- # Get figure tight bbox in inches
16
- fig_bbox = fig.get_tightbbox(renderer)
17
- tight_x0 = fig_bbox.x0
18
- tight_y0 = fig_bbox.y0
19
- tight_width = fig_bbox.width
20
- tight_height = fig_bbox.height
21
-
22
- # bbox_inches='tight' adds pad_inches (default 0.1) around the tight bbox
23
- pad_inches = 0.1
24
- saved_width_inches = tight_width + 2 * pad_inches
25
- saved_height_inches = tight_height + 2 * pad_inches
26
-
27
- # Scale factors for converting inches to pixels
28
- scale_x = img_width / saved_width_inches
29
- scale_y = img_height / saved_height_inches
30
-
31
- bboxes = {}
32
-
33
- def get_element_bbox(element, name):
34
- """Get element bbox in image pixel coordinates."""
35
- try:
36
- bbox = element.get_window_extent(renderer)
37
-
38
- elem_x0_inches = bbox.x0 / fig.dpi
39
- elem_x1_inches = bbox.x1 / fig.dpi
40
- elem_y0_inches = bbox.y0 / fig.dpi
41
- elem_y1_inches = bbox.y1 / fig.dpi
42
-
43
- x0_rel = elem_x0_inches - tight_x0 + pad_inches
44
- x1_rel = elem_x1_inches - tight_x0 + pad_inches
45
- y0_rel = saved_height_inches - (elem_y1_inches - tight_y0 + pad_inches)
46
- y1_rel = saved_height_inches - (elem_y0_inches - tight_y0 + pad_inches)
47
-
48
- bboxes[name] = {
49
- "x0": max(0, int(x0_rel * scale_x)),
50
- "y0": max(0, int(y0_rel * scale_y)),
51
- "x1": min(img_width, int(x1_rel * scale_x)),
52
- "y1": min(img_height, int(y1_rel * scale_y)),
53
- "label": name.replace("_", " ").title(),
54
- }
55
- except Exception as e:
56
- print(f"Error getting bbox for {name}: {e}")
57
-
58
- def bbox_to_img_coords(bbox):
59
- """Convert matplotlib bbox to image pixel coordinates."""
60
- x0_inches = bbox.x0 / fig.dpi
61
- y0_inches = bbox.y0 / fig.dpi
62
- x1_inches = bbox.x1 / fig.dpi
63
- y1_inches = bbox.y1 / fig.dpi
64
- x0_rel = x0_inches - tight_x0 + pad_inches
65
- y0_rel = y0_inches - tight_y0 + pad_inches
66
- x1_rel = x1_inches - tight_x0 + pad_inches
67
- y1_rel = y1_inches - tight_y0 + pad_inches
68
- return {
69
- "x0": int(x0_rel * scale_x),
70
- "y0": int((saved_height_inches - y1_rel) * scale_y),
71
- "x1": int(x1_rel * scale_x),
72
- "y1": int((saved_height_inches - y0_rel) * scale_y),
73
- }
74
-
75
- # Get bboxes for title, labels
76
- if ax.title.get_text():
77
- get_element_bbox(ax.title, "title")
78
- if ax.xaxis.label.get_text():
79
- get_element_bbox(ax.xaxis.label, "xlabel")
80
- if ax.yaxis.label.get_text():
81
- get_element_bbox(ax.yaxis.label, "ylabel")
82
-
83
- # Get axis bboxes
84
- _extract_axis_bboxes(ax, renderer, bboxes, bbox_to_img_coords, Bbox)
85
-
86
- # Get legend bbox
87
- legend = ax.get_legend()
88
- if legend:
89
- get_element_bbox(legend, "legend")
90
-
91
- # Get trace (line) bboxes
92
- _extract_trace_bboxes(
93
- ax,
94
- fig,
95
- renderer,
96
- bboxes,
97
- get_element_bbox,
98
- tight_x0,
99
- tight_y0,
100
- saved_height_inches,
101
- scale_x,
102
- scale_y,
103
- pad_inches,
104
- )
105
-
106
- return bboxes
107
-
108
-
109
- def extract_bboxes_multi(
110
- fig, axes_map: Dict[str, Any], renderer, img_width: int, img_height: int
111
- ) -> Dict[str, Any]:
112
- """Extract bounding boxes for all elements in a multi-axis figure.
113
-
114
- Args:
115
- fig: Matplotlib figure
116
- axes_map: Dict mapping axis IDs (e.g., 'ax_00') to matplotlib Axes objects
117
- renderer: Matplotlib renderer
118
- img_width: Image width in pixels
119
- img_height: Image height in pixels
120
-
121
- Returns:
122
- Dict with bboxes keyed by "{ax_id}_{element_type}" (e.g., "ax_00_xlabel")
123
- """
124
- from matplotlib.transforms import Bbox
125
-
126
- # Get figure tight bbox in inches
127
- fig_bbox = fig.get_tightbbox(renderer)
128
- tight_x0 = fig_bbox.x0
129
- tight_y0 = fig_bbox.y0
130
- tight_width = fig_bbox.width
131
- tight_height = fig_bbox.height
132
-
133
- # bbox_inches='tight' adds pad_inches (default 0.1) around the tight bbox
134
- pad_inches = 0.1
135
- saved_width_inches = tight_width + 2 * pad_inches
136
- saved_height_inches = tight_height + 2 * pad_inches
137
-
138
- # Scale factors for converting inches to pixels
139
- scale_x = img_width / saved_width_inches
140
- scale_y = img_height / saved_height_inches
141
-
142
- bboxes = {}
143
-
144
- def get_element_bbox(element, name, ax_id, current_ax=None):
145
- """Get element bbox in image pixel coordinates."""
146
- import numpy as np
147
- full_name = f"{ax_id}_{name}"
148
- try:
149
- bbox = element.get_window_extent(renderer)
150
-
151
- # Check for invalid bbox (infinity or NaN)
152
- if not (np.isfinite(bbox.x0) and np.isfinite(bbox.x1) and
153
- np.isfinite(bbox.y0) and np.isfinite(bbox.y1)):
154
- # Try to get bbox from data for scatter/collection elements
155
- if hasattr(element, 'get_offsets') and current_ax is not None:
156
- offsets = element.get_offsets()
157
- if len(offsets) > 0 and np.isfinite(offsets).all():
158
- # Use axis transform to get display coordinates
159
- display_coords = current_ax.transData.transform(offsets)
160
- x0 = display_coords[:, 0].min()
161
- x1 = display_coords[:, 0].max()
162
- y0 = display_coords[:, 1].min()
163
- y1 = display_coords[:, 1].max()
164
- if np.isfinite([x0, x1, y0, y1]).all():
165
- from matplotlib.transforms import Bbox
166
- bbox = Bbox.from_extents(x0, y0, x1, y1)
167
- else:
168
- return # Skip this element
169
- else:
170
- return # Skip this element
171
- else:
172
- return # Skip this element
173
-
174
- elem_x0_inches = bbox.x0 / fig.dpi
175
- elem_x1_inches = bbox.x1 / fig.dpi
176
- elem_y0_inches = bbox.y0 / fig.dpi
177
- elem_y1_inches = bbox.y1 / fig.dpi
178
-
179
- x0_rel = elem_x0_inches - tight_x0 + pad_inches
180
- x1_rel = elem_x1_inches - tight_x0 + pad_inches
181
- y0_rel = saved_height_inches - (elem_y1_inches - tight_y0 + pad_inches)
182
- y1_rel = saved_height_inches - (elem_y0_inches - tight_y0 + pad_inches)
183
-
184
- # Clamp values to avoid overflow
185
- x0_px = max(0, min(img_width, int(x0_rel * scale_x)))
186
- y0_px = max(0, min(img_height, int(y0_rel * scale_y)))
187
- x1_px = max(0, min(img_width, int(x1_rel * scale_x)))
188
- y1_px = max(0, min(img_height, int(y1_rel * scale_y)))
189
-
190
- bboxes[full_name] = {
191
- "x0": x0_px,
192
- "y0": y0_px,
193
- "x1": x1_px,
194
- "y1": y1_px,
195
- "label": f"{ax_id}: {name.replace('_', ' ').title()}",
196
- "ax_id": ax_id,
197
- }
198
- except Exception as e:
199
- print(f"Error getting bbox for {full_name}: {e}")
200
-
201
- def bbox_to_img_coords(bbox):
202
- """Convert matplotlib bbox to image pixel coordinates."""
203
- x0_inches = bbox.x0 / fig.dpi
204
- y0_inches = bbox.y0 / fig.dpi
205
- x1_inches = bbox.x1 / fig.dpi
206
- y1_inches = bbox.y1 / fig.dpi
207
- x0_rel = x0_inches - tight_x0 + pad_inches
208
- y0_rel = y0_inches - tight_y0 + pad_inches
209
- x1_rel = x1_inches - tight_x0 + pad_inches
210
- y1_rel = y1_inches - tight_y0 + pad_inches
211
- return {
212
- "x0": int(x0_rel * scale_x),
213
- "y0": int((saved_height_inches - y1_rel) * scale_y),
214
- "x1": int(x1_rel * scale_x),
215
- "y1": int((saved_height_inches - y0_rel) * scale_y),
216
- }
217
-
218
- # Extract bboxes for each axis
219
- for ax_id, ax in axes_map.items():
220
- # Get axes bounding box (the entire panel area)
221
- try:
222
- ax_bbox = ax.get_window_extent(renderer)
223
- coords = bbox_to_img_coords(ax_bbox)
224
- bboxes[f"{ax_id}_panel"] = {
225
- **coords,
226
- "label": f"Panel {ax_id}",
227
- "ax_id": ax_id,
228
- "is_panel": True,
229
- }
230
- except Exception as e:
231
- print(f"Error getting panel bbox for {ax_id}: {e}")
232
-
233
- # Get bboxes for title, labels
234
- if ax.title.get_text():
235
- get_element_bbox(ax.title, "title", ax_id, ax)
236
- if ax.xaxis.label.get_text():
237
- get_element_bbox(ax.xaxis.label, "xlabel", ax_id, ax)
238
- if ax.yaxis.label.get_text():
239
- get_element_bbox(ax.yaxis.label, "ylabel", ax_id, ax)
240
-
241
- # Get legend bbox
242
- legend = ax.get_legend()
243
- if legend:
244
- get_element_bbox(legend, "legend", ax_id, ax)
245
-
246
- # Get trace (line) bboxes
247
- _extract_trace_bboxes_for_axis(
248
- ax,
249
- ax_id,
250
- fig,
251
- renderer,
252
- bboxes,
253
- get_element_bbox,
254
- tight_x0,
255
- tight_y0,
256
- saved_height_inches,
257
- scale_x,
258
- scale_y,
259
- pad_inches,
260
- )
261
-
262
- return bboxes
263
-
264
-
265
- def _extract_trace_bboxes_for_axis(
266
- ax,
267
- ax_id,
268
- fig,
269
- renderer,
270
- bboxes,
271
- get_element_bbox,
272
- tight_x0,
273
- tight_y0,
274
- saved_height_inches,
275
- scale_x,
276
- scale_y,
277
- pad_inches,
278
- ):
279
- """Extract bboxes for all data elements in a specific axis.
280
-
281
- Handles:
282
- - Lines (plot, errorbar lines)
283
- - Scatter points (PathCollection)
284
- - Fill areas (PolyCollection from fill_between)
285
- - Bars (Rectangle patches)
286
- """
287
- import numpy as np
288
-
289
- def coords_to_img_points(data_coords):
290
- """Convert data coordinates to image pixel coordinates."""
291
- if len(data_coords) == 0:
292
- return []
293
- transform = ax.transData
294
- points_display = transform.transform(data_coords)
295
- points_img = []
296
- for px, py in points_display:
297
- # Skip invalid points (NaN, infinity)
298
- if not np.isfinite(px) or not np.isfinite(py):
299
- continue
300
- px_inches = px / fig.dpi
301
- py_inches = py / fig.dpi
302
- x_rel = px_inches - tight_x0 + pad_inches
303
- y_rel = saved_height_inches - (py_inches - tight_y0 + pad_inches)
304
- # Clamp to reasonable bounds to avoid overflow
305
- x_img = max(-10000, min(10000, int(x_rel * scale_x)))
306
- y_img = max(-10000, min(10000, int(y_rel * scale_y)))
307
- points_img.append([x_img, y_img])
308
- # Downsample if too many
309
- if len(points_img) > 100:
310
- step = len(points_img) // 100
311
- points_img = points_img[::step]
312
- return points_img
313
-
314
- # 1. Extract lines (plot, errorbar lines, etc.)
315
- line_idx = 0
316
- for line in ax.get_lines():
317
- try:
318
- label = line.get_label()
319
- if label.startswith("_"):
320
- continue
321
-
322
- trace_name = f"trace_{line_idx}"
323
- full_name = f"{ax_id}_{trace_name}"
324
- get_element_bbox(line, trace_name, ax_id, ax)
325
-
326
- if full_name in bboxes:
327
- bboxes[full_name]["label"] = f"{ax_id}: {label or f'Line {line_idx}'}"
328
- bboxes[full_name]["trace_idx"] = line_idx
329
- bboxes[full_name]["element_type"] = "line"
330
-
331
- xdata, ydata = line.get_xdata(), line.get_ydata()
332
- if len(xdata) > 0:
333
- bboxes[full_name]["points"] = coords_to_img_points(
334
- list(zip(xdata, ydata))
335
- )
336
- line_idx += 1
337
- except Exception as e:
338
- print(f"Error getting line bbox for {ax_id}: {e}")
339
-
340
- # 2. Extract collections (scatter, fill_between, etc.)
341
- coll_idx = 0
342
- for coll in ax.collections:
343
- try:
344
- label = coll.get_label()
345
- if label.startswith("_"):
346
- # Still extract unlabeled collections but with generic name
347
- label = None
348
-
349
- coll_type = type(coll).__name__
350
- if coll_type == "PathCollection":
351
- # Scatter points
352
- element_name = f"scatter_{coll_idx}"
353
- full_name = f"{ax_id}_{element_name}"
354
- get_element_bbox(coll, element_name, ax_id, ax)
355
-
356
- if full_name in bboxes:
357
- bboxes[full_name]["label"] = f"{ax_id}: {label or f'Scatter {coll_idx}'}"
358
- bboxes[full_name]["element_type"] = "scatter"
359
-
360
- # Get scatter point positions
361
- offsets = coll.get_offsets()
362
- if len(offsets) > 0:
363
- bboxes[full_name]["points"] = coords_to_img_points(offsets)
364
-
365
- elif coll_type == "PolyCollection":
366
- # Fill areas (fill_between, etc.)
367
- element_name = f"fill_{coll_idx}"
368
- full_name = f"{ax_id}_{element_name}"
369
- get_element_bbox(coll, element_name, ax_id, ax)
370
-
371
- if full_name in bboxes:
372
- bboxes[full_name]["label"] = f"{ax_id}: {label or f'Fill {coll_idx}'}"
373
- bboxes[full_name]["element_type"] = "fill"
374
-
375
- coll_idx += 1
376
- except Exception as e:
377
- print(f"Error getting collection bbox for {ax_id}: {e}")
378
-
379
- # 3. Extract patches (bars, rectangles, etc.)
380
- patch_idx = 0
381
- for patch in ax.patches:
382
- try:
383
- label = patch.get_label()
384
- patch_type = type(patch).__name__
385
-
386
- if patch_type == "Rectangle":
387
- # Bar chart bars
388
- element_name = f"bar_{patch_idx}"
389
- full_name = f"{ax_id}_{element_name}"
390
- get_element_bbox(patch, element_name, ax_id, ax)
391
-
392
- if full_name in bboxes:
393
- bboxes[full_name]["label"] = f"{ax_id}: {label or f'Bar {patch_idx}'}"
394
- bboxes[full_name]["element_type"] = "bar"
395
-
396
- patch_idx += 1
397
- except Exception as e:
398
- print(f"Error getting patch bbox for {ax_id}: {e}")
399
-
400
-
401
- def _extract_axis_bboxes(ax, renderer, bboxes, bbox_to_img_coords, Bbox):
402
- """Extract bboxes for X and Y axis elements."""
403
- try:
404
- # X-axis: combine spine and tick labels into one bbox
405
- x_axis_bboxes = []
406
- for ticklabel in ax.xaxis.get_ticklabels():
407
- if ticklabel.get_visible():
408
- try:
409
- tb = ticklabel.get_window_extent(renderer)
410
- if tb.width > 0:
411
- x_axis_bboxes.append(tb)
412
- except Exception:
413
- pass
414
- for tick in ax.xaxis.get_major_ticks():
415
- if tick.tick1line.get_visible():
416
- try:
417
- tb = tick.tick1line.get_window_extent(renderer)
418
- if tb.width > 0 or tb.height > 0:
419
- x_axis_bboxes.append(tb)
420
- except Exception:
421
- pass
422
- spine_bbox = ax.spines["bottom"].get_window_extent(renderer)
423
- if spine_bbox.width > 0:
424
- if x_axis_bboxes:
425
- tick_union = Bbox.union(x_axis_bboxes)
426
- constrained_spine = Bbox.from_extents(
427
- tick_union.x0, spine_bbox.y0, tick_union.x1, spine_bbox.y1
428
- )
429
- x_axis_bboxes.append(constrained_spine)
430
- else:
431
- x_axis_bboxes.append(spine_bbox)
432
- if x_axis_bboxes:
433
- combined = Bbox.union(x_axis_bboxes)
434
- bboxes["xaxis_ticks"] = bbox_to_img_coords(combined)
435
- bboxes["xaxis_ticks"]["label"] = "X Spine & Ticks"
436
-
437
- # Y-axis: combine spine and tick labels into one bbox
438
- y_axis_bboxes = []
439
- for ticklabel in ax.yaxis.get_ticklabels():
440
- if ticklabel.get_visible():
441
- try:
442
- tb = ticklabel.get_window_extent(renderer)
443
- if tb.width > 0:
444
- y_axis_bboxes.append(tb)
445
- except Exception:
446
- pass
447
- for tick in ax.yaxis.get_major_ticks():
448
- if tick.tick1line.get_visible():
449
- try:
450
- tb = tick.tick1line.get_window_extent(renderer)
451
- if tb.width > 0 or tb.height > 0:
452
- y_axis_bboxes.append(tb)
453
- except Exception:
454
- pass
455
- spine_bbox = ax.spines["left"].get_window_extent(renderer)
456
- if spine_bbox.height > 0:
457
- if y_axis_bboxes:
458
- tick_union = Bbox.union(y_axis_bboxes)
459
- constrained_spine = Bbox.from_extents(
460
- spine_bbox.x0, tick_union.y0, spine_bbox.x1, tick_union.y1
461
- )
462
- y_axis_bboxes.append(constrained_spine)
463
- else:
464
- y_axis_bboxes.append(spine_bbox)
465
- if y_axis_bboxes:
466
- combined = Bbox.union(y_axis_bboxes)
467
- padded = Bbox.from_extents(
468
- combined.x0 - 10, combined.y0 - 5, combined.x1 + 5, combined.y1 + 5
469
- )
470
- bboxes["yaxis_ticks"] = bbox_to_img_coords(padded)
471
- bboxes["yaxis_ticks"]["label"] = "Y Spine & Ticks"
472
-
473
- except Exception as e:
474
- print(f"Error getting axis bboxes: {e}")
475
-
476
-
477
- def _extract_trace_bboxes(
478
- ax,
479
- fig,
480
- renderer,
481
- bboxes,
482
- get_element_bbox,
483
- tight_x0,
484
- tight_y0,
485
- saved_height_inches,
486
- scale_x,
487
- scale_y,
488
- pad_inches,
489
- ):
490
- """Extract bboxes for trace (line) elements with proximity detection points."""
491
- for idx, line in enumerate(ax.get_lines()):
492
- try:
493
- label = line.get_label()
494
- if label.startswith("_"):
495
- continue
496
- get_element_bbox(line, f"trace_{idx}")
497
- if f"trace_{idx}" in bboxes:
498
- bboxes[f"trace_{idx}"]["label"] = label or f"Trace {idx}"
499
- bboxes[f"trace_{idx}"]["trace_idx"] = idx
500
-
501
- # Get line data points in pixel coordinates for proximity detection
502
- xdata, ydata = line.get_xdata(), line.get_ydata()
503
- if len(xdata) > 0:
504
- transform = ax.transData
505
- points_display = transform.transform(list(zip(xdata, ydata)))
506
-
507
- points_img = []
508
- for px, py in points_display:
509
- px_inches = px / fig.dpi
510
- py_inches = py / fig.dpi
511
- x_rel = px_inches - tight_x0 + pad_inches
512
- y_rel = saved_height_inches - (
513
- py_inches - tight_y0 + pad_inches
514
- )
515
- x_img = int(x_rel * scale_x)
516
- y_img = int(y_rel * scale_y)
517
- points_img.append([x_img, y_img])
518
-
519
- # Downsample points if too many
520
- if len(points_img) > 100:
521
- step = len(points_img) // 100
522
- points_img = points_img[::step]
523
-
524
- bboxes[f"trace_{idx}"]["points"] = points_img
525
- except Exception as e:
526
- print(f"Error getting trace bbox: {e}")
527
-
528
-
529
- # EOF
@@ -1,168 +0,0 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
- # File: ./src/scitex/vis/editor/flask_editor/core.py
4
- """Core WebEditor class for Flask-based figure editing."""
5
-
6
- from pathlib import Path
7
- from typing import Dict, Any, Optional
8
- import copy
9
- import json
10
- import threading
11
- import webbrowser
12
-
13
- from ._utils import find_available_port, kill_process_on_port, check_port_available
14
- from ._renderer import render_preview_with_bboxes
15
- from .templates import build_html_template
16
-
17
-
18
- class WebEditor:
19
- """
20
- Browser-based figure editor using Flask.
21
-
22
- Features:
23
- - Modern responsive UI
24
- - Real-time preview via WebSocket or polling
25
- - Property editors with sliders and color pickers
26
- - Save to .manual.json
27
- - SciTeX style defaults pre-filled
28
- - Auto-finds available port if default is in use
29
- """
30
-
31
- def __init__(
32
- self,
33
- json_path: Path,
34
- metadata: Dict[str, Any],
35
- csv_data: Optional[Any] = None,
36
- png_path: Optional[Path] = None,
37
- manual_overrides: Optional[Dict[str, Any]] = None,
38
- port: int = 5050,
39
- ):
40
- self.json_path = Path(json_path)
41
- self.metadata = metadata
42
- self.csv_data = csv_data
43
- self.png_path = Path(png_path) if png_path else None
44
- self.manual_overrides = manual_overrides or {}
45
- self._requested_port = port
46
- self.port = port
47
-
48
- # Get SciTeX defaults and merge with metadata
49
- from .._defaults import get_scitex_defaults, extract_defaults_from_metadata
50
-
51
- self.scitex_defaults = get_scitex_defaults()
52
- self.metadata_defaults = extract_defaults_from_metadata(metadata)
53
-
54
- # Start with defaults, then overlay manual overrides
55
- self.current_overrides = copy.deepcopy(self.scitex_defaults)
56
- self.current_overrides.update(self.metadata_defaults)
57
- self.current_overrides.update(self.manual_overrides)
58
-
59
- # Track initial state to detect modifications
60
- self._initial_overrides = copy.deepcopy(self.current_overrides)
61
- self._user_modified = False
62
-
63
- def run(self):
64
- """Launch the web editor."""
65
- try:
66
- from flask import Flask, render_template_string, request, jsonify
67
- except ImportError:
68
- raise ImportError(
69
- "Flask is required for web editor. Install: pip install flask"
70
- )
71
-
72
- # Handle port conflicts
73
- if not check_port_available(self._requested_port):
74
- print(f"Port {self._requested_port} is in use. Attempting to free it...")
75
- if kill_process_on_port(self._requested_port):
76
- import time
77
-
78
- time.sleep(0.5)
79
- self.port = self._requested_port
80
- print(f"Successfully freed port {self.port}")
81
- else:
82
- self.port = find_available_port(self._requested_port + 1)
83
- print(f"Using alternative port: {self.port}")
84
- else:
85
- self.port = self._requested_port
86
-
87
- app = Flask(__name__)
88
- editor = self
89
-
90
- @app.route("/")
91
- def index():
92
- # Rebuild template each time for hot reload support
93
- html_template = build_html_template()
94
- return render_template_string(
95
- html_template,
96
- filename=str(editor.json_path.resolve()),
97
- overrides=json.dumps(editor.current_overrides),
98
- )
99
-
100
- @app.route("/preview")
101
- def preview():
102
- """Generate figure preview as base64 PNG with element bboxes."""
103
- img_data, bboxes, img_size = render_preview_with_bboxes(
104
- editor.csv_data, editor.current_overrides,
105
- metadata=editor.metadata
106
- )
107
- return jsonify({"image": img_data, "bboxes": bboxes, "img_size": img_size})
108
-
109
- @app.route("/update", methods=["POST"])
110
- def update():
111
- """Update overrides and return new preview."""
112
- data = request.json
113
- editor.current_overrides.update(data.get("overrides", {}))
114
- editor._user_modified = True
115
- img_data, bboxes, img_size = render_preview_with_bboxes(
116
- editor.csv_data, editor.current_overrides,
117
- metadata=editor.metadata
118
- )
119
- return jsonify(
120
- {
121
- "image": img_data,
122
- "bboxes": bboxes,
123
- "img_size": img_size,
124
- "status": "updated",
125
- }
126
- )
127
-
128
- @app.route("/save", methods=["POST"])
129
- def save():
130
- """Save to .manual.json."""
131
- from .._edit import save_manual_overrides
132
-
133
- try:
134
- manual_path = save_manual_overrides(
135
- editor.json_path, editor.current_overrides
136
- )
137
- return jsonify({"status": "saved", "path": str(manual_path)})
138
- except Exception as e:
139
- return jsonify({"status": "error", "message": str(e)}), 500
140
-
141
- @app.route("/shutdown", methods=["POST"])
142
- def shutdown():
143
- """Shutdown the server."""
144
- func = request.environ.get("werkzeug.server.shutdown")
145
- if func is None:
146
- raise RuntimeError("Not running with Werkzeug Server")
147
- func()
148
- return jsonify({"status": "shutdown"})
149
-
150
- # Open browser after short delay
151
- def open_browser():
152
- import time
153
-
154
- time.sleep(0.5)
155
- webbrowser.open(f"http://127.0.0.1:{self.port}")
156
-
157
- threading.Thread(target=open_browser, daemon=True).start()
158
-
159
- print(f"Starting SciTeX Editor at http://127.0.0.1:{self.port}")
160
- print("Press Ctrl+C to stop")
161
-
162
- # Note: use_reloader=False because the reloader re-runs the entire script
163
- # which causes infinite loops when the demo generates figures
164
- # Templates are rebuilt on each page refresh anyway
165
- app.run(host="127.0.0.1", port=self.port, debug=False, use_reloader=False)
166
-
167
-
168
- # EOF