scitex 2.14.0__py3-none-any.whl → 2.15.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (300) hide show
  1. scitex/__init__.py +71 -17
  2. scitex/_env_loader.py +156 -0
  3. scitex/_mcp_resources/__init__.py +37 -0
  4. scitex/_mcp_resources/_cheatsheet.py +135 -0
  5. scitex/_mcp_resources/_figrecipe.py +138 -0
  6. scitex/_mcp_resources/_formats.py +102 -0
  7. scitex/_mcp_resources/_modules.py +337 -0
  8. scitex/_mcp_resources/_session.py +149 -0
  9. scitex/_mcp_tools/__init__.py +4 -0
  10. scitex/_mcp_tools/audio.py +66 -0
  11. scitex/_mcp_tools/diagram.py +11 -95
  12. scitex/_mcp_tools/introspect.py +210 -0
  13. scitex/_mcp_tools/plt.py +260 -305
  14. scitex/_mcp_tools/scholar.py +74 -0
  15. scitex/_mcp_tools/social.py +244 -0
  16. scitex/_mcp_tools/template.py +24 -0
  17. scitex/_mcp_tools/writer.py +21 -204
  18. scitex/ai/_gen_ai/_PARAMS.py +10 -7
  19. scitex/ai/classification/reporters/_SingleClassificationReporter.py +45 -1603
  20. scitex/ai/classification/reporters/_mixins/__init__.py +36 -0
  21. scitex/ai/classification/reporters/_mixins/_constants.py +67 -0
  22. scitex/ai/classification/reporters/_mixins/_cv_summary.py +387 -0
  23. scitex/ai/classification/reporters/_mixins/_feature_importance.py +119 -0
  24. scitex/ai/classification/reporters/_mixins/_metrics.py +275 -0
  25. scitex/ai/classification/reporters/_mixins/_plotting.py +179 -0
  26. scitex/ai/classification/reporters/_mixins/_reports.py +153 -0
  27. scitex/ai/classification/reporters/_mixins/_storage.py +160 -0
  28. scitex/ai/classification/timeseries/_TimeSeriesSlidingWindowSplit.py +30 -1550
  29. scitex/ai/classification/timeseries/_sliding_window_core.py +467 -0
  30. scitex/ai/classification/timeseries/_sliding_window_plotting.py +369 -0
  31. scitex/audio/README.md +40 -36
  32. scitex/audio/__init__.py +129 -61
  33. scitex/audio/_branding.py +185 -0
  34. scitex/audio/_mcp/__init__.py +32 -0
  35. scitex/audio/_mcp/handlers.py +59 -6
  36. scitex/audio/_mcp/speak_handlers.py +238 -0
  37. scitex/audio/_relay.py +225 -0
  38. scitex/audio/_tts.py +18 -10
  39. scitex/audio/engines/base.py +17 -10
  40. scitex/audio/engines/elevenlabs_engine.py +7 -2
  41. scitex/audio/mcp_server.py +228 -75
  42. scitex/canvas/README.md +1 -1
  43. scitex/canvas/editor/_dearpygui/__init__.py +25 -0
  44. scitex/canvas/editor/_dearpygui/_editor.py +147 -0
  45. scitex/canvas/editor/_dearpygui/_handlers.py +476 -0
  46. scitex/canvas/editor/_dearpygui/_panels/__init__.py +17 -0
  47. scitex/canvas/editor/_dearpygui/_panels/_control.py +119 -0
  48. scitex/canvas/editor/_dearpygui/_panels/_element_controls.py +190 -0
  49. scitex/canvas/editor/_dearpygui/_panels/_preview.py +43 -0
  50. scitex/canvas/editor/_dearpygui/_panels/_sections.py +390 -0
  51. scitex/canvas/editor/_dearpygui/_plotting.py +187 -0
  52. scitex/canvas/editor/_dearpygui/_rendering.py +504 -0
  53. scitex/canvas/editor/_dearpygui/_selection.py +295 -0
  54. scitex/canvas/editor/_dearpygui/_state.py +93 -0
  55. scitex/canvas/editor/_dearpygui/_utils.py +61 -0
  56. scitex/canvas/editor/flask_editor/_core/__init__.py +27 -0
  57. scitex/canvas/editor/flask_editor/_core/_bbox_extraction.py +200 -0
  58. scitex/canvas/editor/flask_editor/_core/_editor.py +173 -0
  59. scitex/canvas/editor/flask_editor/_core/_export_helpers.py +353 -0
  60. scitex/canvas/editor/flask_editor/_core/_routes_basic.py +190 -0
  61. scitex/canvas/editor/flask_editor/_core/_routes_export.py +332 -0
  62. scitex/canvas/editor/flask_editor/_core/_routes_panels.py +252 -0
  63. scitex/canvas/editor/flask_editor/_core/_routes_save.py +218 -0
  64. scitex/canvas/editor/flask_editor/_core.py +25 -1684
  65. scitex/canvas/editor/flask_editor/templates/__init__.py +32 -70
  66. scitex/cli/__init__.py +38 -43
  67. scitex/cli/audio.py +76 -27
  68. scitex/cli/capture.py +13 -20
  69. scitex/cli/introspect.py +481 -0
  70. scitex/cli/main.py +200 -109
  71. scitex/cli/mcp.py +60 -34
  72. scitex/cli/plt.py +357 -0
  73. scitex/cli/repro.py +15 -8
  74. scitex/cli/resource.py +15 -8
  75. scitex/cli/scholar/__init__.py +23 -8
  76. scitex/cli/scholar/_crossref_scitex.py +296 -0
  77. scitex/cli/scholar/_fetch.py +25 -3
  78. scitex/cli/social.py +314 -0
  79. scitex/cli/stats.py +15 -8
  80. scitex/cli/template.py +129 -12
  81. scitex/cli/tex.py +15 -8
  82. scitex/cli/writer.py +132 -8
  83. scitex/cloud/__init__.py +41 -2
  84. scitex/config/README.md +1 -1
  85. scitex/config/__init__.py +16 -2
  86. scitex/config/_env_registry.py +256 -0
  87. scitex/context/__init__.py +22 -0
  88. scitex/dev/__init__.py +20 -1
  89. scitex/diagram/__init__.py +42 -19
  90. scitex/diagram/mcp_server.py +13 -125
  91. scitex/gen/__init__.py +50 -14
  92. scitex/gen/_list_packages.py +4 -4
  93. scitex/introspect/__init__.py +82 -0
  94. scitex/introspect/_call_graph.py +303 -0
  95. scitex/introspect/_class_hierarchy.py +163 -0
  96. scitex/introspect/_core.py +41 -0
  97. scitex/introspect/_docstring.py +131 -0
  98. scitex/introspect/_examples.py +113 -0
  99. scitex/introspect/_imports.py +271 -0
  100. scitex/{gen/_inspect_module.py → introspect/_list_api.py} +43 -54
  101. scitex/introspect/_mcp/__init__.py +41 -0
  102. scitex/introspect/_mcp/handlers.py +233 -0
  103. scitex/introspect/_members.py +155 -0
  104. scitex/introspect/_resolve.py +89 -0
  105. scitex/introspect/_signature.py +131 -0
  106. scitex/introspect/_source.py +80 -0
  107. scitex/introspect/_type_hints.py +172 -0
  108. scitex/io/_save.py +1 -2
  109. scitex/io/bundle/README.md +1 -1
  110. scitex/logging/_formatters.py +19 -9
  111. scitex/mcp_server.py +98 -5
  112. scitex/os/__init__.py +4 -0
  113. scitex/{gen → os}/_check_host.py +4 -5
  114. scitex/plt/__init__.py +245 -550
  115. scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_wrappers.py +5 -10
  116. scitex/plt/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
  117. scitex/plt/gallery/README.md +1 -1
  118. scitex/plt/utils/_hitmap/__init__.py +82 -0
  119. scitex/plt/utils/_hitmap/_artist_extraction.py +343 -0
  120. scitex/plt/utils/_hitmap/_color_application.py +346 -0
  121. scitex/plt/utils/_hitmap/_color_conversion.py +121 -0
  122. scitex/plt/utils/_hitmap/_constants.py +40 -0
  123. scitex/plt/utils/_hitmap/_hitmap_core.py +334 -0
  124. scitex/plt/utils/_hitmap/_path_extraction.py +357 -0
  125. scitex/plt/utils/_hitmap/_query.py +113 -0
  126. scitex/plt/utils/_hitmap.py +46 -1616
  127. scitex/plt/utils/_metadata/__init__.py +80 -0
  128. scitex/plt/utils/_metadata/_artists/__init__.py +25 -0
  129. scitex/plt/utils/_metadata/_artists/_base.py +195 -0
  130. scitex/plt/utils/_metadata/_artists/_collections.py +356 -0
  131. scitex/plt/utils/_metadata/_artists/_extract.py +57 -0
  132. scitex/plt/utils/_metadata/_artists/_images.py +80 -0
  133. scitex/plt/utils/_metadata/_artists/_lines.py +261 -0
  134. scitex/plt/utils/_metadata/_artists/_patches.py +247 -0
  135. scitex/plt/utils/_metadata/_artists/_text.py +106 -0
  136. scitex/plt/utils/_metadata/_csv.py +416 -0
  137. scitex/plt/utils/_metadata/_detect.py +225 -0
  138. scitex/plt/utils/_metadata/_legend.py +127 -0
  139. scitex/plt/utils/_metadata/_rounding.py +117 -0
  140. scitex/plt/utils/_metadata/_verification.py +202 -0
  141. scitex/schema/README.md +1 -1
  142. scitex/scholar/__init__.py +8 -0
  143. scitex/scholar/_mcp/crossref_handlers.py +265 -0
  144. scitex/scholar/core/Scholar.py +63 -1700
  145. scitex/scholar/core/_mixins/__init__.py +36 -0
  146. scitex/scholar/core/_mixins/_enrichers.py +270 -0
  147. scitex/scholar/core/_mixins/_library_handlers.py +100 -0
  148. scitex/scholar/core/_mixins/_loaders.py +103 -0
  149. scitex/scholar/core/_mixins/_pdf_download.py +375 -0
  150. scitex/scholar/core/_mixins/_pipeline.py +312 -0
  151. scitex/scholar/core/_mixins/_project_handlers.py +125 -0
  152. scitex/scholar/core/_mixins/_savers.py +69 -0
  153. scitex/scholar/core/_mixins/_search.py +103 -0
  154. scitex/scholar/core/_mixins/_services.py +88 -0
  155. scitex/scholar/core/_mixins/_url_finding.py +105 -0
  156. scitex/scholar/crossref_scitex.py +367 -0
  157. scitex/scholar/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
  158. scitex/scholar/examples/00_run_all.sh +120 -0
  159. scitex/scholar/jobs/_executors.py +27 -3
  160. scitex/scholar/pdf_download/ScholarPDFDownloader.py +38 -416
  161. scitex/scholar/pdf_download/_cli.py +154 -0
  162. scitex/scholar/pdf_download/strategies/__init__.py +11 -8
  163. scitex/scholar/pdf_download/strategies/manual_download_fallback.py +80 -3
  164. scitex/scholar/pipelines/ScholarPipelineBibTeX.py +73 -121
  165. scitex/scholar/pipelines/ScholarPipelineParallel.py +80 -138
  166. scitex/scholar/pipelines/ScholarPipelineSingle.py +43 -63
  167. scitex/scholar/pipelines/_single_steps.py +71 -36
  168. scitex/scholar/storage/_LibraryManager.py +97 -1695
  169. scitex/scholar/storage/_mixins/__init__.py +30 -0
  170. scitex/scholar/storage/_mixins/_bibtex_handlers.py +128 -0
  171. scitex/scholar/storage/_mixins/_library_operations.py +218 -0
  172. scitex/scholar/storage/_mixins/_metadata_conversion.py +226 -0
  173. scitex/scholar/storage/_mixins/_paper_saving.py +456 -0
  174. scitex/scholar/storage/_mixins/_resolution.py +376 -0
  175. scitex/scholar/storage/_mixins/_storage_helpers.py +121 -0
  176. scitex/scholar/storage/_mixins/_symlink_handlers.py +226 -0
  177. scitex/scholar/url_finder/.tmp/open_url/KNOWN_RESOLVERS.py +462 -0
  178. scitex/scholar/url_finder/.tmp/open_url/README.md +223 -0
  179. scitex/scholar/url_finder/.tmp/open_url/_DOIToURLResolver.py +694 -0
  180. scitex/scholar/url_finder/.tmp/open_url/_OpenURLResolver.py +1160 -0
  181. scitex/scholar/url_finder/.tmp/open_url/_ResolverLinkFinder.py +344 -0
  182. scitex/scholar/url_finder/.tmp/open_url/__init__.py +24 -0
  183. scitex/security/README.md +3 -3
  184. scitex/session/README.md +1 -1
  185. scitex/session/__init__.py +26 -7
  186. scitex/session/_decorator.py +1 -1
  187. scitex/sh/README.md +1 -1
  188. scitex/sh/__init__.py +7 -4
  189. scitex/social/__init__.py +155 -0
  190. scitex/social/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
  191. scitex/stats/_mcp/_handlers/__init__.py +31 -0
  192. scitex/stats/_mcp/_handlers/_corrections.py +113 -0
  193. scitex/stats/_mcp/_handlers/_descriptive.py +78 -0
  194. scitex/stats/_mcp/_handlers/_effect_size.py +106 -0
  195. scitex/stats/_mcp/_handlers/_format.py +94 -0
  196. scitex/stats/_mcp/_handlers/_normality.py +110 -0
  197. scitex/stats/_mcp/_handlers/_posthoc.py +224 -0
  198. scitex/stats/_mcp/_handlers/_power.py +247 -0
  199. scitex/stats/_mcp/_handlers/_recommend.py +102 -0
  200. scitex/stats/_mcp/_handlers/_run_test.py +279 -0
  201. scitex/stats/_mcp/_handlers/_stars.py +48 -0
  202. scitex/stats/_mcp/handlers.py +19 -1171
  203. scitex/stats/auto/_stat_style.py +175 -0
  204. scitex/stats/auto/_style_definitions.py +411 -0
  205. scitex/stats/auto/_styles.py +22 -620
  206. scitex/stats/descriptive/__init__.py +11 -8
  207. scitex/stats/descriptive/_ci.py +39 -0
  208. scitex/stats/power/_power.py +15 -4
  209. scitex/str/__init__.py +2 -1
  210. scitex/str/_title_case.py +63 -0
  211. scitex/template/README.md +1 -1
  212. scitex/template/__init__.py +25 -10
  213. scitex/template/_code_templates.py +147 -0
  214. scitex/template/_mcp/handlers.py +81 -0
  215. scitex/template/_mcp/tool_schemas.py +55 -0
  216. scitex/template/_templates/__init__.py +51 -0
  217. scitex/template/_templates/audio.py +233 -0
  218. scitex/template/_templates/canvas.py +312 -0
  219. scitex/template/_templates/capture.py +268 -0
  220. scitex/template/_templates/config.py +43 -0
  221. scitex/template/_templates/diagram.py +294 -0
  222. scitex/template/_templates/io.py +107 -0
  223. scitex/template/_templates/module.py +53 -0
  224. scitex/template/_templates/plt.py +202 -0
  225. scitex/template/_templates/scholar.py +267 -0
  226. scitex/template/_templates/session.py +130 -0
  227. scitex/template/_templates/session_minimal.py +43 -0
  228. scitex/template/_templates/session_plot.py +67 -0
  229. scitex/template/_templates/session_stats.py +77 -0
  230. scitex/template/_templates/stats.py +323 -0
  231. scitex/template/_templates/writer.py +296 -0
  232. scitex/template/clone_writer_directory.py +5 -5
  233. scitex/ui/_backends/_email.py +10 -2
  234. scitex/ui/_backends/_webhook.py +5 -1
  235. scitex/web/_search_pubmed.py +10 -6
  236. scitex/writer/README.md +1 -1
  237. scitex/writer/_mcp/handlers.py +11 -744
  238. scitex/writer/_mcp/tool_schemas.py +5 -335
  239. scitex-2.15.2.dist-info/METADATA +648 -0
  240. {scitex-2.14.0.dist-info → scitex-2.15.2.dist-info}/RECORD +246 -150
  241. scitex/canvas/editor/flask_editor/templates/_scripts.py +0 -4933
  242. scitex/canvas/editor/flask_editor/templates/_styles.py +0 -1658
  243. scitex/dev/plt/data/mpl/PLOTTING_FUNCTIONS.yaml +0 -90
  244. scitex/dev/plt/data/mpl/PLOTTING_SIGNATURES.yaml +0 -1571
  245. scitex/dev/plt/data/mpl/PLOTTING_SIGNATURES_DETAILED.yaml +0 -6262
  246. scitex/dev/plt/data/mpl/SIGNATURES_FLATTENED.yaml +0 -1274
  247. scitex/dev/plt/data/mpl/dir_ax.txt +0 -459
  248. scitex/diagram/_compile.py +0 -312
  249. scitex/diagram/_diagram.py +0 -355
  250. scitex/diagram/_mcp/__init__.py +0 -4
  251. scitex/diagram/_mcp/handlers.py +0 -400
  252. scitex/diagram/_mcp/tool_schemas.py +0 -157
  253. scitex/diagram/_presets.py +0 -173
  254. scitex/diagram/_schema.py +0 -182
  255. scitex/diagram/_split.py +0 -278
  256. scitex/gen/_ci.py +0 -12
  257. scitex/gen/_title_case.py +0 -89
  258. scitex/plt/_mcp/__init__.py +0 -4
  259. scitex/plt/_mcp/_handlers_annotation.py +0 -102
  260. scitex/plt/_mcp/_handlers_figure.py +0 -195
  261. scitex/plt/_mcp/_handlers_plot.py +0 -252
  262. scitex/plt/_mcp/_handlers_style.py +0 -219
  263. scitex/plt/_mcp/handlers.py +0 -74
  264. scitex/plt/_mcp/tool_schemas.py +0 -497
  265. scitex/plt/mcp_server.py +0 -231
  266. scitex/scholar/data/.gitkeep +0 -0
  267. scitex/scholar/data/README.md +0 -44
  268. scitex/scholar/data/bib_files/bibliography.bib +0 -1952
  269. scitex/scholar/data/bib_files/neurovista.bib +0 -277
  270. scitex/scholar/data/bib_files/neurovista_enriched.bib +0 -441
  271. scitex/scholar/data/bib_files/neurovista_enriched_enriched.bib +0 -441
  272. scitex/scholar/data/bib_files/neurovista_processed.bib +0 -338
  273. scitex/scholar/data/bib_files/openaccess.bib +0 -89
  274. scitex/scholar/data/bib_files/pac-seizure_prediction_enriched.bib +0 -2178
  275. scitex/scholar/data/bib_files/pac.bib +0 -698
  276. scitex/scholar/data/bib_files/pac_enriched.bib +0 -1061
  277. scitex/scholar/data/bib_files/pac_processed.bib +0 -0
  278. scitex/scholar/data/bib_files/pac_titles.txt +0 -75
  279. scitex/scholar/data/bib_files/paywalled.bib +0 -98
  280. scitex/scholar/data/bib_files/related-papers-by-coauthors.bib +0 -58
  281. scitex/scholar/data/bib_files/related-papers-by-coauthors_enriched.bib +0 -87
  282. scitex/scholar/data/bib_files/seizure_prediction.bib +0 -694
  283. scitex/scholar/data/bib_files/seizure_prediction_processed.bib +0 -0
  284. scitex/scholar/data/bib_files/test_complete_enriched.bib +0 -437
  285. scitex/scholar/data/bib_files/test_final_enriched.bib +0 -437
  286. scitex/scholar/data/bib_files/test_seizure.bib +0 -46
  287. scitex/scholar/data/impact_factor/JCR_IF_2022.xlsx +0 -0
  288. scitex/scholar/data/impact_factor/JCR_IF_2024.db +0 -0
  289. scitex/scholar/data/impact_factor/JCR_IF_2024.xlsx +0 -0
  290. scitex/scholar/data/impact_factor/JCR_IF_2024_v01.db +0 -0
  291. scitex/scholar/data/impact_factor.db +0 -0
  292. scitex/scholar/examples/SUGGESTIONS.md +0 -865
  293. scitex/scholar/examples/dev.py +0 -38
  294. scitex-2.14.0.dist-info/METADATA +0 -1238
  295. /scitex/{gen → context}/_detect_environment.py +0 -0
  296. /scitex/{gen → context}/_get_notebook_path.py +0 -0
  297. /scitex/{gen/_shell.py → sh/_shell_legacy.py} +0 -0
  298. {scitex-2.14.0.dist-info → scitex-2.15.2.dist-info}/WHEEL +0 -0
  299. {scitex-2.14.0.dist-info → scitex-2.15.2.dist-info}/entry_points.txt +0 -0
  300. {scitex-2.14.0.dist-info → scitex-2.15.2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: "2026-01-24 (ywatanabe)"
3
+ # File: /home/ywatanabe/proj/scitex-python/src/scitex/plt/utils/_metadata/_artists/_text.py
4
+
5
+ """
6
+ Text artist extraction.
7
+
8
+ Handles Text annotations and labels.
9
+ """
10
+
11
+ from typing import List
12
+
13
+ from ._base import ExtractionContext, color_to_hex
14
+
15
+
16
+ def extract_text(ctx: ExtractionContext) -> List[dict]:
17
+ """Extract text artists from axes."""
18
+ artists = []
19
+ text_count = 0
20
+
21
+ for i, text_obj in enumerate(ctx.mpl_ax.texts):
22
+ text_content = text_obj.get_text()
23
+ if not text_content or text_content.strip() == "":
24
+ continue
25
+
26
+ artist = _extract_text_artist(ctx, text_count, text_obj, text_content)
27
+ if artist:
28
+ artists.append(artist)
29
+ text_count += 1
30
+
31
+ return artists
32
+
33
+
34
+ def _extract_text_artist(
35
+ ctx: ExtractionContext, index: int, text_obj, text_content: str
36
+ ) -> dict:
37
+ """Extract Text artist."""
38
+ artist = {}
39
+
40
+ scitex_id = getattr(text_obj, "_scitex_id", None)
41
+
42
+ if scitex_id:
43
+ artist["id"] = scitex_id
44
+ else:
45
+ artist["id"] = f"text_{index}"
46
+
47
+ # Semantic layer
48
+ artist["mark"] = "text"
49
+
50
+ # Determine role from content
51
+ pos = text_obj.get_position()
52
+ if any(kw in text_content.lower() for kw in ["r=", "p=", "r²=", "n="]):
53
+ artist["role"] = "stats_annotation"
54
+ else:
55
+ artist["role"] = "annotation"
56
+
57
+ artist["legend_included"] = False
58
+ artist["zorder"] = text_obj.get_zorder()
59
+
60
+ # Geometry
61
+ artist["geometry"] = {
62
+ "x": pos[0],
63
+ "y": pos[1],
64
+ }
65
+
66
+ # Text content
67
+ artist["text"] = text_content
68
+
69
+ # Backend layer
70
+ backend = {
71
+ "name": "matplotlib",
72
+ "artist_class": type(text_obj).__name__,
73
+ "props": {},
74
+ }
75
+
76
+ try:
77
+ color = text_obj.get_color()
78
+ backend["props"]["color"] = color_to_hex(color)
79
+ except (ValueError, TypeError):
80
+ pass
81
+
82
+ try:
83
+ backend["props"]["fontsize_pt"] = text_obj.get_fontsize()
84
+ except (ValueError, TypeError):
85
+ pass
86
+
87
+ try:
88
+ backend["props"]["ha"] = text_obj.get_ha()
89
+ backend["props"]["va"] = text_obj.get_va()
90
+ except (ValueError, TypeError):
91
+ pass
92
+
93
+ artist["backend"] = backend
94
+
95
+ # Data reference for tracked text
96
+ if scitex_id:
97
+ artist["data_ref"] = {
98
+ "x": f"text_{index}_x",
99
+ "y": f"text_{index}_y",
100
+ "content": f"text_{index}_content",
101
+ }
102
+
103
+ return artist
104
+
105
+
106
+ # EOF
@@ -0,0 +1,416 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: "2026-01-24 (ywatanabe)"
3
+ # File: /home/ywatanabe/proj/scitex-python/src/scitex/plt/utils/_metadata/_csv.py
4
+
5
+ """
6
+ CSV column naming and hash utilities for figure metadata.
7
+
8
+ Provides functions for extracting CSV column information from scitex history
9
+ and computing data hashes for reproducibility verification.
10
+ """
11
+
12
+ from typing import List, Optional
13
+
14
+
15
+ def _get_csv_column_names(
16
+ trace_id: str, ax_row: int = 0, ax_col: int = 0, variables: list = None
17
+ ) -> dict:
18
+ """
19
+ Get the CSV column names for a given trace.
20
+
21
+ Parameters
22
+ ----------
23
+ trace_id : str
24
+ Unique ID of the trace (e.g., "plot_0", "scatter_1")
25
+ ax_row : int
26
+ Row position of axes in grid (default: 0)
27
+ ax_col : int
28
+ Column position of axes in grid (default: 0)
29
+ variables : list, optional
30
+ List of variable names (default: ["x", "y"])
31
+
32
+ Returns
33
+ -------
34
+ dict
35
+ Dictionary mapping variable names to CSV column names
36
+ """
37
+ from .._csv_column_naming import get_csv_column_name
38
+
39
+ if variables is None:
40
+ variables = ["x", "y"]
41
+
42
+ data_ref = {}
43
+ for var in variables:
44
+ data_ref[var] = get_csv_column_name(var, ax_row, ax_col, trace_id=trace_id)
45
+
46
+ return data_ref
47
+
48
+
49
+ def _extract_csv_columns_from_history(ax) -> list:
50
+ """
51
+ Extract CSV column names from scitex history for all plot types.
52
+
53
+ Parameters
54
+ ----------
55
+ ax : AxisWrapper or matplotlib.axes.Axes
56
+ The axes to extract CSV column info from
57
+
58
+ Returns
59
+ -------
60
+ list
61
+ List of dictionaries containing CSV column mappings for each tracked plot
62
+ """
63
+ # Get axes position for CSV column naming
64
+ ax_row, ax_col = 0, 0
65
+ if hasattr(ax, "_scitex_metadata") and "position_in_grid" in ax._scitex_metadata:
66
+ pos = ax._scitex_metadata["position_in_grid"]
67
+ ax_row, ax_col = pos[0], pos[1]
68
+
69
+ csv_columns_list = []
70
+
71
+ if not hasattr(ax, "history") or len(ax.history) == 0:
72
+ return csv_columns_list
73
+
74
+ for trace_index, (record_id, record) in enumerate(ax.history.items()):
75
+ if not isinstance(record, tuple) or len(record) < 4:
76
+ continue
77
+
78
+ id_val, method, tracked_dict, kwargs = record
79
+
80
+ columns = _get_csv_columns_for_method_with_index(
81
+ id_val, method, tracked_dict, kwargs, ax_row, ax_col, trace_index
82
+ )
83
+
84
+ if columns:
85
+ csv_columns_list.append(
86
+ {
87
+ "id": id_val,
88
+ "method": method,
89
+ "columns": columns,
90
+ }
91
+ )
92
+
93
+ return csv_columns_list
94
+
95
+
96
+ def _get_csv_columns_for_method_with_index(
97
+ id_val, method, tracked_dict, kwargs, ax_row: int, ax_col: int, trace_index: int
98
+ ) -> List[str]:
99
+ """
100
+ Get CSV column names for a specific plotting method using trace index.
101
+
102
+ Parameters
103
+ ----------
104
+ id_val : str
105
+ The plot ID
106
+ method : str
107
+ The plotting method name
108
+ tracked_dict : dict
109
+ The tracked data dictionary
110
+ kwargs : dict
111
+ The keyword arguments passed to the plot
112
+ ax_row : int
113
+ Row index of axes in grid
114
+ ax_col : int
115
+ Column index of axes in grid
116
+ trace_index : int
117
+ Index of this trace
118
+
119
+ Returns
120
+ -------
121
+ list
122
+ List of column names that will be in the CSV
123
+ """
124
+ from .._csv_column_naming import get_csv_column_name
125
+
126
+ columns = []
127
+
128
+ method_columns = {
129
+ ("plot", "stx_line"): ["x", "y"],
130
+ ("scatter", "plot_scatter"): ["x", "y"],
131
+ ("bar", "barh"): ["x", "height"],
132
+ ("hist",): ["bins", "counts"],
133
+ ("boxplot", "stx_box"): ["data"],
134
+ ("violinplot", "stx_violin"): ["data"],
135
+ ("errorbar",): ["x", "y", "yerr"],
136
+ ("fill_between",): ["x", "y1", "y2"],
137
+ ("imshow", "stx_heatmap", "stx_image"): ["data"],
138
+ ("stx_kde", "stx_ecdf"): ["x", "y"],
139
+ ("stx_mean_std", "stx_mean_ci", "stx_median_iqr", "stx_shaded_line"): [
140
+ "x",
141
+ "y",
142
+ "lower",
143
+ "upper",
144
+ ],
145
+ }
146
+
147
+ for methods, vars in method_columns.items():
148
+ if method in methods:
149
+ columns = [
150
+ get_csv_column_name(v, ax_row, ax_col, trace_index=trace_index)
151
+ for v in vars
152
+ ]
153
+ return columns
154
+
155
+ # Handle seaborn methods
156
+ if method.startswith("sns_"):
157
+ sns_type = method.replace("sns_", "")
158
+ sns_columns = {
159
+ ("boxplot", "violinplot"): ["data"],
160
+ ("scatterplot", "lineplot"): ["x", "y"],
161
+ ("barplot",): ["x", "y"],
162
+ ("histplot",): ["bins", "counts"],
163
+ ("kdeplot",): ["x", "y"],
164
+ }
165
+ for types, vars in sns_columns.items():
166
+ if sns_type in types:
167
+ columns = [
168
+ get_csv_column_name(v, ax_row, ax_col, trace_index=trace_index)
169
+ for v in vars
170
+ ]
171
+ return columns
172
+
173
+ return columns
174
+
175
+
176
+ def _get_csv_columns_for_method(
177
+ id_val, method, tracked_dict, kwargs, ax_index: int
178
+ ) -> List[str]:
179
+ """
180
+ Get CSV column names for a specific plotting method.
181
+
182
+ Uses the same formatters that generate the CSV to ensure consistency.
183
+
184
+ Parameters
185
+ ----------
186
+ id_val : str
187
+ The plot ID
188
+ method : str
189
+ The plotting method name
190
+ tracked_dict : dict
191
+ The tracked data dictionary
192
+ kwargs : dict
193
+ The keyword arguments passed to the plot
194
+ ax_index : int
195
+ Flattened index of axes
196
+
197
+ Returns
198
+ -------
199
+ list
200
+ List of column names that will be in the CSV
201
+ """
202
+ try:
203
+ from scitex.plt._subplots._export_as_csv import format_record
204
+
205
+ record = (id_val, method, tracked_dict, kwargs)
206
+ df = format_record(record)
207
+
208
+ if df is not None and not df.empty:
209
+ prefix = f"ax_{ax_index:02d}_"
210
+ columns = []
211
+ for col in df.columns:
212
+ col_str = str(col)
213
+ if not col_str.startswith(prefix):
214
+ col_str = f"{prefix}{col_str}"
215
+ columns.append(col_str)
216
+ return columns
217
+
218
+ except Exception:
219
+ pass
220
+
221
+ # Fallback: Pattern-based column name generation
222
+ return _get_csv_columns_fallback(id_val, method, tracked_dict, kwargs, ax_index)
223
+
224
+
225
+ def _get_csv_columns_fallback(
226
+ id_val, method, tracked_dict, kwargs, ax_index: int
227
+ ) -> List[str]:
228
+ """Fallback pattern-based CSV column name generation."""
229
+ prefix = f"ax_{ax_index:02d}_"
230
+ columns = []
231
+ args = tracked_dict.get("args", []) if tracked_dict else []
232
+
233
+ if method in ("boxplot", "stx_box"):
234
+ columns = _get_boxplot_columns(args, kwargs, prefix, id_val)
235
+ elif method in ("plot", "stx_line"):
236
+ columns = [f"{prefix}{id_val}_plot_x", f"{prefix}{id_val}_plot_y"]
237
+ elif method in ("scatter", "plot_scatter"):
238
+ columns = [f"{prefix}{id_val}_scatter_x", f"{prefix}{id_val}_scatter_y"]
239
+ elif method in ("bar", "barh"):
240
+ columns = [f"{prefix}{id_val}_bar_x", f"{prefix}{id_val}_bar_height"]
241
+ elif method == "hist":
242
+ columns = [f"{prefix}{id_val}_hist_bins", f"{prefix}{id_val}_hist_counts"]
243
+ elif method in ("violinplot", "stx_violin"):
244
+ columns = _get_violin_columns(args, prefix, id_val)
245
+ elif method == "errorbar":
246
+ columns = [
247
+ f"{prefix}{id_val}_errorbar_x",
248
+ f"{prefix}{id_val}_errorbar_y",
249
+ f"{prefix}{id_val}_errorbar_yerr",
250
+ ]
251
+ elif method == "fill_between":
252
+ columns = [
253
+ f"{prefix}{id_val}_fill_x",
254
+ f"{prefix}{id_val}_fill_y1",
255
+ f"{prefix}{id_val}_fill_y2",
256
+ ]
257
+ elif method in ("imshow", "stx_heatmap", "stx_image"):
258
+ if args and hasattr(args[0], "shape") and len(args[0].shape) >= 2:
259
+ columns = [f"{prefix}{id_val}_image_data"]
260
+ elif method in ("stx_kde", "stx_ecdf"):
261
+ suffix = method.replace("stx_", "")
262
+ columns = [f"{prefix}{id_val}_{suffix}_x", f"{prefix}{id_val}_{suffix}_y"]
263
+ elif method in ("stx_mean_std", "stx_mean_ci", "stx_median_iqr", "stx_shaded_line"):
264
+ suffix = method.replace("stx_", "")
265
+ columns = [
266
+ f"{prefix}{id_val}_{suffix}_x",
267
+ f"{prefix}{id_val}_{suffix}_y",
268
+ f"{prefix}{id_val}_{suffix}_lower",
269
+ f"{prefix}{id_val}_{suffix}_upper",
270
+ ]
271
+ elif method.startswith("sns_"):
272
+ columns = _get_seaborn_columns(method, prefix, id_val)
273
+
274
+ return columns
275
+
276
+
277
+ def _get_boxplot_columns(args, kwargs, prefix, id_val) -> List[str]:
278
+ """Get columns for boxplot data."""
279
+ import numpy as np
280
+
281
+ columns = []
282
+ if len(args) >= 1:
283
+ data = args[0]
284
+ labels = kwargs.get("labels", None) if kwargs else None
285
+
286
+ from scitex.types import is_listed_X as scitex_types_is_listed_X
287
+
288
+ if isinstance(data, np.ndarray) or scitex_types_is_listed_X(data, [float, int]):
289
+ if labels and len(labels) == 1:
290
+ columns.append(f"{prefix}{id_val}_{labels[0]}")
291
+ else:
292
+ columns.append(f"{prefix}{id_val}_boxplot_0")
293
+ else:
294
+ try:
295
+ num_boxes = len(data)
296
+ if labels and len(labels) == num_boxes:
297
+ for label in labels:
298
+ columns.append(f"{prefix}{id_val}_{label}")
299
+ else:
300
+ for i in range(num_boxes):
301
+ columns.append(f"{prefix}{id_val}_boxplot_{i}")
302
+ except TypeError:
303
+ columns.append(f"{prefix}{id_val}_boxplot_0")
304
+
305
+ return columns
306
+
307
+
308
+ def _get_violin_columns(args, prefix, id_val) -> List[str]:
309
+ """Get columns for violin plot data."""
310
+ columns = []
311
+ if len(args) >= 1:
312
+ data = args[0]
313
+ try:
314
+ num_violins = len(data)
315
+ for i in range(num_violins):
316
+ columns.append(f"{prefix}{id_val}_violin_{i}")
317
+ except TypeError:
318
+ columns.append(f"{prefix}{id_val}_violin_0")
319
+ return columns
320
+
321
+
322
+ def _get_seaborn_columns(method, prefix, id_val) -> List[str]:
323
+ """Get columns for seaborn plots."""
324
+ sns_type = method.replace("sns_", "")
325
+ if sns_type in ("boxplot", "violinplot"):
326
+ return [f"{prefix}{id_val}_{sns_type}_data"]
327
+ elif sns_type in ("scatterplot", "lineplot"):
328
+ return [f"{prefix}{id_val}_{sns_type}_x", f"{prefix}{id_val}_{sns_type}_y"]
329
+ elif sns_type == "barplot":
330
+ return [f"{prefix}{id_val}_barplot_x", f"{prefix}{id_val}_barplot_y"]
331
+ elif sns_type == "histplot":
332
+ return [f"{prefix}{id_val}_histplot_bins", f"{prefix}{id_val}_histplot_counts"]
333
+ elif sns_type == "kdeplot":
334
+ return [f"{prefix}{id_val}_kdeplot_x", f"{prefix}{id_val}_kdeplot_y"]
335
+ return []
336
+
337
+
338
+ def _compute_csv_hash_from_df(df) -> Optional[str]:
339
+ """
340
+ Compute a hash of CSV data from a DataFrame.
341
+
342
+ Parameters
343
+ ----------
344
+ df : pandas.DataFrame
345
+ The DataFrame to compute hash from
346
+
347
+ Returns
348
+ -------
349
+ str or None
350
+ SHA256 hash (first 16 chars) or None if unable to compute
351
+ """
352
+ import hashlib
353
+
354
+ try:
355
+ if df is None or df.empty:
356
+ return None
357
+
358
+ csv_string = df.to_csv(index=False)
359
+ hash_obj = hashlib.sha256(csv_string.encode("utf-8"))
360
+ return hash_obj.hexdigest()[:16]
361
+
362
+ except Exception:
363
+ return None
364
+
365
+
366
+ def _compute_csv_hash(ax_or_df) -> Optional[str]:
367
+ """
368
+ Compute a hash of the CSV data for reproducibility verification.
369
+
370
+ Parameters
371
+ ----------
372
+ ax_or_df : AxisWrapper, matplotlib.axes.Axes, or pandas.DataFrame
373
+ The axes or DataFrame to compute hash from
374
+
375
+ Returns
376
+ -------
377
+ str or None
378
+ SHA256 hash (first 16 chars) or None if unable to compute
379
+ """
380
+ import hashlib
381
+
382
+ import pandas as pd
383
+
384
+ if isinstance(ax_or_df, pd.DataFrame):
385
+ return _compute_csv_hash_from_df(ax_or_df)
386
+
387
+ ax = ax_or_df
388
+
389
+ if not hasattr(ax, "export_as_csv"):
390
+ return None
391
+
392
+ try:
393
+ ax_index = 0
394
+ df = ax.export_as_csv()
395
+
396
+ if df is None or df.empty:
397
+ return None
398
+
399
+ prefix = f"ax_{ax_index:02d}_"
400
+ new_cols = []
401
+ for col in df.columns:
402
+ col_str = str(col)
403
+ if not col_str.startswith(prefix):
404
+ col_str = f"{prefix}{col_str}"
405
+ new_cols.append(col_str)
406
+ df.columns = new_cols
407
+
408
+ csv_string = df.to_csv(index=False)
409
+ hash_obj = hashlib.sha256(csv_string.encode("utf-8"))
410
+ return hash_obj.hexdigest()[:16]
411
+
412
+ except Exception:
413
+ return None
414
+
415
+
416
+ # EOF