scitex 2.14.0__py3-none-any.whl → 2.15.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- scitex/__init__.py +47 -0
- scitex/_env_loader.py +156 -0
- scitex/_mcp_resources/__init__.py +37 -0
- scitex/_mcp_resources/_cheatsheet.py +135 -0
- scitex/_mcp_resources/_figrecipe.py +138 -0
- scitex/_mcp_resources/_formats.py +102 -0
- scitex/_mcp_resources/_modules.py +337 -0
- scitex/_mcp_resources/_session.py +149 -0
- scitex/_mcp_tools/__init__.py +4 -0
- scitex/_mcp_tools/audio.py +66 -0
- scitex/_mcp_tools/diagram.py +11 -95
- scitex/_mcp_tools/introspect.py +191 -0
- scitex/_mcp_tools/plt.py +260 -305
- scitex/_mcp_tools/scholar.py +74 -0
- scitex/_mcp_tools/social.py +244 -0
- scitex/_mcp_tools/writer.py +21 -204
- scitex/ai/_gen_ai/_PARAMS.py +10 -7
- scitex/ai/classification/reporters/_SingleClassificationReporter.py +45 -1603
- scitex/ai/classification/reporters/_mixins/__init__.py +36 -0
- scitex/ai/classification/reporters/_mixins/_constants.py +67 -0
- scitex/ai/classification/reporters/_mixins/_cv_summary.py +387 -0
- scitex/ai/classification/reporters/_mixins/_feature_importance.py +119 -0
- scitex/ai/classification/reporters/_mixins/_metrics.py +275 -0
- scitex/ai/classification/reporters/_mixins/_plotting.py +179 -0
- scitex/ai/classification/reporters/_mixins/_reports.py +153 -0
- scitex/ai/classification/reporters/_mixins/_storage.py +160 -0
- scitex/audio/README.md +40 -36
- scitex/audio/__init__.py +127 -59
- scitex/audio/_branding.py +185 -0
- scitex/audio/_mcp/__init__.py +32 -0
- scitex/audio/_mcp/handlers.py +59 -6
- scitex/audio/_mcp/speak_handlers.py +238 -0
- scitex/audio/_relay.py +225 -0
- scitex/audio/engines/elevenlabs_engine.py +6 -1
- scitex/audio/mcp_server.py +228 -75
- scitex/canvas/README.md +1 -1
- scitex/canvas/editor/_dearpygui/__init__.py +25 -0
- scitex/canvas/editor/_dearpygui/_editor.py +147 -0
- scitex/canvas/editor/_dearpygui/_handlers.py +476 -0
- scitex/canvas/editor/_dearpygui/_panels/__init__.py +17 -0
- scitex/canvas/editor/_dearpygui/_panels/_control.py +119 -0
- scitex/canvas/editor/_dearpygui/_panels/_element_controls.py +190 -0
- scitex/canvas/editor/_dearpygui/_panels/_preview.py +43 -0
- scitex/canvas/editor/_dearpygui/_panels/_sections.py +390 -0
- scitex/canvas/editor/_dearpygui/_plotting.py +187 -0
- scitex/canvas/editor/_dearpygui/_rendering.py +504 -0
- scitex/canvas/editor/_dearpygui/_selection.py +295 -0
- scitex/canvas/editor/_dearpygui/_state.py +93 -0
- scitex/canvas/editor/_dearpygui/_utils.py +61 -0
- scitex/canvas/editor/flask_editor/templates/__init__.py +32 -70
- scitex/cli/__init__.py +38 -43
- scitex/cli/audio.py +76 -27
- scitex/cli/capture.py +13 -20
- scitex/cli/introspect.py +443 -0
- scitex/cli/main.py +198 -109
- scitex/cli/mcp.py +60 -34
- scitex/cli/scholar/__init__.py +8 -0
- scitex/cli/scholar/_crossref_scitex.py +296 -0
- scitex/cli/scholar/_fetch.py +25 -3
- scitex/cli/social.py +314 -0
- scitex/cli/writer.py +117 -0
- scitex/config/README.md +1 -1
- scitex/config/__init__.py +16 -2
- scitex/config/_env_registry.py +191 -0
- scitex/diagram/__init__.py +42 -19
- scitex/diagram/mcp_server.py +13 -125
- scitex/introspect/__init__.py +75 -0
- scitex/introspect/_call_graph.py +303 -0
- scitex/introspect/_class_hierarchy.py +163 -0
- scitex/introspect/_core.py +42 -0
- scitex/introspect/_docstring.py +131 -0
- scitex/introspect/_examples.py +113 -0
- scitex/introspect/_imports.py +271 -0
- scitex/introspect/_mcp/__init__.py +37 -0
- scitex/introspect/_mcp/handlers.py +208 -0
- scitex/introspect/_members.py +151 -0
- scitex/introspect/_resolve.py +89 -0
- scitex/introspect/_signature.py +131 -0
- scitex/introspect/_source.py +80 -0
- scitex/introspect/_type_hints.py +172 -0
- scitex/io/bundle/README.md +1 -1
- scitex/mcp_server.py +98 -5
- scitex/plt/__init__.py +248 -550
- scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_wrappers.py +5 -10
- scitex/plt/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
- scitex/plt/gallery/README.md +1 -1
- scitex/plt/utils/_hitmap/__init__.py +82 -0
- scitex/plt/utils/_hitmap/_artist_extraction.py +343 -0
- scitex/plt/utils/_hitmap/_color_application.py +346 -0
- scitex/plt/utils/_hitmap/_color_conversion.py +121 -0
- scitex/plt/utils/_hitmap/_constants.py +40 -0
- scitex/plt/utils/_hitmap/_hitmap_core.py +334 -0
- scitex/plt/utils/_hitmap/_path_extraction.py +357 -0
- scitex/plt/utils/_hitmap/_query.py +113 -0
- scitex/plt/utils/_hitmap.py +46 -1616
- scitex/plt/utils/_metadata/__init__.py +80 -0
- scitex/plt/utils/_metadata/_artists/__init__.py +25 -0
- scitex/plt/utils/_metadata/_artists/_base.py +195 -0
- scitex/plt/utils/_metadata/_artists/_collections.py +356 -0
- scitex/plt/utils/_metadata/_artists/_extract.py +57 -0
- scitex/plt/utils/_metadata/_artists/_images.py +80 -0
- scitex/plt/utils/_metadata/_artists/_lines.py +261 -0
- scitex/plt/utils/_metadata/_artists/_patches.py +247 -0
- scitex/plt/utils/_metadata/_artists/_text.py +106 -0
- scitex/plt/utils/_metadata/_csv.py +416 -0
- scitex/plt/utils/_metadata/_detect.py +225 -0
- scitex/plt/utils/_metadata/_legend.py +127 -0
- scitex/plt/utils/_metadata/_rounding.py +117 -0
- scitex/plt/utils/_metadata/_verification.py +202 -0
- scitex/schema/README.md +1 -1
- scitex/scholar/__init__.py +8 -0
- scitex/scholar/_mcp/crossref_handlers.py +265 -0
- scitex/scholar/core/Scholar.py +63 -1700
- scitex/scholar/core/_mixins/__init__.py +36 -0
- scitex/scholar/core/_mixins/_enrichers.py +270 -0
- scitex/scholar/core/_mixins/_library_handlers.py +100 -0
- scitex/scholar/core/_mixins/_loaders.py +103 -0
- scitex/scholar/core/_mixins/_pdf_download.py +375 -0
- scitex/scholar/core/_mixins/_pipeline.py +312 -0
- scitex/scholar/core/_mixins/_project_handlers.py +125 -0
- scitex/scholar/core/_mixins/_savers.py +69 -0
- scitex/scholar/core/_mixins/_search.py +103 -0
- scitex/scholar/core/_mixins/_services.py +88 -0
- scitex/scholar/core/_mixins/_url_finding.py +105 -0
- scitex/scholar/crossref_scitex.py +367 -0
- scitex/scholar/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
- scitex/scholar/examples/00_run_all.sh +120 -0
- scitex/scholar/jobs/_executors.py +27 -3
- scitex/scholar/pdf_download/ScholarPDFDownloader.py +38 -416
- scitex/scholar/pdf_download/_cli.py +154 -0
- scitex/scholar/pdf_download/strategies/__init__.py +11 -8
- scitex/scholar/pdf_download/strategies/manual_download_fallback.py +80 -3
- scitex/scholar/pipelines/ScholarPipelineBibTeX.py +73 -121
- scitex/scholar/pipelines/ScholarPipelineParallel.py +80 -138
- scitex/scholar/pipelines/ScholarPipelineSingle.py +43 -63
- scitex/scholar/pipelines/_single_steps.py +71 -36
- scitex/scholar/storage/_LibraryManager.py +97 -1695
- scitex/scholar/storage/_mixins/__init__.py +30 -0
- scitex/scholar/storage/_mixins/_bibtex_handlers.py +128 -0
- scitex/scholar/storage/_mixins/_library_operations.py +218 -0
- scitex/scholar/storage/_mixins/_metadata_conversion.py +226 -0
- scitex/scholar/storage/_mixins/_paper_saving.py +456 -0
- scitex/scholar/storage/_mixins/_resolution.py +376 -0
- scitex/scholar/storage/_mixins/_storage_helpers.py +121 -0
- scitex/scholar/storage/_mixins/_symlink_handlers.py +226 -0
- scitex/scholar/url_finder/.tmp/open_url/KNOWN_RESOLVERS.py +462 -0
- scitex/scholar/url_finder/.tmp/open_url/README.md +223 -0
- scitex/scholar/url_finder/.tmp/open_url/_DOIToURLResolver.py +694 -0
- scitex/scholar/url_finder/.tmp/open_url/_OpenURLResolver.py +1160 -0
- scitex/scholar/url_finder/.tmp/open_url/_ResolverLinkFinder.py +344 -0
- scitex/scholar/url_finder/.tmp/open_url/__init__.py +24 -0
- scitex/security/README.md +3 -3
- scitex/session/README.md +1 -1
- scitex/sh/README.md +1 -1
- scitex/social/__init__.py +153 -0
- scitex/social/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
- scitex/template/README.md +1 -1
- scitex/template/clone_writer_directory.py +5 -5
- scitex/writer/README.md +1 -1
- scitex/writer/_mcp/handlers.py +11 -744
- scitex/writer/_mcp/tool_schemas.py +5 -335
- scitex-2.15.1.dist-info/METADATA +648 -0
- {scitex-2.14.0.dist-info → scitex-2.15.1.dist-info}/RECORD +166 -111
- scitex/canvas/editor/flask_editor/templates/_scripts.py +0 -4933
- scitex/canvas/editor/flask_editor/templates/_styles.py +0 -1658
- scitex/dev/plt/data/mpl/PLOTTING_FUNCTIONS.yaml +0 -90
- scitex/dev/plt/data/mpl/PLOTTING_SIGNATURES.yaml +0 -1571
- scitex/dev/plt/data/mpl/PLOTTING_SIGNATURES_DETAILED.yaml +0 -6262
- scitex/dev/plt/data/mpl/SIGNATURES_FLATTENED.yaml +0 -1274
- scitex/dev/plt/data/mpl/dir_ax.txt +0 -459
- scitex/diagram/_compile.py +0 -312
- scitex/diagram/_diagram.py +0 -355
- scitex/diagram/_mcp/__init__.py +0 -4
- scitex/diagram/_mcp/handlers.py +0 -400
- scitex/diagram/_mcp/tool_schemas.py +0 -157
- scitex/diagram/_presets.py +0 -173
- scitex/diagram/_schema.py +0 -182
- scitex/diagram/_split.py +0 -278
- scitex/plt/_mcp/__init__.py +0 -4
- scitex/plt/_mcp/_handlers_annotation.py +0 -102
- scitex/plt/_mcp/_handlers_figure.py +0 -195
- scitex/plt/_mcp/_handlers_plot.py +0 -252
- scitex/plt/_mcp/_handlers_style.py +0 -219
- scitex/plt/_mcp/handlers.py +0 -74
- scitex/plt/_mcp/tool_schemas.py +0 -497
- scitex/plt/mcp_server.py +0 -231
- scitex/scholar/data/.gitkeep +0 -0
- scitex/scholar/data/README.md +0 -44
- scitex/scholar/data/bib_files/bibliography.bib +0 -1952
- scitex/scholar/data/bib_files/neurovista.bib +0 -277
- scitex/scholar/data/bib_files/neurovista_enriched.bib +0 -441
- scitex/scholar/data/bib_files/neurovista_enriched_enriched.bib +0 -441
- scitex/scholar/data/bib_files/neurovista_processed.bib +0 -338
- scitex/scholar/data/bib_files/openaccess.bib +0 -89
- scitex/scholar/data/bib_files/pac-seizure_prediction_enriched.bib +0 -2178
- scitex/scholar/data/bib_files/pac.bib +0 -698
- scitex/scholar/data/bib_files/pac_enriched.bib +0 -1061
- scitex/scholar/data/bib_files/pac_processed.bib +0 -0
- scitex/scholar/data/bib_files/pac_titles.txt +0 -75
- scitex/scholar/data/bib_files/paywalled.bib +0 -98
- scitex/scholar/data/bib_files/related-papers-by-coauthors.bib +0 -58
- scitex/scholar/data/bib_files/related-papers-by-coauthors_enriched.bib +0 -87
- scitex/scholar/data/bib_files/seizure_prediction.bib +0 -694
- scitex/scholar/data/bib_files/seizure_prediction_processed.bib +0 -0
- scitex/scholar/data/bib_files/test_complete_enriched.bib +0 -437
- scitex/scholar/data/bib_files/test_final_enriched.bib +0 -437
- scitex/scholar/data/bib_files/test_seizure.bib +0 -46
- scitex/scholar/data/impact_factor/JCR_IF_2022.xlsx +0 -0
- scitex/scholar/data/impact_factor/JCR_IF_2024.db +0 -0
- scitex/scholar/data/impact_factor/JCR_IF_2024.xlsx +0 -0
- scitex/scholar/data/impact_factor/JCR_IF_2024_v01.db +0 -0
- scitex/scholar/data/impact_factor.db +0 -0
- scitex/scholar/examples/SUGGESTIONS.md +0 -865
- scitex/scholar/examples/dev.py +0 -38
- scitex-2.14.0.dist-info/METADATA +0 -1238
- {scitex-2.14.0.dist-info → scitex-2.15.1.dist-info}/WHEEL +0 -0
- {scitex-2.14.0.dist-info → scitex-2.15.1.dist-info}/entry_points.txt +0 -0
- {scitex-2.14.0.dist-info → scitex-2.15.1.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
|