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
@@ -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