scitex 2.14.0__py3-none-any.whl → 2.15.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 (264) 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 +27 -0
  16. scitex/_mcp_tools/template.py +24 -0
  17. scitex/_mcp_tools/writer.py +17 -210
  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 +160 -41
  68. scitex/cli/capture.py +133 -20
  69. scitex/cli/introspect.py +488 -0
  70. scitex/cli/main.py +200 -109
  71. scitex/cli/mcp.py +60 -34
  72. scitex/cli/plt.py +414 -0
  73. scitex/cli/repro.py +15 -8
  74. scitex/cli/resource.py +15 -8
  75. scitex/cli/scholar/__init__.py +154 -8
  76. scitex/cli/scholar/_crossref_scitex.py +296 -0
  77. scitex/cli/scholar/_fetch.py +25 -3
  78. scitex/cli/social.py +355 -0
  79. scitex/cli/stats.py +136 -11
  80. scitex/cli/template.py +129 -12
  81. scitex/cli/tex.py +15 -8
  82. scitex/cli/writer.py +49 -299
  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} +48 -56
  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/security/README.md +3 -3
  178. scitex/session/README.md +1 -1
  179. scitex/session/__init__.py +26 -7
  180. scitex/session/_decorator.py +1 -1
  181. scitex/sh/README.md +1 -1
  182. scitex/sh/__init__.py +7 -4
  183. scitex/social/__init__.py +155 -0
  184. scitex/social/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
  185. scitex/stats/_mcp/_handlers/__init__.py +31 -0
  186. scitex/stats/_mcp/_handlers/_corrections.py +113 -0
  187. scitex/stats/_mcp/_handlers/_descriptive.py +78 -0
  188. scitex/stats/_mcp/_handlers/_effect_size.py +106 -0
  189. scitex/stats/_mcp/_handlers/_format.py +94 -0
  190. scitex/stats/_mcp/_handlers/_normality.py +110 -0
  191. scitex/stats/_mcp/_handlers/_posthoc.py +224 -0
  192. scitex/stats/_mcp/_handlers/_power.py +247 -0
  193. scitex/stats/_mcp/_handlers/_recommend.py +102 -0
  194. scitex/stats/_mcp/_handlers/_run_test.py +279 -0
  195. scitex/stats/_mcp/_handlers/_stars.py +48 -0
  196. scitex/stats/_mcp/handlers.py +19 -1171
  197. scitex/stats/auto/_stat_style.py +175 -0
  198. scitex/stats/auto/_style_definitions.py +411 -0
  199. scitex/stats/auto/_styles.py +22 -620
  200. scitex/stats/descriptive/__init__.py +11 -8
  201. scitex/stats/descriptive/_ci.py +39 -0
  202. scitex/stats/power/_power.py +15 -4
  203. scitex/str/__init__.py +2 -1
  204. scitex/str/_title_case.py +63 -0
  205. scitex/template/README.md +1 -1
  206. scitex/template/__init__.py +25 -10
  207. scitex/template/_code_templates.py +147 -0
  208. scitex/template/_mcp/handlers.py +81 -0
  209. scitex/template/_mcp/tool_schemas.py +55 -0
  210. scitex/template/_templates/__init__.py +51 -0
  211. scitex/template/_templates/audio.py +233 -0
  212. scitex/template/_templates/canvas.py +312 -0
  213. scitex/template/_templates/capture.py +268 -0
  214. scitex/template/_templates/config.py +43 -0
  215. scitex/template/_templates/diagram.py +294 -0
  216. scitex/template/_templates/io.py +107 -0
  217. scitex/template/_templates/module.py +53 -0
  218. scitex/template/_templates/plt.py +202 -0
  219. scitex/template/_templates/scholar.py +267 -0
  220. scitex/template/_templates/session.py +130 -0
  221. scitex/template/_templates/session_minimal.py +43 -0
  222. scitex/template/_templates/session_plot.py +67 -0
  223. scitex/template/_templates/session_stats.py +77 -0
  224. scitex/template/_templates/stats.py +323 -0
  225. scitex/template/_templates/writer.py +296 -0
  226. scitex/template/clone_writer_directory.py +5 -5
  227. scitex/ui/_backends/_email.py +10 -2
  228. scitex/ui/_backends/_webhook.py +5 -1
  229. scitex/web/_search_pubmed.py +10 -6
  230. scitex/writer/README.md +1 -1
  231. scitex/writer/__init__.py +43 -34
  232. scitex/writer/_mcp/handlers.py +11 -744
  233. scitex/writer/_mcp/tool_schemas.py +5 -335
  234. scitex-2.15.3.dist-info/METADATA +667 -0
  235. {scitex-2.14.0.dist-info → scitex-2.15.3.dist-info}/RECORD +241 -120
  236. scitex/canvas/editor/flask_editor/templates/_scripts.py +0 -4933
  237. scitex/canvas/editor/flask_editor/templates/_styles.py +0 -1658
  238. scitex/diagram/_compile.py +0 -312
  239. scitex/diagram/_diagram.py +0 -355
  240. scitex/diagram/_mcp/__init__.py +0 -4
  241. scitex/diagram/_mcp/handlers.py +0 -400
  242. scitex/diagram/_mcp/tool_schemas.py +0 -157
  243. scitex/diagram/_presets.py +0 -173
  244. scitex/diagram/_schema.py +0 -182
  245. scitex/diagram/_split.py +0 -278
  246. scitex/gen/_ci.py +0 -12
  247. scitex/gen/_title_case.py +0 -89
  248. scitex/plt/_mcp/__init__.py +0 -4
  249. scitex/plt/_mcp/_handlers_annotation.py +0 -102
  250. scitex/plt/_mcp/_handlers_figure.py +0 -195
  251. scitex/plt/_mcp/_handlers_plot.py +0 -252
  252. scitex/plt/_mcp/_handlers_style.py +0 -219
  253. scitex/plt/_mcp/handlers.py +0 -74
  254. scitex/plt/_mcp/tool_schemas.py +0 -497
  255. scitex/plt/mcp_server.py +0 -231
  256. scitex/scholar/examples/SUGGESTIONS.md +0 -865
  257. scitex/scholar/examples/dev.py +0 -38
  258. scitex-2.14.0.dist-info/METADATA +0 -1238
  259. /scitex/{gen → context}/_detect_environment.py +0 -0
  260. /scitex/{gen → context}/_get_notebook_path.py +0 -0
  261. /scitex/{gen/_shell.py → sh/_shell_legacy.py} +0 -0
  262. {scitex-2.14.0.dist-info → scitex-2.15.3.dist-info}/WHEEL +0 -0
  263. {scitex-2.14.0.dist-info → scitex-2.15.3.dist-info}/entry_points.txt +0 -0
  264. {scitex-2.14.0.dist-info → scitex-2.15.3.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: "2026-01-24 (ywatanabe)"
3
+ # File: /home/ywatanabe/proj/scitex-python/src/scitex/plt/utils/_metadata/_artists/_extract.py
4
+
5
+ """
6
+ Main artist extraction function.
7
+
8
+ Orchestrates extraction of all artist types from matplotlib axes.
9
+ """
10
+
11
+ from typing import List
12
+
13
+ from ._base import create_extraction_context
14
+ from ._collections import extract_collections
15
+ from ._images import extract_images
16
+ from ._lines import extract_lines
17
+ from ._patches import extract_patches
18
+ from ._text import extract_text
19
+
20
+
21
+ def _extract_artists(ax) -> List[dict]:
22
+ """
23
+ Extract artist information including properties and CSV column mapping.
24
+
25
+ Uses matplotlib terminology: each drawable element is an Artist.
26
+ Only includes artists that were explicitly created via scitex tracking,
27
+ not internal artists created by matplotlib functions.
28
+
29
+ Parameters
30
+ ----------
31
+ ax : matplotlib.axes.Axes
32
+ The axes to extract artists from
33
+
34
+ Returns
35
+ -------
36
+ list
37
+ List of artist dictionaries with:
38
+ - id: unique identifier
39
+ - artist_class: matplotlib class name (Line2D, PathCollection, etc.)
40
+ - label: legend label
41
+ - style: color, linestyle, linewidth, etc.
42
+ - data_ref: CSV column mapping (matches columns_actual exactly)
43
+ """
44
+ ctx = create_extraction_context(ax)
45
+ artists = []
46
+
47
+ # Extract different artist types
48
+ artists.extend(extract_lines(ctx))
49
+ artists.extend(extract_patches(ctx))
50
+ artists.extend(extract_collections(ctx))
51
+ artists.extend(extract_images(ctx))
52
+ artists.extend(extract_text(ctx))
53
+
54
+ return artists
55
+
56
+
57
+ # EOF
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: "2026-01-24 (ywatanabe)"
3
+ # File: /home/ywatanabe/proj/scitex-python/src/scitex/plt/utils/_metadata/_artists/_images.py
4
+
5
+ """
6
+ Image artist extraction.
7
+
8
+ Handles AxesImage (imshow, matshow) extraction.
9
+ """
10
+
11
+ from typing import List
12
+
13
+ from ._base import ExtractionContext
14
+
15
+
16
+ def extract_images(ctx: ExtractionContext) -> List[dict]:
17
+ """Extract image artists from axes."""
18
+ artists = []
19
+
20
+ for i, img in enumerate(ctx.mpl_ax.images):
21
+ artist = _extract_image(ctx, i, img)
22
+ if artist:
23
+ artists.append(artist)
24
+
25
+ return artists
26
+
27
+
28
+ def _extract_image(ctx: ExtractionContext, index: int, img) -> dict:
29
+ """Extract AxesImage artist."""
30
+ artist = {}
31
+ img_type = type(img).__name__
32
+
33
+ scitex_id = getattr(img, "_scitex_id", None)
34
+ label = img.get_label() if hasattr(img, "get_label") else ""
35
+
36
+ if scitex_id:
37
+ artist["id"] = scitex_id
38
+ elif label and not label.startswith("_"):
39
+ artist["id"] = label
40
+ else:
41
+ artist["id"] = f"image_{index}"
42
+
43
+ # Semantic layer
44
+ artist["mark"] = "image"
45
+ artist["role"] = "image"
46
+
47
+ artist["legend_included"] = False
48
+ artist["zorder"] = img.get_zorder()
49
+
50
+ # Backend layer
51
+ backend = {
52
+ "name": "matplotlib",
53
+ "artist_class": img_type,
54
+ "props": {},
55
+ }
56
+
57
+ try:
58
+ cmap = img.get_cmap()
59
+ if cmap:
60
+ backend["props"]["cmap"] = cmap.name
61
+ except (ValueError, TypeError, AttributeError):
62
+ pass
63
+
64
+ try:
65
+ backend["props"]["vmin"] = float(img.norm.vmin) if img.norm else None
66
+ backend["props"]["vmax"] = float(img.norm.vmax) if img.norm else None
67
+ except (ValueError, TypeError, AttributeError):
68
+ pass
69
+
70
+ try:
71
+ backend["props"]["interpolation"] = img.get_interpolation()
72
+ except (ValueError, TypeError, AttributeError):
73
+ pass
74
+
75
+ artist["backend"] = backend
76
+
77
+ return artist
78
+
79
+
80
+ # EOF
@@ -0,0 +1,261 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: "2026-01-24 (ywatanabe)"
3
+ # File: /home/ywatanabe/proj/scitex-python/src/scitex/plt/utils/_metadata/_artists/_lines.py
4
+
5
+ """
6
+ Line2D artist extraction.
7
+
8
+ Handles extraction of line plots, including special handling for
9
+ boxplot, violin, and stem semantic components.
10
+ """
11
+
12
+ from typing import List, Optional, Tuple
13
+
14
+ from ._base import ExtractionContext, color_to_hex
15
+
16
+
17
+ def extract_lines(ctx: ExtractionContext) -> List[dict]:
18
+ """Extract Line2D artists from axes."""
19
+ from .._csv import _get_csv_column_names
20
+
21
+ artists = []
22
+
23
+ for i, line in enumerate(ctx.mpl_ax.lines):
24
+ scitex_id = getattr(line, "_scitex_id", None)
25
+ label = line.get_label()
26
+
27
+ # Determine semantic type for special plot types
28
+ semantic_type, semantic_id, box_idx = _get_line_semantic_info(
29
+ ctx, i, line, scitex_id, label
30
+ )
31
+
32
+ # Skip internal artists for certain plot types
33
+ if _should_skip_line(ctx, scitex_id, label, semantic_type):
34
+ continue
35
+
36
+ artist = _build_line_artist(
37
+ ctx, i, line, scitex_id, label, semantic_type, semantic_id, box_idx
38
+ )
39
+
40
+ # Add data_ref for non-semantic lines
41
+ if not semantic_type:
42
+ trace_id = _get_trace_id_for_line(ctx, i, scitex_id, artist.get("id"))
43
+ artist["data_ref"] = _get_csv_column_names(trace_id, ctx.ax_row, ctx.ax_col)
44
+ elif ctx.is_stem and scitex_id:
45
+ artist["data_ref"] = _get_csv_column_names(
46
+ scitex_id, ctx.ax_row, ctx.ax_col
47
+ )
48
+ if semantic_type == "stem_baseline":
49
+ artist["derived"] = True
50
+ artist["data_ref"]["derived_from"] = "y=0"
51
+
52
+ # Add boxplot statistics
53
+ if (
54
+ semantic_type == "boxplot_median"
55
+ and box_idx is not None
56
+ and box_idx < len(ctx.boxplot_stats)
57
+ ):
58
+ artist["stats"] = ctx.boxplot_stats[box_idx]
59
+
60
+ artists.append(artist)
61
+
62
+ return artists
63
+
64
+
65
+ def _get_line_semantic_info(
66
+ ctx: ExtractionContext,
67
+ index: int,
68
+ line,
69
+ scitex_id: Optional[str],
70
+ label: str,
71
+ ) -> Tuple[Optional[str], Optional[str], Optional[int]]:
72
+ """Get semantic type info for a line."""
73
+ semantic_type = None
74
+ semantic_id = None
75
+ box_idx = None
76
+
77
+ # Stem detection
78
+ if ctx.is_stem:
79
+ semantic_type, semantic_id = _detect_stem_semantic(line, index)
80
+
81
+ # Boxplot detection for unlabeled lines
82
+ if (
83
+ ctx.skip_unlabeled
84
+ and not scitex_id
85
+ and label.startswith("_")
86
+ and ctx.is_boxplot
87
+ and ctx.num_boxes > 0
88
+ ):
89
+ semantic_type, semantic_id, box_idx = _detect_boxplot_semantic(
90
+ ctx.num_boxes, index
91
+ )
92
+ elif (
93
+ ctx.skip_unlabeled and not scitex_id and label.startswith("_") and ctx.is_violin
94
+ ):
95
+ semantic_type = "violin_component"
96
+ semantic_id = f"violin_line_{index}"
97
+
98
+ return semantic_type, semantic_id, box_idx
99
+
100
+
101
+ def _detect_stem_semantic(line, index: int) -> Tuple[str, str]:
102
+ """Detect stem plot semantic type."""
103
+ marker = line.get_marker()
104
+ linestyle = line.get_linestyle()
105
+
106
+ if marker and marker != "None" and linestyle == "None":
107
+ return "stem_marker", "stem_markers"
108
+ elif linestyle and linestyle != "None":
109
+ ydata = line.get_ydata()
110
+ if len(ydata) >= 2 and len(set(ydata)) == 1:
111
+ return "stem_baseline", "stem_baseline"
112
+ else:
113
+ return "stem_stem", "stem_lines"
114
+ else:
115
+ return "stem_component", f"stem_{index}"
116
+
117
+
118
+ def _detect_boxplot_semantic(num_boxes: int, index: int) -> Tuple[str, str, int]:
119
+ """Detect boxplot semantic type based on line index."""
120
+ total_whiskers = 2 * num_boxes
121
+ total_caps = 2 * num_boxes
122
+ total_medians = num_boxes
123
+
124
+ if index < total_whiskers:
125
+ box_idx = index // 2
126
+ whisker_idx = index % 2
127
+ return "boxplot_whisker", f"box_{box_idx}_whisker_{whisker_idx}", box_idx
128
+ elif index < total_whiskers + total_caps:
129
+ cap_i = index - total_whiskers
130
+ box_idx = cap_i // 2
131
+ cap_idx = cap_i % 2
132
+ return "boxplot_cap", f"box_{box_idx}_cap_{cap_idx}", box_idx
133
+ elif index < total_whiskers + total_caps + total_medians:
134
+ box_idx = index - total_whiskers - total_caps
135
+ return "boxplot_median", f"box_{box_idx}_median", box_idx
136
+ else:
137
+ flier_idx = index - total_whiskers - total_caps - total_medians
138
+ box_idx = flier_idx if flier_idx < num_boxes else num_boxes - 1
139
+ return "boxplot_flier", f"box_{box_idx}_flier", box_idx
140
+
141
+
142
+ def _should_skip_line(
143
+ ctx: ExtractionContext,
144
+ scitex_id: Optional[str],
145
+ label: str,
146
+ semantic_type: Optional[str],
147
+ ) -> bool:
148
+ """Check if line should be skipped."""
149
+ if ctx.skip_unlabeled and not scitex_id and label.startswith("_"):
150
+ # Allow boxplot, violin, stem semantic types
151
+ if ctx.is_boxplot or ctx.is_violin or ctx.is_stem:
152
+ return False
153
+ return True
154
+ return False
155
+
156
+
157
+ def _build_line_artist(
158
+ ctx: ExtractionContext,
159
+ index: int,
160
+ line,
161
+ scitex_id: Optional[str],
162
+ label: str,
163
+ semantic_type: Optional[str],
164
+ semantic_id: Optional[str],
165
+ box_idx: Optional[int],
166
+ ) -> dict:
167
+ """Build artist dict for a line."""
168
+ artist = {}
169
+
170
+ # ID assignment
171
+ if semantic_id and ctx.is_stem:
172
+ artist["id"] = semantic_id
173
+ if scitex_id:
174
+ artist["group_id"] = scitex_id
175
+ elif scitex_id:
176
+ artist["id"] = scitex_id
177
+ elif semantic_id:
178
+ artist["id"] = semantic_id
179
+ elif not label.startswith("_"):
180
+ artist["id"] = label
181
+ else:
182
+ artist["id"] = f"line_{index}"
183
+
184
+ # Semantic layer
185
+ artist["mark"] = "line"
186
+ if semantic_type:
187
+ artist["role"] = semantic_type
188
+
189
+ # Legend
190
+ if not label.startswith("_"):
191
+ artist["label"] = label
192
+ artist["legend_included"] = True
193
+ else:
194
+ artist["legend_included"] = False
195
+
196
+ artist["zorder"] = line.get_zorder()
197
+
198
+ # Backend layer
199
+ backend = {
200
+ "name": "matplotlib",
201
+ "artist_class": type(line).__name__,
202
+ "props": {},
203
+ }
204
+
205
+ color_hex = color_to_hex(line.get_color())
206
+ if color_hex:
207
+ backend["props"]["color"] = color_hex
208
+
209
+ backend["props"]["linestyle"] = line.get_linestyle()
210
+ backend["props"]["linewidth_pt"] = line.get_linewidth()
211
+
212
+ marker = line.get_marker()
213
+ if marker and marker != "None" and marker != "none":
214
+ backend["props"]["marker"] = marker
215
+ backend["props"]["markersize_pt"] = line.get_markersize()
216
+ else:
217
+ backend["props"]["marker"] = None
218
+
219
+ artist["backend"] = backend
220
+
221
+ return artist
222
+
223
+
224
+ def _get_trace_id_for_line(
225
+ ctx: ExtractionContext,
226
+ index: int,
227
+ scitex_id: Optional[str],
228
+ artist_id: str,
229
+ ) -> str:
230
+ """Get trace ID for data_ref."""
231
+ if scitex_id:
232
+ return scitex_id
233
+
234
+ # Try to find from history
235
+ if hasattr(ctx.ax_for_detection, "history"):
236
+ plot_records = []
237
+ for record_id, record in ctx.ax_for_detection.history.items():
238
+ if isinstance(record, tuple) and len(record) >= 2:
239
+ if record[1] == "plot":
240
+ tracking_id = record[0]
241
+ if tracking_id.startswith("ax_"):
242
+ parts = tracking_id.split("_")
243
+ if len(parts) >= 4:
244
+ trace_id = "_".join(parts[3:])
245
+ else:
246
+ trace_id = parts[-1]
247
+ elif tracking_id.startswith("plot_"):
248
+ trace_id = (
249
+ tracking_id[5:] if len(tracking_id) > 5 else str(index)
250
+ )
251
+ else:
252
+ trace_id = tracking_id
253
+ plot_records.append(trace_id)
254
+
255
+ if plot_records and index < len(plot_records):
256
+ return plot_records[index]
257
+
258
+ return artist_id
259
+
260
+
261
+ # EOF
@@ -0,0 +1,247 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: "2026-01-24 (ywatanabe)"
3
+ # File: /home/ywatanabe/proj/scitex-python/src/scitex/plt/utils/_metadata/_artists/_patches.py
4
+
5
+ """
6
+ Patch artist extraction.
7
+
8
+ Handles Rectangle (bar, hist), Wedge (pie), and Polygon patches.
9
+ """
10
+
11
+ from typing import List, Optional
12
+
13
+ from ._base import ExtractionContext, color_to_hex
14
+
15
+
16
+ def extract_patches(ctx: ExtractionContext) -> List[dict]:
17
+ """Extract patch artists from axes."""
18
+ artists = []
19
+
20
+ # Collect rectangles
21
+ rectangles = []
22
+ for i, patch in enumerate(ctx.mpl_ax.patches):
23
+ patch_type = type(patch).__name__
24
+ if patch_type == "Rectangle":
25
+ rectangles.append((i, patch))
26
+ elif patch_type == "Wedge":
27
+ artist = _extract_wedge(ctx, i, patch)
28
+ if artist:
29
+ artists.append(artist)
30
+ elif "Poly" in patch_type:
31
+ artist = _extract_polygon(ctx, i, patch)
32
+ if artist:
33
+ artists.append(artist)
34
+
35
+ # Extract rectangles (bar/hist)
36
+ if rectangles:
37
+ bar_artists = _extract_rectangles(ctx, rectangles)
38
+ artists.extend(bar_artists)
39
+
40
+ return artists
41
+
42
+
43
+ def _extract_rectangles(ctx: ExtractionContext, rectangles: List[tuple]) -> List[dict]:
44
+ """Extract Rectangle patches (bar/hist)."""
45
+ from .._csv import _get_csv_column_names
46
+
47
+ artists = []
48
+ is_bar = ctx.plot_type in ("bar", "barh")
49
+ is_hist = ctx.plot_type == "hist"
50
+
51
+ # Get trace_id from history
52
+ trace_id_for_bars = _get_bar_trace_id(ctx)
53
+
54
+ bar_count = 0
55
+ for rect_idx, (i, patch) in enumerate(rectangles):
56
+ scitex_id = getattr(patch, "_scitex_id", None)
57
+ label = patch.get_label() if hasattr(patch, "get_label") else ""
58
+
59
+ # Skip internal patches for non-bar/hist types
60
+ if not (is_bar or is_hist):
61
+ if (
62
+ ctx.skip_unlabeled
63
+ and not scitex_id
64
+ and (not label or label.startswith("_"))
65
+ ):
66
+ continue
67
+
68
+ artist = {}
69
+
70
+ # Generate unique ID
71
+ base_id = scitex_id or (
72
+ label if label and not label.startswith("_") else trace_id_for_bars or "bar"
73
+ )
74
+ artist["id"] = f"{base_id}_{bar_count}"
75
+ artist["group_id"] = base_id
76
+
77
+ # Semantic layer
78
+ artist["mark"] = "bar"
79
+ if is_hist:
80
+ artist["role"] = "hist_bin"
81
+ else:
82
+ artist["role"] = "bar_body"
83
+
84
+ # Legend
85
+ if label and not label.startswith("_") and bar_count == 0:
86
+ artist["label"] = label
87
+ artist["legend_included"] = True
88
+ else:
89
+ artist["legend_included"] = False
90
+
91
+ artist["zorder"] = patch.get_zorder()
92
+
93
+ # Backend layer
94
+ backend = {
95
+ "name": "matplotlib",
96
+ "artist_class": type(patch).__name__,
97
+ "props": {},
98
+ }
99
+
100
+ try:
101
+ backend["props"]["facecolor"] = color_to_hex(patch.get_facecolor())
102
+ except (ValueError, TypeError):
103
+ pass
104
+
105
+ try:
106
+ backend["props"]["edgecolor"] = color_to_hex(patch.get_edgecolor())
107
+ except (ValueError, TypeError):
108
+ pass
109
+
110
+ try:
111
+ backend["props"]["linewidth_pt"] = patch.get_linewidth()
112
+ except (ValueError, TypeError):
113
+ pass
114
+
115
+ artist["backend"] = backend
116
+
117
+ # Geometry
118
+ try:
119
+ artist["geometry"] = {
120
+ "x": patch.get_x(),
121
+ "y": patch.get_y(),
122
+ "width": patch.get_width(),
123
+ "height": patch.get_height(),
124
+ }
125
+ except (ValueError, TypeError, AttributeError):
126
+ pass
127
+
128
+ # Data reference
129
+ if trace_id_for_bars:
130
+ artist["data_ref"] = _get_csv_column_names(
131
+ trace_id_for_bars, ctx.ax_row, ctx.ax_col
132
+ )
133
+ artist["data_ref"]["row_index"] = bar_count
134
+
135
+ bar_count += 1
136
+ artists.append(artist)
137
+
138
+ return artists
139
+
140
+
141
+ def _get_bar_trace_id(ctx: ExtractionContext) -> Optional[str]:
142
+ """Get trace ID for bar/hist from history."""
143
+ if hasattr(ctx.ax_for_detection, "history"):
144
+ for record in ctx.ax_for_detection.history.values():
145
+ if isinstance(record, tuple) and len(record) >= 2:
146
+ method_name = record[1]
147
+ if method_name in ("bar", "barh", "hist"):
148
+ return record[0]
149
+ return None
150
+
151
+
152
+ def _extract_wedge(ctx: ExtractionContext, index: int, patch) -> dict:
153
+ """Extract Wedge (pie) patch."""
154
+ artist = {}
155
+ scitex_id = getattr(patch, "_scitex_id", None)
156
+ label = patch.get_label() if hasattr(patch, "get_label") else ""
157
+
158
+ if scitex_id:
159
+ artist["id"] = scitex_id
160
+ elif label and not label.startswith("_"):
161
+ artist["id"] = label
162
+ else:
163
+ artist["id"] = f"wedge_{index}"
164
+
165
+ artist["mark"] = "wedge"
166
+ artist["role"] = "pie_slice"
167
+
168
+ if label and not label.startswith("_"):
169
+ artist["label"] = label
170
+ artist["legend_included"] = True
171
+ else:
172
+ artist["legend_included"] = False
173
+
174
+ artist["zorder"] = patch.get_zorder()
175
+
176
+ # Backend layer
177
+ backend = {
178
+ "name": "matplotlib",
179
+ "artist_class": type(patch).__name__,
180
+ "props": {},
181
+ }
182
+
183
+ try:
184
+ backend["props"]["facecolor"] = color_to_hex(patch.get_facecolor())
185
+ except (ValueError, TypeError):
186
+ pass
187
+
188
+ try:
189
+ backend["props"]["edgecolor"] = color_to_hex(patch.get_edgecolor())
190
+ except (ValueError, TypeError):
191
+ pass
192
+
193
+ artist["backend"] = backend
194
+
195
+ # Geometry
196
+ try:
197
+ artist["geometry"] = {
198
+ "theta1": patch.theta1,
199
+ "theta2": patch.theta2,
200
+ "r": patch.r,
201
+ }
202
+ except (ValueError, TypeError, AttributeError):
203
+ pass
204
+
205
+ return artist
206
+
207
+
208
+ def _extract_polygon(ctx: ExtractionContext, index: int, patch) -> dict:
209
+ """Extract Polygon patch."""
210
+ artist = {}
211
+ scitex_id = getattr(patch, "_scitex_id", None)
212
+ label = patch.get_label() if hasattr(patch, "get_label") else ""
213
+
214
+ if scitex_id:
215
+ artist["id"] = scitex_id
216
+ elif label and not label.startswith("_"):
217
+ artist["id"] = label
218
+ else:
219
+ artist["id"] = f"polygon_{index}"
220
+
221
+ artist["mark"] = "polygon"
222
+ artist["legend_included"] = False
223
+ artist["zorder"] = patch.get_zorder()
224
+
225
+ # Backend layer
226
+ backend = {
227
+ "name": "matplotlib",
228
+ "artist_class": type(patch).__name__,
229
+ "props": {},
230
+ }
231
+
232
+ try:
233
+ backend["props"]["facecolor"] = color_to_hex(patch.get_facecolor())
234
+ except (ValueError, TypeError):
235
+ pass
236
+
237
+ try:
238
+ backend["props"]["edgecolor"] = color_to_hex(patch.get_edgecolor())
239
+ except (ValueError, TypeError):
240
+ pass
241
+
242
+ artist["backend"] = backend
243
+
244
+ return artist
245
+
246
+
247
+ # EOF