scitex 2.7.0__py3-none-any.whl → 2.8.1__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 (355) 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/diagram/README.md +197 -0
  79. scitex/diagram/__init__.py +48 -0
  80. scitex/diagram/_compile.py +312 -0
  81. scitex/diagram/_diagram.py +355 -0
  82. scitex/diagram/_presets.py +173 -0
  83. scitex/diagram/_schema.py +182 -0
  84. scitex/diagram/_split.py +278 -0
  85. scitex/fig/__init__.py +352 -0
  86. scitex/{vis → fig}/backend/_parser.py +1 -1
  87. scitex/{vis → fig}/canvas.py +1 -1
  88. scitex/{vis → fig}/editor/__init__.py +5 -2
  89. scitex/{vis → fig}/editor/_dearpygui_editor.py +1 -1
  90. scitex/{vis → fig}/editor/_defaults.py +70 -5
  91. scitex/{vis → fig}/editor/_mpl_editor.py +1 -1
  92. scitex/{vis → fig}/editor/_qt_editor.py +182 -2
  93. scitex/{vis → fig}/editor/_tkinter_editor.py +1 -1
  94. scitex/fig/editor/edit/__init__.py +50 -0
  95. scitex/fig/editor/edit/backend_detector.py +109 -0
  96. scitex/fig/editor/edit/bundle_resolver.py +240 -0
  97. scitex/fig/editor/edit/editor_launcher.py +239 -0
  98. scitex/fig/editor/edit/manual_handler.py +53 -0
  99. scitex/fig/editor/edit/panel_loader.py +232 -0
  100. scitex/fig/editor/edit/path_resolver.py +67 -0
  101. scitex/fig/editor/flask_editor/_bbox.py +1299 -0
  102. scitex/fig/editor/flask_editor/_core.py +1429 -0
  103. scitex/{vis → fig}/editor/flask_editor/_plotter.py +38 -4
  104. scitex/fig/editor/flask_editor/_renderer.py +813 -0
  105. scitex/fig/editor/flask_editor/static/css/base/reset.css +41 -0
  106. scitex/fig/editor/flask_editor/static/css/base/typography.css +16 -0
  107. scitex/fig/editor/flask_editor/static/css/base/variables.css +85 -0
  108. scitex/fig/editor/flask_editor/static/css/components/buttons.css +217 -0
  109. scitex/fig/editor/flask_editor/static/css/components/context-menu.css +93 -0
  110. scitex/fig/editor/flask_editor/static/css/components/dropdown.css +57 -0
  111. scitex/fig/editor/flask_editor/static/css/components/forms.css +112 -0
  112. scitex/fig/editor/flask_editor/static/css/components/modal.css +59 -0
  113. scitex/fig/editor/flask_editor/static/css/components/sections.css +212 -0
  114. scitex/fig/editor/flask_editor/static/css/features/canvas.css +176 -0
  115. scitex/fig/editor/flask_editor/static/css/features/element-inspector.css +190 -0
  116. scitex/fig/editor/flask_editor/static/css/features/loading.css +59 -0
  117. scitex/fig/editor/flask_editor/static/css/features/overlay.css +45 -0
  118. scitex/fig/editor/flask_editor/static/css/features/panel-grid.css +95 -0
  119. scitex/fig/editor/flask_editor/static/css/features/selection.css +101 -0
  120. scitex/fig/editor/flask_editor/static/css/features/statistics.css +138 -0
  121. scitex/fig/editor/flask_editor/static/css/index.css +31 -0
  122. scitex/fig/editor/flask_editor/static/css/layout/container.css +7 -0
  123. scitex/fig/editor/flask_editor/static/css/layout/controls.css +56 -0
  124. scitex/fig/editor/flask_editor/static/css/layout/preview.css +78 -0
  125. scitex/fig/editor/flask_editor/static/js/alignment/axis.js +314 -0
  126. scitex/fig/editor/flask_editor/static/js/alignment/basic.js +107 -0
  127. scitex/fig/editor/flask_editor/static/js/alignment/distribute.js +54 -0
  128. scitex/fig/editor/flask_editor/static/js/canvas/canvas.js +172 -0
  129. scitex/fig/editor/flask_editor/static/js/canvas/dragging.js +258 -0
  130. scitex/fig/editor/flask_editor/static/js/canvas/resize.js +48 -0
  131. scitex/fig/editor/flask_editor/static/js/canvas/selection.js +71 -0
  132. scitex/fig/editor/flask_editor/static/js/core/api.js +288 -0
  133. scitex/fig/editor/flask_editor/static/js/core/state.js +143 -0
  134. scitex/fig/editor/flask_editor/static/js/core/utils.js +245 -0
  135. scitex/fig/editor/flask_editor/static/js/dev/element-inspector.js +992 -0
  136. scitex/fig/editor/flask_editor/static/js/editor/bbox.js +339 -0
  137. scitex/fig/editor/flask_editor/static/js/editor/element-drag.js +286 -0
  138. scitex/fig/editor/flask_editor/static/js/editor/overlay.js +371 -0
  139. scitex/fig/editor/flask_editor/static/js/editor/preview.js +293 -0
  140. scitex/fig/editor/flask_editor/static/js/main.js +426 -0
  141. scitex/fig/editor/flask_editor/static/js/shortcuts/context-menu.js +152 -0
  142. scitex/fig/editor/flask_editor/static/js/shortcuts/keyboard.js +265 -0
  143. scitex/fig/editor/flask_editor/static/js/ui/controls.js +184 -0
  144. scitex/fig/editor/flask_editor/static/js/ui/download.js +57 -0
  145. scitex/fig/editor/flask_editor/static/js/ui/help.js +100 -0
  146. scitex/fig/editor/flask_editor/static/js/ui/theme.js +34 -0
  147. scitex/fig/editor/flask_editor/templates/__init__.py +123 -0
  148. scitex/fig/editor/flask_editor/templates/_html.py +852 -0
  149. scitex/fig/editor/flask_editor/templates/_scripts.py +4933 -0
  150. scitex/fig/editor/flask_editor/templates/_styles.py +1658 -0
  151. scitex/{vis → fig}/io/__init__.py +13 -1
  152. scitex/fig/io/_bundle.py +1058 -0
  153. scitex/{vis → fig}/io/_canvas.py +1 -1
  154. scitex/{vis → fig}/io/_data.py +1 -1
  155. scitex/{vis → fig}/io/_export.py +1 -1
  156. scitex/{vis → fig}/io/_load.py +1 -1
  157. scitex/{vis → fig}/io/_panel.py +1 -1
  158. scitex/{vis → fig}/io/_save.py +1 -1
  159. scitex/{vis → fig}/model/__init__.py +1 -1
  160. scitex/{vis → fig}/model/_annotations.py +1 -1
  161. scitex/{vis → fig}/model/_axes.py +1 -1
  162. scitex/{vis → fig}/model/_figure.py +1 -1
  163. scitex/{vis → fig}/model/_guides.py +1 -1
  164. scitex/{vis → fig}/model/_plot.py +1 -1
  165. scitex/{vis → fig}/model/_styles.py +1 -1
  166. scitex/{vis → fig}/utils/__init__.py +1 -1
  167. scitex/io/__init__.py +22 -26
  168. scitex/io/_bundle.py +493 -0
  169. scitex/io/_flush.py +5 -2
  170. scitex/io/_load.py +98 -0
  171. scitex/io/_load_modules/_H5Explorer.py +5 -2
  172. scitex/io/_load_modules/_canvas.py +2 -2
  173. scitex/io/_load_modules/_image.py +3 -4
  174. scitex/io/_load_modules/_txt.py +4 -2
  175. scitex/io/_metadata.py +34 -324
  176. scitex/io/_metadata_modules/__init__.py +46 -0
  177. scitex/io/_metadata_modules/_embed.py +70 -0
  178. scitex/io/_metadata_modules/_read.py +64 -0
  179. scitex/io/_metadata_modules/_utils.py +79 -0
  180. scitex/io/_metadata_modules/embed_metadata_jpeg.py +74 -0
  181. scitex/io/_metadata_modules/embed_metadata_pdf.py +53 -0
  182. scitex/io/_metadata_modules/embed_metadata_png.py +26 -0
  183. scitex/io/_metadata_modules/embed_metadata_svg.py +62 -0
  184. scitex/io/_metadata_modules/read_metadata_jpeg.py +57 -0
  185. scitex/io/_metadata_modules/read_metadata_pdf.py +51 -0
  186. scitex/io/_metadata_modules/read_metadata_png.py +39 -0
  187. scitex/io/_metadata_modules/read_metadata_svg.py +44 -0
  188. scitex/io/_qr_utils.py +5 -3
  189. scitex/io/_save.py +548 -30
  190. scitex/io/_save_modules/_canvas.py +3 -3
  191. scitex/io/_save_modules/_image.py +5 -9
  192. scitex/io/_save_modules/_tex.py +7 -4
  193. scitex/io/_zip_bundle.py +439 -0
  194. scitex/io/utils/h5_to_zarr.py +11 -9
  195. scitex/msword/__init__.py +255 -0
  196. scitex/msword/profiles.py +357 -0
  197. scitex/msword/reader.py +753 -0
  198. scitex/msword/utils.py +289 -0
  199. scitex/msword/writer.py +362 -0
  200. scitex/plt/__init__.py +5 -2
  201. scitex/plt/_subplots/_AxesWrapper.py +6 -6
  202. scitex/plt/_subplots/_AxisWrapper.py +15 -9
  203. scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/__init__.py +36 -0
  204. scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/_labels.py +264 -0
  205. scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/_metadata.py +213 -0
  206. scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/_visual.py +128 -0
  207. scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/__init__.py +59 -0
  208. scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_base.py +34 -0
  209. scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_scientific.py +593 -0
  210. scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_statistical.py +654 -0
  211. scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_stx_aliases.py +527 -0
  212. scitex/plt/_subplots/_AxisWrapperMixins/_RawMatplotlibMixin.py +321 -0
  213. scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/__init__.py +33 -0
  214. scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_base.py +152 -0
  215. scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_wrappers.py +600 -0
  216. scitex/plt/_subplots/_AxisWrapperMixins/__init__.py +79 -5
  217. scitex/plt/_subplots/_FigWrapper.py +6 -6
  218. scitex/plt/_subplots/_SubplotsWrapper.py +28 -18
  219. scitex/plt/_subplots/_export_as_csv.py +35 -5
  220. scitex/plt/_subplots/_export_as_csv_formatters/__init__.py +8 -0
  221. scitex/plt/_subplots/_export_as_csv_formatters/_format_annotate.py +10 -21
  222. scitex/plt/_subplots/_export_as_csv_formatters/_format_eventplot.py +18 -7
  223. scitex/plt/_subplots/_export_as_csv_formatters/_format_imshow2d.py +28 -12
  224. scitex/plt/_subplots/_export_as_csv_formatters/_format_matshow.py +10 -4
  225. scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_imshow.py +13 -1
  226. scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_kde.py +12 -2
  227. scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_scatter.py +10 -3
  228. scitex/plt/_subplots/_export_as_csv_formatters/_format_quiver.py +10 -4
  229. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_jointplot.py +18 -3
  230. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_lineplot.py +44 -36
  231. scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_pairplot.py +14 -2
  232. scitex/plt/_subplots/_export_as_csv_formatters/_format_streamplot.py +11 -5
  233. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_bar.py +84 -0
  234. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_barh.py +85 -0
  235. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_conf_mat.py +14 -3
  236. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_contour.py +54 -0
  237. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_ecdf.py +14 -2
  238. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_errorbar.py +120 -0
  239. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_heatmap.py +16 -6
  240. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_image.py +29 -19
  241. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_imshow.py +63 -0
  242. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_joyplot.py +22 -5
  243. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_mean_ci.py +18 -14
  244. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_mean_std.py +18 -14
  245. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_median_iqr.py +18 -14
  246. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_raster.py +10 -2
  247. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_scatter.py +51 -0
  248. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_scatter_hist.py +18 -9
  249. scitex/plt/ax/_plot/_stx_ecdf.py +4 -2
  250. scitex/plt/gallery/_generate.py +421 -14
  251. scitex/plt/io/__init__.py +53 -0
  252. scitex/plt/io/_bundle.py +490 -0
  253. scitex/plt/io/_layered_bundle.py +1343 -0
  254. scitex/plt/styles/SCITEX_STYLE.yaml +26 -0
  255. scitex/plt/styles/__init__.py +14 -0
  256. scitex/plt/styles/presets.py +78 -0
  257. scitex/plt/utils/__init__.py +13 -1
  258. scitex/plt/utils/_collect_figure_metadata.py +10 -14
  259. scitex/plt/utils/_configure_mpl.py +6 -18
  260. scitex/plt/utils/_crop.py +32 -14
  261. scitex/plt/utils/_csv_column_naming.py +54 -0
  262. scitex/plt/utils/_figure_mm.py +116 -1
  263. scitex/plt/utils/_hitmap.py +1643 -0
  264. scitex/plt/utils/metadata/__init__.py +25 -0
  265. scitex/plt/utils/metadata/_core.py +9 -10
  266. scitex/plt/utils/metadata/_dimensions.py +6 -3
  267. scitex/plt/utils/metadata/_editable_export.py +405 -0
  268. scitex/plt/utils/metadata/_geometry_extraction.py +570 -0
  269. scitex/schema/__init__.py +109 -16
  270. scitex/schema/_canvas.py +1 -1
  271. scitex/schema/_plot.py +1015 -0
  272. scitex/schema/_stats.py +2 -2
  273. scitex/stats/__init__.py +117 -0
  274. scitex/stats/io/__init__.py +29 -0
  275. scitex/stats/io/_bundle.py +156 -0
  276. scitex/tex/__init__.py +4 -0
  277. scitex/tex/_export.py +890 -0
  278. {scitex-2.7.0.dist-info → scitex-2.8.1.dist-info}/METADATA +11 -1
  279. {scitex-2.7.0.dist-info → scitex-2.8.1.dist-info}/RECORD +294 -170
  280. scitex/io/memo.md +0 -2827
  281. scitex/plt/REQUESTS.md +0 -191
  282. scitex/plt/_subplots/TODO.md +0 -53
  283. scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin.py +0 -559
  284. scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin.py +0 -1609
  285. scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin.py +0 -447
  286. scitex/plt/templates/research-master/scitex/vis/gallery/area/fill_between.json +0 -110
  287. scitex/plt/templates/research-master/scitex/vis/gallery/area/fill_betweenx.json +0 -88
  288. scitex/plt/templates/research-master/scitex/vis/gallery/area/stx_fill_between.json +0 -103
  289. scitex/plt/templates/research-master/scitex/vis/gallery/area/stx_fillv.json +0 -106
  290. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/bar.json +0 -92
  291. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/barh.json +0 -92
  292. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/boxplot.json +0 -92
  293. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_bar.json +0 -84
  294. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_barh.json +0 -84
  295. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_box.json +0 -83
  296. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_boxplot.json +0 -93
  297. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_violin.json +0 -91
  298. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_violinplot.json +0 -91
  299. scitex/plt/templates/research-master/scitex/vis/gallery/categorical/violinplot.json +0 -91
  300. scitex/plt/templates/research-master/scitex/vis/gallery/contour/contour.json +0 -97
  301. scitex/plt/templates/research-master/scitex/vis/gallery/contour/contourf.json +0 -98
  302. scitex/plt/templates/research-master/scitex/vis/gallery/contour/stx_contour.json +0 -84
  303. scitex/plt/templates/research-master/scitex/vis/gallery/distribution/hist.json +0 -101
  304. scitex/plt/templates/research-master/scitex/vis/gallery/distribution/hist2d.json +0 -96
  305. scitex/plt/templates/research-master/scitex/vis/gallery/distribution/stx_ecdf.json +0 -95
  306. scitex/plt/templates/research-master/scitex/vis/gallery/distribution/stx_joyplot.json +0 -95
  307. scitex/plt/templates/research-master/scitex/vis/gallery/distribution/stx_kde.json +0 -93
  308. scitex/plt/templates/research-master/scitex/vis/gallery/grid/imshow.json +0 -95
  309. scitex/plt/templates/research-master/scitex/vis/gallery/grid/matshow.json +0 -95
  310. scitex/plt/templates/research-master/scitex/vis/gallery/grid/stx_conf_mat.json +0 -83
  311. scitex/plt/templates/research-master/scitex/vis/gallery/grid/stx_heatmap.json +0 -92
  312. scitex/plt/templates/research-master/scitex/vis/gallery/grid/stx_image.json +0 -121
  313. scitex/plt/templates/research-master/scitex/vis/gallery/grid/stx_imshow.json +0 -84
  314. scitex/plt/templates/research-master/scitex/vis/gallery/line/plot.json +0 -110
  315. scitex/plt/templates/research-master/scitex/vis/gallery/line/step.json +0 -92
  316. scitex/plt/templates/research-master/scitex/vis/gallery/line/stx_line.json +0 -95
  317. scitex/plt/templates/research-master/scitex/vis/gallery/line/stx_shaded_line.json +0 -96
  318. scitex/plt/templates/research-master/scitex/vis/gallery/scatter/hexbin.json +0 -95
  319. scitex/plt/templates/research-master/scitex/vis/gallery/scatter/scatter.json +0 -95
  320. scitex/plt/templates/research-master/scitex/vis/gallery/scatter/stem.json +0 -92
  321. scitex/plt/templates/research-master/scitex/vis/gallery/scatter/stx_scatter.json +0 -84
  322. scitex/plt/templates/research-master/scitex/vis/gallery/special/pie.json +0 -94
  323. scitex/plt/templates/research-master/scitex/vis/gallery/special/stx_raster.json +0 -109
  324. scitex/plt/templates/research-master/scitex/vis/gallery/special/stx_rectangle.json +0 -108
  325. scitex/plt/templates/research-master/scitex/vis/gallery/statistical/errorbar.json +0 -93
  326. scitex/plt/templates/research-master/scitex/vis/gallery/statistical/stx_errorbar.json +0 -84
  327. scitex/plt/templates/research-master/scitex/vis/gallery/statistical/stx_mean_ci.json +0 -96
  328. scitex/plt/templates/research-master/scitex/vis/gallery/statistical/stx_mean_std.json +0 -96
  329. scitex/plt/templates/research-master/scitex/vis/gallery/statistical/stx_median_iqr.json +0 -96
  330. scitex/plt/templates/research-master/scitex/vis/gallery/vector/quiver.json +0 -99
  331. scitex/plt/templates/research-master/scitex/vis/gallery/vector/streamplot.json +0 -100
  332. scitex/vis/__init__.py +0 -177
  333. scitex/vis/editor/_edit.py +0 -390
  334. scitex/vis/editor/flask_editor/_bbox.py +0 -529
  335. scitex/vis/editor/flask_editor/_core.py +0 -168
  336. scitex/vis/editor/flask_editor/_renderer.py +0 -393
  337. scitex/vis/editor/flask_editor/templates/__init__.py +0 -33
  338. scitex/vis/editor/flask_editor/templates/_html.py +0 -513
  339. scitex/vis/editor/flask_editor/templates/_scripts.py +0 -1261
  340. scitex/vis/editor/flask_editor/templates/_styles.py +0 -739
  341. /scitex/{vis → fig}/README.md +0 -0
  342. /scitex/{vis → fig}/backend/__init__.py +0 -0
  343. /scitex/{vis → fig}/backend/_export.py +0 -0
  344. /scitex/{vis → fig}/backend/_render.py +0 -0
  345. /scitex/{vis → fig}/docs/CANVAS_ARCHITECTURE.md +0 -0
  346. /scitex/{vis → fig}/editor/_flask_editor.py +0 -0
  347. /scitex/{vis → fig}/editor/flask_editor/__init__.py +0 -0
  348. /scitex/{vis → fig}/editor/flask_editor/_utils.py +0 -0
  349. /scitex/{vis → fig}/io/_directory.py +0 -0
  350. /scitex/{vis → fig}/model/_plot_types.py +0 -0
  351. /scitex/{vis → fig}/utils/_defaults.py +0 -0
  352. /scitex/{vis → fig}/utils/_validate.py +0 -0
  353. {scitex-2.7.0.dist-info → scitex-2.8.1.dist-info}/WHEEL +0 -0
  354. {scitex-2.7.0.dist-info → scitex-2.8.1.dist-info}/entry_points.txt +0 -0
  355. {scitex-2.7.0.dist-info → scitex-2.8.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,490 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # Timestamp: "2025-12-13 (ywatanabe)"
4
+ # File: /home/ywatanabe/proj/scitex-code/src/scitex/plt/io/_bundle.py
5
+
6
+ """
7
+ SciTeX .pltz Bundle I/O - Plot-specific bundle operations.
8
+
9
+ Handles:
10
+ - Plot specification validation
11
+ - CSV data loading/saving
12
+ - Hitmap PNG/SVG generation and storage
13
+ - Bundle overview image generation
14
+ """
15
+
16
+ import json
17
+ import shutil
18
+ import warnings
19
+ from pathlib import Path
20
+ from typing import Any, Dict, List
21
+
22
+ import numpy as np
23
+
24
+ from scitex.plt.styles import get_preview_dpi
25
+
26
+ __all__ = [
27
+ "validate_pltz_spec",
28
+ "load_pltz_bundle",
29
+ "save_pltz_bundle",
30
+ "generate_bundle_overview",
31
+ "PLTZ_SCHEMA_SPEC",
32
+ ]
33
+
34
+ # Schema specification for .pltz bundles
35
+ PLTZ_SCHEMA_SPEC = {
36
+ "name": "scitex.plt.plot",
37
+ "version": "1.0.0",
38
+ "required_fields": ["schema"],
39
+ "optional_fields": [
40
+ "backend",
41
+ "plot_type",
42
+ "data",
43
+ "axes",
44
+ "styles",
45
+ "stats",
46
+ ],
47
+ }
48
+
49
+
50
+ def validate_pltz_spec(spec: Dict[str, Any]) -> List[str]:
51
+ """Validate .pltz-specific fields.
52
+
53
+ Args:
54
+ spec: The specification dictionary to validate.
55
+
56
+ Returns:
57
+ List of validation error messages (empty if valid).
58
+ """
59
+ errors = []
60
+
61
+ if "axes" in spec:
62
+ axes = spec["axes"]
63
+ if not isinstance(axes, (dict, list)):
64
+ errors.append("'axes' must be a dictionary or list")
65
+
66
+ return errors
67
+
68
+
69
+ def load_pltz_bundle(bundle_dir: Path) -> Dict[str, Any]:
70
+ """Load .pltz bundle contents from directory.
71
+
72
+ Args:
73
+ bundle_dir: Path to the bundle directory.
74
+
75
+ Returns:
76
+ Dictionary with loaded bundle contents.
77
+ """
78
+ result = {}
79
+
80
+ # Find the spec file (could be plot.json or {basename}.json)
81
+ spec_file = None
82
+ for f in bundle_dir.glob("*.json"):
83
+ if not f.name.startswith('.'): # Skip hidden files
84
+ spec_file = f
85
+ break
86
+
87
+ if spec_file and spec_file.exists():
88
+ with open(spec_file, "r") as f:
89
+ result["spec"] = json.load(f)
90
+ # Extract basename from spec filename
91
+ result["basename"] = spec_file.stem
92
+ else:
93
+ result["spec"] = None
94
+ result["basename"] = "plot" # Default
95
+
96
+ # Find and load CSV data (could be plot.csv or {basename}.csv)
97
+ csv_file = None
98
+ for f in bundle_dir.glob("*.csv"):
99
+ if not f.name.startswith('.'): # Skip hidden files
100
+ csv_file = f
101
+ break
102
+
103
+ if csv_file and csv_file.exists():
104
+ try:
105
+ import pandas as pd
106
+ result["data"] = pd.read_csv(csv_file)
107
+ except ImportError:
108
+ # Fallback to basic CSV reading
109
+ with open(csv_file, "r") as f:
110
+ result["data"] = f.read()
111
+
112
+ return result
113
+
114
+
115
+ def save_pltz_bundle(data: Dict[str, Any], dir_path: Path) -> None:
116
+ """Save .pltz bundle contents to directory.
117
+
118
+ Args:
119
+ data: Bundle data dictionary containing:
120
+ - spec: JSON specification
121
+ - data: CSV data (DataFrame or string)
122
+ - basename: Base filename for all exports (e.g., "myplot")
123
+ - png, svg, pdf: Export file data
124
+ - hitmap_png, hitmap_svg: Hitmap file data
125
+ dir_path: Path to the bundle directory.
126
+ """
127
+ # Get basename from data, fallback to "plot" for backward compatibility
128
+ basename = data.get("basename", "plot")
129
+
130
+ # Save specification
131
+ spec = data.get("spec", {})
132
+ spec_file = dir_path / f"{basename}.json"
133
+ with open(spec_file, "w") as f:
134
+ json.dump(spec, f, indent=2)
135
+
136
+ # Save CSV data
137
+ if "data" in data:
138
+ csv_file = dir_path / f"{basename}.csv"
139
+ df = data["data"]
140
+ if hasattr(df, "to_csv"):
141
+ df.to_csv(csv_file, index=False)
142
+ else:
143
+ with open(csv_file, "w") as f:
144
+ f.write(str(df))
145
+
146
+ # Save exports (PNG, SVG, PDF)
147
+ _save_exports(data, dir_path, spec, basename)
148
+
149
+ # Save hitmaps
150
+ _save_hitmaps(data, dir_path, basename)
151
+
152
+ # Generate overview
153
+ try:
154
+ generate_bundle_overview(dir_path, spec, data, basename)
155
+ except Exception as e:
156
+ import logging
157
+ logging.getLogger("scitex").debug(f"Could not generate overview: {e}")
158
+
159
+
160
+ def _save_exports(data: Dict[str, Any], dir_path: Path, spec: Dict, basename: str = "plot") -> None:
161
+ """Save export files (PNG, SVG, PDF) with embedded metadata."""
162
+ from scitex.io._metadata import embed_metadata
163
+
164
+ for fmt in ["png", "svg", "pdf"]:
165
+ if fmt not in data:
166
+ continue
167
+
168
+ out_file = dir_path / f"{basename}.{fmt}"
169
+ export_data = data[fmt]
170
+
171
+ if isinstance(export_data, bytes):
172
+ with open(out_file, "wb") as f:
173
+ f.write(export_data)
174
+ elif isinstance(export_data, (str, Path)) and Path(export_data).exists():
175
+ shutil.copy(export_data, out_file)
176
+
177
+ # Embed metadata into PNG and PDF files
178
+ if out_file.exists() and spec:
179
+ try:
180
+ _embed_metadata_in_export(out_file, spec, fmt)
181
+ except Exception as e:
182
+ import logging
183
+ logging.getLogger("scitex").debug(
184
+ f"Could not embed metadata in {out_file}: {e}"
185
+ )
186
+
187
+
188
+ def _embed_metadata_in_export(
189
+ file_path: Path, spec: Dict[str, Any], fmt: str
190
+ ) -> None:
191
+ """Embed bundle spec metadata into exported image files."""
192
+ from scitex.io._metadata import embed_metadata
193
+
194
+ embed_data = {
195
+ "scitex_bundle": True,
196
+ "schema": spec.get("schema", {}),
197
+ }
198
+
199
+ for key in ["plot_type", "backend", "size", "axes", "figure", "panels"]:
200
+ if key in spec:
201
+ embed_data[key] = spec[key]
202
+
203
+ if fmt in ("png", "svg", "pdf"):
204
+ embed_metadata(str(file_path), embed_data)
205
+
206
+
207
+ def _save_hitmaps(data: Dict[str, Any], dir_path: Path, basename: str = "plot") -> None:
208
+ """Save hitmap PNG and SVG files."""
209
+ # Save hitmap PNG
210
+ if "hitmap_png" in data:
211
+ hitmap_file = dir_path / f"{basename}_hitmap.png"
212
+ hitmap_data = data["hitmap_png"]
213
+ if isinstance(hitmap_data, bytes):
214
+ with open(hitmap_file, "wb") as f:
215
+ f.write(hitmap_data)
216
+ elif isinstance(hitmap_data, (str, Path)) and Path(hitmap_data).exists():
217
+ shutil.copy(hitmap_data, hitmap_file)
218
+
219
+ # Save hitmap SVG
220
+ if "hitmap_svg" in data:
221
+ hitmap_svg_file = dir_path / f"{basename}_hitmap.svg"
222
+ hitmap_svg_data = data["hitmap_svg"]
223
+ if isinstance(hitmap_svg_data, bytes):
224
+ with open(hitmap_svg_file, "wb") as f:
225
+ f.write(hitmap_svg_data)
226
+ elif isinstance(hitmap_svg_data, (str, Path)) and Path(hitmap_svg_data).exists():
227
+ shutil.copy(hitmap_svg_data, hitmap_svg_file)
228
+
229
+
230
+ def generate_bundle_overview(dir_path: Path, spec: Dict, data: Dict, basename: str = "plot") -> None:
231
+ """Generate overview image showing bundle contents visually.
232
+
233
+ Creates a comprehensive overview image with:
234
+ - CSV statistics (columns, rows, dtypes)
235
+ - JSON structure as tree
236
+ - Figures grid (PNG, hitmap, diff overlay)
237
+
238
+ Parameters
239
+ ----------
240
+ dir_path : Path
241
+ Bundle directory path.
242
+ spec : dict
243
+ Bundle specification.
244
+ data : dict
245
+ Bundle data dictionary.
246
+ basename : str
247
+ Base filename for bundle files (e.g., "myplot").
248
+ """
249
+ import matplotlib.pyplot as plt
250
+ import matplotlib.gridspec as gridspec
251
+ from PIL import Image
252
+
253
+ # Create figure with custom layout
254
+ fig = plt.figure(figsize=(16, 10), facecolor="white")
255
+ gs = gridspec.GridSpec(
256
+ 2, 4, figure=fig, hspace=0.3, wspace=0.3,
257
+ left=0.05, right=0.95, top=0.92, bottom=0.05,
258
+ )
259
+
260
+ # Title
261
+ bundle_name = dir_path.name
262
+ fig.suptitle(f"Bundle Overview: {bundle_name}", fontsize=16, fontweight="bold")
263
+
264
+ # === Panel 1: CSV Statistics ===
265
+ ax_csv = fig.add_subplot(gs[0, 0])
266
+ ax_csv.set_title("CSV Data", fontweight="bold", fontsize=11)
267
+ ax_csv.axis("off")
268
+
269
+ csv_text = []
270
+ if "data" in data and hasattr(data["data"], "columns"):
271
+ df = data["data"]
272
+ csv_text.append(f"Rows: {len(df)}")
273
+ csv_text.append(f"Columns: {len(df.columns)}")
274
+ csv_text.append("")
275
+ csv_text.append("Columns:")
276
+ for col in df.columns[:10]:
277
+ dtype = str(df[col].dtype)
278
+ csv_text.append(f" • {col} ({dtype})")
279
+ if len(df.columns) > 10:
280
+ csv_text.append(f" ... +{len(df.columns) - 10} more")
281
+ else:
282
+ csv_text.append("No CSV data")
283
+
284
+ ax_csv.text(
285
+ 0.05, 0.95, "\n".join(csv_text),
286
+ transform=ax_csv.transAxes, fontsize=9, verticalalignment="top",
287
+ fontfamily="monospace",
288
+ bbox=dict(boxstyle="round", facecolor="#f0f0f0", alpha=0.8),
289
+ )
290
+
291
+ # === Panel 2: JSON Tree ===
292
+ ax_json = fig.add_subplot(gs[0, 1])
293
+ ax_json.set_title("JSON Structure", fontweight="bold", fontsize=11)
294
+ ax_json.axis("off")
295
+
296
+ json_lines = _json_to_tree(spec, max_depth=4, max_keys=8, max_lines=25)
297
+ ax_json.text(
298
+ 0.02, 0.98, "\n".join(json_lines),
299
+ transform=ax_json.transAxes, fontsize=7, verticalalignment="top",
300
+ fontfamily="monospace",
301
+ bbox=dict(boxstyle="round", facecolor="#f0f0f0", alpha=0.8),
302
+ )
303
+
304
+ # === Panel 3: PNG Image ===
305
+ ax_png = fig.add_subplot(gs[0, 2])
306
+ ax_png.set_title(f"{basename}.png", fontweight="bold", fontsize=11)
307
+ png_path = dir_path / f"{basename}.png"
308
+ if png_path.exists():
309
+ png_img = Image.open(png_path)
310
+ ax_png.imshow(png_img)
311
+ ax_png.set_xlabel(f"{png_img.size[0]}×{png_img.size[1]}", fontsize=9)
312
+ ax_png.axis("off")
313
+
314
+ # === Panel 4: Hitmap PNG ===
315
+ ax_hitmap = fig.add_subplot(gs[0, 3])
316
+ ax_hitmap.set_title(f"{basename}_hitmap.png", fontweight="bold", fontsize=11)
317
+ hitmap_path = dir_path / f"{basename}_hitmap.png"
318
+ if hitmap_path.exists():
319
+ hitmap_img = Image.open(hitmap_path)
320
+ ax_hitmap.imshow(hitmap_img)
321
+ ax_hitmap.set_xlabel(f"{hitmap_img.size[0]}×{hitmap_img.size[1]}", fontsize=9)
322
+ ax_hitmap.axis("off")
323
+
324
+ # === Panel 5: SVG info ===
325
+ ax_svg = fig.add_subplot(gs[1, 0])
326
+ ax_svg.set_title(f"{basename}.svg", fontweight="bold", fontsize=11)
327
+ svg_path = dir_path / f"{basename}.svg"
328
+ if svg_path.exists():
329
+ svg_size = svg_path.stat().st_size
330
+ ax_svg.text(
331
+ 0.5, 0.5, f"SVG File\n{svg_size/1024:.1f} KB",
332
+ transform=ax_svg.transAxes, ha="center", va="center",
333
+ fontsize=12, bbox=dict(boxstyle="round", facecolor="#e0e0ff"),
334
+ )
335
+ ax_svg.axis("off")
336
+
337
+ # === Panel 6: Hitmap SVG ===
338
+ ax_hitmap_svg = fig.add_subplot(gs[1, 1])
339
+ ax_hitmap_svg.set_title(f"{basename}_hitmap.svg", fontweight="bold", fontsize=11)
340
+ hitmap_svg_path = dir_path / f"{basename}_hitmap.svg"
341
+ if hitmap_svg_path.exists():
342
+ svg_size = hitmap_svg_path.stat().st_size
343
+ ax_hitmap_svg.text(
344
+ 0.5, 0.5, f"SVG Hitmap\n{svg_size/1024:.1f} KB",
345
+ transform=ax_hitmap_svg.transAxes, ha="center", va="center",
346
+ fontsize=12, bbox=dict(boxstyle="round", facecolor="#ffe0e0"),
347
+ )
348
+ ax_hitmap_svg.axis("off")
349
+
350
+ # === Panel 7: PNG vs Hitmap Diff ===
351
+ ax_diff = fig.add_subplot(gs[1, 2])
352
+ ax_diff.set_title("PNG vs Hitmap (Overlay)", fontweight="bold", fontsize=11)
353
+ if png_path.exists() and hitmap_path.exists():
354
+ png_arr = np.array(Image.open(png_path).convert("RGB"))
355
+ hitmap_arr = np.array(Image.open(hitmap_path).convert("RGB"))
356
+
357
+ if png_arr.shape == hitmap_arr.shape:
358
+ overlay = np.zeros_like(png_arr)
359
+ overlay[:, :, 0] = hitmap_arr[:, :, 0]
360
+ overlay[:, :, 2] = np.mean(png_arr, axis=2).astype(np.uint8)
361
+ overlay[:, :, 1] = 128
362
+ ax_diff.imshow(overlay)
363
+ ax_diff.set_xlabel("Red=Hitmap, Blue=PNG", fontsize=9)
364
+ else:
365
+ ax_diff.text(
366
+ 0.5, 0.5, "Size mismatch!",
367
+ transform=ax_diff.transAxes, ha="center", va="center",
368
+ fontsize=14, color="red",
369
+ )
370
+ ax_diff.axis("off")
371
+
372
+ # === Panel 8: Alignment Validation ===
373
+ ax_valid = fig.add_subplot(gs[1, 3])
374
+ ax_valid.set_title("Alignment Check", fontweight="bold", fontsize=11)
375
+ ax_valid.axis("off")
376
+
377
+ validation_text = _generate_alignment_validation(png_path, hitmap_path)
378
+ color = "green" if all(
379
+ "✓" in t for t in validation_text if t.startswith("✓") or t.startswith("✗")
380
+ ) else "red"
381
+
382
+ ax_valid.text(
383
+ 0.05, 0.95, "\n".join(validation_text),
384
+ transform=ax_valid.transAxes, fontsize=9, verticalalignment="top",
385
+ fontfamily="monospace",
386
+ bbox=dict(
387
+ boxstyle="round",
388
+ facecolor="#f0fff0" if color == "green" else "#fff0f0",
389
+ alpha=0.8,
390
+ ),
391
+ )
392
+
393
+ # Save overview
394
+ overview_path = dir_path / f"{basename}_overview.png"
395
+ with warnings.catch_warnings():
396
+ warnings.filterwarnings("ignore", message=".*tight_layout.*")
397
+ fig.savefig(overview_path, dpi=get_preview_dpi(), bbox_inches="tight", facecolor="white")
398
+ plt.close(fig)
399
+
400
+
401
+ def _json_to_tree(
402
+ obj, prefix="", max_depth=4, depth=0, max_keys=6, max_lines=30
403
+ ) -> List[str]:
404
+ """Convert JSON to tree representation with depth control."""
405
+ lines = []
406
+ if depth >= max_depth:
407
+ return []
408
+
409
+ if isinstance(obj, dict):
410
+ items = list(obj.items())[:max_keys]
411
+ for i, (k, v) in enumerate(items):
412
+ is_last = i == len(items) - 1 and len(obj) <= max_keys
413
+ branch = "└─ " if is_last else "├─ "
414
+ next_prefix = prefix + (" " if is_last else "│ ")
415
+
416
+ if isinstance(v, dict):
417
+ if depth < max_depth - 1 and v:
418
+ lines.append(prefix + branch + f"{k}:")
419
+ lines.extend(_json_to_tree(
420
+ v, next_prefix, max_depth, depth + 1, max_keys, max_lines
421
+ ))
422
+ else:
423
+ lines.append(prefix + branch + f"{k}: {{{len(v)} keys}}")
424
+ elif isinstance(v, list):
425
+ lines.append(prefix + branch + f"{k}: [{len(v)} items]")
426
+ else:
427
+ val_str = str(v)
428
+ if len(val_str) > 25:
429
+ val_str = val_str[:22] + "..."
430
+ lines.append(prefix + branch + f"{k}: {val_str}")
431
+
432
+ if len(obj) > max_keys:
433
+ lines.append(prefix + f" ... +{len(obj) - max_keys} more")
434
+
435
+ return lines[:max_lines]
436
+
437
+
438
+ def _generate_alignment_validation(png_path: Path, hitmap_path: Path) -> List[str]:
439
+ """Generate alignment validation text."""
440
+ from PIL import Image
441
+
442
+ validation_text = []
443
+
444
+ if png_path.exists() and hitmap_path.exists():
445
+ png_img = Image.open(png_path)
446
+ hitmap_img = Image.open(hitmap_path)
447
+
448
+ # Size check
449
+ size_match = png_img.size == hitmap_img.size
450
+ validation_text.append(
451
+ f"✓ Size match: {png_img.size}"
452
+ if size_match
453
+ else f"✗ Size mismatch: PNG={png_img.size}, Hitmap={hitmap_img.size}"
454
+ )
455
+
456
+ # Content bounds check
457
+ png_arr = np.array(png_img.convert("RGB"))
458
+ hitmap_arr = np.array(hitmap_img.convert("RGB"))
459
+
460
+ def find_bounds(arr):
461
+ white = np.array([255, 255, 255])
462
+ mask = np.any(np.abs(arr.astype(int) - white) > 20, axis=2)
463
+ rows = np.any(mask, axis=1)
464
+ cols = np.any(mask, axis=0)
465
+ if np.any(rows) and np.any(cols):
466
+ y_min, y_max = np.where(rows)[0][[0, -1]]
467
+ x_min, x_max = np.where(cols)[0][[0, -1]]
468
+ return (int(x_min), int(y_min), int(x_max), int(y_max))
469
+ return (0, 0, arr.shape[1], arr.shape[0])
470
+
471
+ png_bounds = find_bounds(png_arr)
472
+ hitmap_bounds = find_bounds(hitmap_arr)
473
+
474
+ bounds_match = png_bounds == hitmap_bounds
475
+ validation_text.append(
476
+ f"✓ Content aligned"
477
+ if bounds_match
478
+ else f"✗ Content offset: {hitmap_bounds[0]-png_bounds[0]}, {hitmap_bounds[1]-png_bounds[1]}"
479
+ )
480
+
481
+ validation_text.append("")
482
+ validation_text.append(f"PNG bounds: {png_bounds}")
483
+ validation_text.append(f"Hitmap bounds: {hitmap_bounds}")
484
+ else:
485
+ validation_text.append("Files not found")
486
+
487
+ return validation_text
488
+
489
+
490
+ # EOF