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
scitex/plt/__init__.py
CHANGED
|
@@ -1,46 +1,200 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
# Timestamp: "
|
|
2
|
+
# Timestamp: "2026-01-19 (ywatanabe)"
|
|
3
3
|
# File: /home/ywatanabe/proj/scitex-code/src/scitex/plt/__init__.py
|
|
4
4
|
# ----------------------------------------
|
|
5
|
-
import os
|
|
6
|
-
|
|
7
|
-
__FILE__ = __file__
|
|
8
|
-
__DIR__ = os.path.dirname(__FILE__)
|
|
9
|
-
# ----------------------------------------
|
|
10
5
|
"""
|
|
11
|
-
SciTeX plt module - Publication-quality plotting.
|
|
6
|
+
SciTeX plt module - Publication-quality plotting via figrecipe.
|
|
12
7
|
|
|
8
|
+
This module provides a thin wrapper around figrecipe with scitex branding.
|
|
13
9
|
Simply importing this module automatically configures matplotlib with
|
|
14
|
-
SciTeX publication defaults
|
|
10
|
+
SciTeX publication defaults.
|
|
11
|
+
|
|
12
|
+
Usage
|
|
13
|
+
-----
|
|
14
|
+
>>> import scitex.plt as plt
|
|
15
|
+
>>> fig, ax = plt.subplots()
|
|
16
|
+
>>> ax.plot([1, 2, 3], [1, 4, 9])
|
|
17
|
+
>>> plt.save(fig, "figure.png")
|
|
18
|
+
|
|
19
|
+
Style Management
|
|
20
|
+
----------------
|
|
21
|
+
>>> plt.load_style("SCITEX") # Load publication style
|
|
22
|
+
>>> plt.STYLE # Access current style configuration
|
|
23
|
+
>>> plt.list_presets() # Show available presets
|
|
24
|
+
|
|
25
|
+
The module delegates to figrecipe for:
|
|
26
|
+
- Recording and reproducing figures
|
|
27
|
+
- Style management (mm-based layouts)
|
|
28
|
+
- Figure composition
|
|
29
|
+
- Graph visualization
|
|
30
|
+
|
|
31
|
+
SciTeX-specific features (kept locally):
|
|
32
|
+
- AxisWrapper/FigWrapper compatibility
|
|
33
|
+
- Color palettes (scitex.plt.color)
|
|
34
|
+
- Gallery utilities (scitex.plt.gallery)
|
|
35
|
+
"""
|
|
15
36
|
|
|
16
|
-
|
|
17
|
-
fig, ax = splt.subplots()
|
|
18
|
-
ax.plot([1, 2, 3], [1, 4, 9])
|
|
19
|
-
fig.savefig("figure.png")
|
|
37
|
+
import os
|
|
20
38
|
|
|
21
|
-
|
|
39
|
+
# ============================================================================
|
|
40
|
+
# Set branding environment variables BEFORE importing figrecipe
|
|
41
|
+
# This enables automatic docstring replacement: figrecipe -> scitex.plt, fr -> plt
|
|
42
|
+
# ============================================================================
|
|
43
|
+
os.environ.setdefault("FIGRECIPE_BRAND", "scitex.plt")
|
|
44
|
+
os.environ.setdefault("FIGRECIPE_ALIAS", "plt")
|
|
22
45
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
46
|
+
# ============================================================================
|
|
47
|
+
# Now import figrecipe (branding will be applied)
|
|
48
|
+
# ============================================================================
|
|
49
|
+
try:
|
|
50
|
+
import figrecipe as _fr
|
|
51
|
+
|
|
52
|
+
_FIGRECIPE_AVAILABLE = True
|
|
53
|
+
except ImportError:
|
|
54
|
+
_FIGRECIPE_AVAILABLE = False
|
|
55
|
+
_fr = None
|
|
28
56
|
|
|
57
|
+
# Standard library and matplotlib imports
|
|
29
58
|
import matplotlib as mpl
|
|
30
59
|
import matplotlib.font_manager as fm
|
|
31
|
-
import matplotlib.pyplot as
|
|
60
|
+
import matplotlib.pyplot as _plt
|
|
32
61
|
|
|
33
62
|
from scitex import logging as _logging
|
|
34
63
|
|
|
35
64
|
_logger = _logging.getLogger(__name__)
|
|
36
65
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
66
|
+
__FILE__ = __file__
|
|
67
|
+
__DIR__ = os.path.dirname(__FILE__)
|
|
68
|
+
|
|
69
|
+
# ============================================================================
|
|
70
|
+
# Re-export figrecipe public API with scitex branding
|
|
71
|
+
# ============================================================================
|
|
72
|
+
if _FIGRECIPE_AVAILABLE:
|
|
73
|
+
# Core functions
|
|
74
|
+
from figrecipe import (
|
|
75
|
+
STYLE,
|
|
76
|
+
align_panels,
|
|
77
|
+
apply_style,
|
|
78
|
+
compose,
|
|
79
|
+
crop,
|
|
80
|
+
distribute_panels,
|
|
81
|
+
edit,
|
|
82
|
+
enable_svg,
|
|
83
|
+
extract_data,
|
|
84
|
+
get_graph_preset,
|
|
85
|
+
info,
|
|
86
|
+
list_graph_presets,
|
|
87
|
+
list_presets,
|
|
88
|
+
load_style,
|
|
89
|
+
register_graph_preset,
|
|
90
|
+
reproduce,
|
|
91
|
+
save,
|
|
92
|
+
smart_align,
|
|
93
|
+
sns,
|
|
94
|
+
subplots,
|
|
95
|
+
unload_style,
|
|
96
|
+
validate,
|
|
97
|
+
)
|
|
98
|
+
from figrecipe import (
|
|
99
|
+
__version__ as _figrecipe_version,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# Also export load as alias for reproduce
|
|
103
|
+
load = reproduce
|
|
104
|
+
else:
|
|
105
|
+
# Provide stub versions when figrecipe is not available
|
|
106
|
+
_figrecipe_version = "0.0.0"
|
|
107
|
+
|
|
108
|
+
def _not_available(*args, **kwargs):
|
|
109
|
+
raise ImportError(
|
|
110
|
+
"figrecipe is required for this feature. Install with: pip install figrecipe"
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
STYLE = None
|
|
114
|
+
load_style = _not_available
|
|
115
|
+
unload_style = _not_available
|
|
116
|
+
list_presets = _not_available
|
|
117
|
+
apply_style = _not_available
|
|
118
|
+
subplots = _not_available
|
|
119
|
+
save = _not_available
|
|
120
|
+
reproduce = _not_available
|
|
121
|
+
load = _not_available
|
|
122
|
+
crop = _not_available
|
|
123
|
+
validate = _not_available
|
|
124
|
+
extract_data = _not_available
|
|
125
|
+
info = _not_available
|
|
126
|
+
edit = _not_available
|
|
127
|
+
compose = _not_available
|
|
128
|
+
align_panels = _not_available
|
|
129
|
+
distribute_panels = _not_available
|
|
130
|
+
smart_align = _not_available
|
|
131
|
+
sns = None
|
|
132
|
+
enable_svg = _not_available
|
|
133
|
+
get_graph_preset = _not_available
|
|
134
|
+
list_graph_presets = _not_available
|
|
135
|
+
register_graph_preset = _not_available
|
|
136
|
+
|
|
137
|
+
# ============================================================================
|
|
138
|
+
# Local scitex submodules (kept for compatibility)
|
|
139
|
+
# ============================================================================
|
|
140
|
+
try:
|
|
141
|
+
from ._tpl import termplot
|
|
142
|
+
except ImportError:
|
|
143
|
+
termplot = None
|
|
144
|
+
|
|
145
|
+
# Backward compatibility: expose styles submodule (deprecated, use figrecipe)
|
|
146
|
+
from . import ax, color, gallery, styles, utils
|
|
147
|
+
|
|
148
|
+
# Import draw_graph from figrecipe integration (handles AxisWrapper)
|
|
149
|
+
from ._figrecipe_integration import draw_graph
|
|
150
|
+
from .styles import presets
|
|
151
|
+
|
|
152
|
+
# ============================================================================
|
|
153
|
+
# Auto-configure matplotlib with SciTeX defaults on import
|
|
154
|
+
# ============================================================================
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _register_arial_fonts():
|
|
158
|
+
"""Register Arial fonts if available."""
|
|
159
|
+
try:
|
|
160
|
+
fm.findfont("Arial", fallback_to_default=False)
|
|
161
|
+
return True
|
|
162
|
+
except Exception:
|
|
163
|
+
# Search for Arial font files and register them
|
|
164
|
+
arial_paths = [
|
|
165
|
+
f
|
|
166
|
+
for f in fm.findSystemFonts()
|
|
167
|
+
if os.path.basename(f).lower().startswith("arial")
|
|
168
|
+
]
|
|
169
|
+
|
|
170
|
+
if arial_paths:
|
|
171
|
+
for path in arial_paths:
|
|
172
|
+
try:
|
|
173
|
+
fm.fontManager.addfont(path)
|
|
174
|
+
except Exception:
|
|
175
|
+
pass
|
|
176
|
+
|
|
177
|
+
# Verify Arial is now available
|
|
178
|
+
try:
|
|
179
|
+
fm.findfont("Arial", fallback_to_default=False)
|
|
180
|
+
return True
|
|
181
|
+
except Exception:
|
|
182
|
+
pass
|
|
183
|
+
return False
|
|
40
184
|
|
|
41
185
|
|
|
42
186
|
def _auto_configure_mpl():
|
|
43
187
|
"""Apply SciTeX style configuration automatically on import."""
|
|
188
|
+
# Try to use figrecipe's style system first
|
|
189
|
+
if _FIGRECIPE_AVAILABLE:
|
|
190
|
+
try:
|
|
191
|
+
# Load SCITEX style preset from figrecipe
|
|
192
|
+
load_style("SCITEX")
|
|
193
|
+
return
|
|
194
|
+
except Exception:
|
|
195
|
+
pass
|
|
196
|
+
|
|
197
|
+
# Fallback: use local style loader
|
|
44
198
|
from .styles import resolve_style_value
|
|
45
199
|
|
|
46
200
|
# mm to pt conversion factor
|
|
@@ -111,32 +265,8 @@ def _auto_configure_mpl():
|
|
|
111
265
|
mpl.rcParams.update(mpl_config)
|
|
112
266
|
|
|
113
267
|
|
|
114
|
-
# Register Arial fonts eagerly
|
|
115
|
-
_arial_enabled =
|
|
116
|
-
try:
|
|
117
|
-
fm.findfont("Arial", fallback_to_default=False)
|
|
118
|
-
_arial_enabled = True
|
|
119
|
-
except Exception:
|
|
120
|
-
# Search for Arial font files and register them
|
|
121
|
-
arial_paths = [
|
|
122
|
-
f
|
|
123
|
-
for f in fm.findSystemFonts()
|
|
124
|
-
if os.path.basename(f).lower().startswith("arial")
|
|
125
|
-
]
|
|
126
|
-
|
|
127
|
-
if arial_paths:
|
|
128
|
-
for path in arial_paths:
|
|
129
|
-
try:
|
|
130
|
-
fm.fontManager.addfont(path)
|
|
131
|
-
except Exception:
|
|
132
|
-
pass
|
|
133
|
-
|
|
134
|
-
# Verify Arial is now available
|
|
135
|
-
try:
|
|
136
|
-
fm.findfont("Arial", fallback_to_default=False)
|
|
137
|
-
_arial_enabled = True
|
|
138
|
-
except Exception:
|
|
139
|
-
pass
|
|
268
|
+
# Register Arial fonts eagerly
|
|
269
|
+
_arial_enabled = _register_arial_fonts()
|
|
140
270
|
|
|
141
271
|
# Configure font family
|
|
142
272
|
if _arial_enabled:
|
|
@@ -165,458 +295,35 @@ _auto_configure_mpl()
|
|
|
165
295
|
|
|
166
296
|
# Set up color cycle from scitex colors
|
|
167
297
|
try:
|
|
168
|
-
from . import color as _color_module
|
|
169
|
-
|
|
170
298
|
_rgba_norm_cycle = {
|
|
171
|
-
k: tuple(
|
|
172
|
-
for k, v in
|
|
299
|
+
k: tuple(color.update_alpha(v, 1.0))
|
|
300
|
+
for k, v in color.PARAMS.get("RGBA_NORM_FOR_CYCLE", {}).items()
|
|
173
301
|
}
|
|
174
302
|
if _rgba_norm_cycle:
|
|
175
|
-
mpl.rcParams["axes.prop_cycle"] =
|
|
303
|
+
mpl.rcParams["axes.prop_cycle"] = _plt.cycler(
|
|
176
304
|
color=list(_rgba_norm_cycle.values())
|
|
177
305
|
)
|
|
178
306
|
except Exception:
|
|
179
307
|
pass # Use matplotlib default colors if color module fails
|
|
180
308
|
|
|
181
|
-
try:
|
|
182
|
-
from ._tpl import termplot
|
|
183
|
-
except ImportError:
|
|
184
|
-
termplot = None
|
|
185
|
-
from . import ax, color, gallery, styles, utils
|
|
186
|
-
|
|
187
|
-
# Figrecipe integration (graph visualization, editor)
|
|
188
|
-
from ._figrecipe_integration import draw_graph, edit
|
|
189
|
-
from .styles import presets
|
|
190
|
-
|
|
191
|
-
# Lazy import for subplots to avoid circular dependencies
|
|
192
|
-
# Note: Use names that don't conflict with submodule names like _subplots
|
|
193
|
-
_subplots_func_cached = None
|
|
194
|
-
_figure_func_cached = None
|
|
195
|
-
_crop_func_cached = None
|
|
196
|
-
|
|
197
309
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
if _subplots_func_cached is None:
|
|
202
|
-
from ._subplots._SubplotsWrapper import subplots as _subplots_impl
|
|
203
|
-
|
|
204
|
-
_subplots_func_cached = _subplots_impl
|
|
205
|
-
return _subplots_func_cached(*args, **kwargs)
|
|
310
|
+
# ============================================================================
|
|
311
|
+
# SciTeX-specific wrapper functions (for AxisWrapper/FigWrapper compatibility)
|
|
312
|
+
# ============================================================================
|
|
206
313
|
|
|
207
314
|
|
|
208
315
|
def figure(*args, **kwargs):
|
|
209
|
-
"""
|
|
210
|
-
global _figure_func_cached
|
|
211
|
-
if _figure_func_cached is None:
|
|
212
|
-
import matplotlib.pyplot as plt
|
|
213
|
-
|
|
214
|
-
from ._subplots._FigWrapper import FigWrapper
|
|
215
|
-
|
|
216
|
-
def _figure_impl(*args, **kwargs):
|
|
217
|
-
fig_mpl = plt.figure(*args, **kwargs)
|
|
218
|
-
return FigWrapper(fig_mpl)
|
|
219
|
-
|
|
220
|
-
_figure_func_cached = _figure_impl
|
|
221
|
-
return _figure_func_cached(*args, **kwargs)
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
def crop(input_path, output_path=None, margin=12, overwrite=False, verbose=False):
|
|
225
|
-
"""
|
|
226
|
-
Auto-crop a figure to its content area.
|
|
227
|
-
|
|
228
|
-
This function automatically detects the content area of a saved figure
|
|
229
|
-
and crops it, removing excess whitespace. Designed for publication figures
|
|
230
|
-
created with large margins.
|
|
231
|
-
|
|
232
|
-
Parameters
|
|
233
|
-
----------
|
|
234
|
-
input_path : str
|
|
235
|
-
Path to the input image
|
|
236
|
-
output_path : str, optional
|
|
237
|
-
Path to save cropped image. If None and overwrite=True, overwrites input.
|
|
238
|
-
If None and overwrite=False, adds '_cropped' suffix.
|
|
239
|
-
margin : int, optional
|
|
240
|
-
Margin in pixels around content (default: 12, ~1mm at 300 DPI)
|
|
241
|
-
overwrite : bool, optional
|
|
242
|
-
Overwrite input file (default: False)
|
|
243
|
-
verbose : bool, optional
|
|
244
|
-
Print detailed information (default: False)
|
|
245
|
-
|
|
246
|
-
Returns
|
|
247
|
-
-------
|
|
248
|
-
str
|
|
249
|
-
Path to the saved cropped image
|
|
250
|
-
|
|
251
|
-
Examples
|
|
252
|
-
--------
|
|
253
|
-
>>> fig, ax = stx.plt.subplots(**stx.plt.presets.SCITEX_STYLE)
|
|
254
|
-
>>> ax.plot([1, 2, 3], [1, 2, 3])
|
|
255
|
-
>>> stx.io.save(fig, "figure.png")
|
|
256
|
-
>>> stx.plt.crop("figure.png", "figure_cropped.png") # 1mm margin
|
|
257
|
-
"""
|
|
258
|
-
global _crop_func_cached
|
|
259
|
-
if _crop_func_cached is None:
|
|
260
|
-
from .utils._crop import crop as _crop_impl
|
|
261
|
-
|
|
262
|
-
_crop_func_cached = _crop_impl
|
|
263
|
-
return _crop_func_cached(input_path, output_path, margin, overwrite, verbose)
|
|
316
|
+
"""Create a figure that returns a FigWrapper.
|
|
264
317
|
|
|
318
|
+
This is the scitex-specific figure function that creates FigWrapper
|
|
319
|
+
objects for compatibility with scitex.plt.ax utilities.
|
|
265
320
|
|
|
266
|
-
|
|
321
|
+
For figrecipe-style recording figures, use subplots() instead.
|
|
267
322
|
"""
|
|
268
|
-
|
|
323
|
+
from ._subplots._FigWrapper import FigWrapper
|
|
269
324
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
path : str or Path
|
|
273
|
-
Path to JSON file, PNG file, or CSV file.
|
|
274
|
-
Will auto-detect sibling files in same directory or organized subdirectories.
|
|
275
|
-
apply_manual : bool, optional
|
|
276
|
-
If True, apply .manual.json overrides if exists (default: True)
|
|
277
|
-
|
|
278
|
-
Returns
|
|
279
|
-
-------
|
|
280
|
-
tuple
|
|
281
|
-
(fig, axes) where fig is FigWrapper and axes is AxisWrapper or array
|
|
282
|
-
|
|
283
|
-
Raises
|
|
284
|
-
------
|
|
285
|
-
FileNotFoundError
|
|
286
|
-
If required JSON file is not found
|
|
287
|
-
ValueError
|
|
288
|
-
If manual.json hash doesn't match (stale manual edits)
|
|
289
|
-
|
|
290
|
-
Examples
|
|
291
|
-
--------
|
|
292
|
-
>>> # Load from JSON (sibling pattern)
|
|
293
|
-
>>> fig, axes = stx.plt.load("output/figure.json")
|
|
294
|
-
|
|
295
|
-
>>> # Load from PNG (finds sibling JSON + CSV)
|
|
296
|
-
>>> fig, axes = stx.plt.load("output/figure.png")
|
|
297
|
-
|
|
298
|
-
>>> # Load from organized directory pattern
|
|
299
|
-
>>> fig, axes = stx.plt.load("output/json/figure.json")
|
|
300
|
-
|
|
301
|
-
>>> # Skip manual overrides
|
|
302
|
-
>>> fig, axes = stx.plt.load("figure.json", apply_manual=False)
|
|
303
|
-
|
|
304
|
-
Notes
|
|
305
|
-
-----
|
|
306
|
-
Supports two directory patterns:
|
|
307
|
-
|
|
308
|
-
Pattern 1 (flat/sibling):
|
|
309
|
-
output/figure.png
|
|
310
|
-
output/figure.json
|
|
311
|
-
output/figure.csv
|
|
312
|
-
|
|
313
|
-
Pattern 2 (organized):
|
|
314
|
-
output/png/figure.png
|
|
315
|
-
output/json/figure.json
|
|
316
|
-
output/csv/figure.csv
|
|
317
|
-
|
|
318
|
-
Manual overrides (.manual.json) are applied if:
|
|
319
|
-
- apply_manual=True
|
|
320
|
-
- figure.manual.json exists alongside figure.json
|
|
321
|
-
- Hash validation passes (warns if stale)
|
|
322
|
-
"""
|
|
323
|
-
import hashlib
|
|
324
|
-
from pathlib import Path
|
|
325
|
-
|
|
326
|
-
import scitex as stx
|
|
327
|
-
|
|
328
|
-
path = Path(path)
|
|
329
|
-
|
|
330
|
-
# Resolve JSON path from any input (png, csv, or json)
|
|
331
|
-
json_path, csv_path = _resolve_figure_paths(path)
|
|
332
|
-
|
|
333
|
-
if not json_path.exists():
|
|
334
|
-
raise FileNotFoundError(f"JSON file not found: {json_path}")
|
|
335
|
-
|
|
336
|
-
# Load JSON metadata
|
|
337
|
-
metadata = stx.io.load(json_path)
|
|
338
|
-
|
|
339
|
-
# Load CSV data if exists
|
|
340
|
-
csv_data = None
|
|
341
|
-
if csv_path and csv_path.exists():
|
|
342
|
-
csv_data = stx.io.load(csv_path)
|
|
343
|
-
|
|
344
|
-
# Check for manual overrides
|
|
345
|
-
manual_path = json_path.with_suffix(".manual.json")
|
|
346
|
-
manual_overrides = None
|
|
347
|
-
if apply_manual and manual_path.exists():
|
|
348
|
-
manual_data = stx.io.load(manual_path)
|
|
349
|
-
|
|
350
|
-
# Validate hash
|
|
351
|
-
if "base_hash" in manual_data:
|
|
352
|
-
current_hash = _compute_file_hash(json_path)
|
|
353
|
-
if manual_data["base_hash"] != current_hash:
|
|
354
|
-
_logger.warning(
|
|
355
|
-
f"Manual overrides may be stale: base data changed since manual edits.\n"
|
|
356
|
-
f" Expected hash: {manual_data['base_hash'][:16]}...\n"
|
|
357
|
-
f" Current hash: {current_hash[:16]}...\n"
|
|
358
|
-
f" Review: {manual_path}"
|
|
359
|
-
)
|
|
360
|
-
manual_overrides = manual_data.get("overrides", {})
|
|
361
|
-
|
|
362
|
-
# Reconstruct figure
|
|
363
|
-
fig, axes = _reconstruct_figure(metadata, csv_data, manual_overrides)
|
|
364
|
-
|
|
365
|
-
return fig, axes
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
def _resolve_figure_paths(path):
|
|
369
|
-
"""
|
|
370
|
-
Resolve JSON and CSV paths from any input file path.
|
|
371
|
-
|
|
372
|
-
Supports both flat (sibling) and organized (subdirectory) patterns.
|
|
373
|
-
"""
|
|
374
|
-
from pathlib import Path
|
|
375
|
-
|
|
376
|
-
path = Path(path)
|
|
377
|
-
stem = path.stem
|
|
378
|
-
suffix = path.suffix.lower()
|
|
379
|
-
parent = path.parent
|
|
380
|
-
|
|
381
|
-
# Determine base name (remove .manual if present)
|
|
382
|
-
if stem.endswith(".manual"):
|
|
383
|
-
stem = stem[:-7]
|
|
384
|
-
|
|
385
|
-
json_path = None
|
|
386
|
-
csv_path = None
|
|
387
|
-
|
|
388
|
-
if suffix == ".json":
|
|
389
|
-
json_path = path
|
|
390
|
-
# Try sibling CSV first
|
|
391
|
-
csv_sibling = parent / f"{stem}.csv"
|
|
392
|
-
if csv_sibling.exists():
|
|
393
|
-
csv_path = csv_sibling
|
|
394
|
-
# Try organized pattern (../csv/)
|
|
395
|
-
elif parent.name == "json":
|
|
396
|
-
csv_organized = parent.parent / "csv" / f"{stem}.csv"
|
|
397
|
-
if csv_organized.exists():
|
|
398
|
-
csv_path = csv_organized
|
|
399
|
-
|
|
400
|
-
elif suffix in (".png", ".jpg", ".jpeg", ".pdf", ".svg"):
|
|
401
|
-
# Look for sibling JSON
|
|
402
|
-
json_sibling = parent / f"{stem}.json"
|
|
403
|
-
csv_sibling = parent / f"{stem}.csv"
|
|
404
|
-
|
|
405
|
-
if json_sibling.exists():
|
|
406
|
-
json_path = json_sibling
|
|
407
|
-
if csv_sibling.exists():
|
|
408
|
-
csv_path = csv_sibling
|
|
409
|
-
# Try organized pattern (parent has png/, look for json/)
|
|
410
|
-
elif parent.name in ("png", "jpg", "jpeg", "pdf", "svg"):
|
|
411
|
-
json_organized = parent.parent / "json" / f"{stem}.json"
|
|
412
|
-
csv_organized = parent.parent / "csv" / f"{stem}.csv"
|
|
413
|
-
if json_organized.exists():
|
|
414
|
-
json_path = json_organized
|
|
415
|
-
if csv_organized.exists():
|
|
416
|
-
csv_path = csv_organized
|
|
417
|
-
|
|
418
|
-
elif suffix == ".csv":
|
|
419
|
-
csv_path = path
|
|
420
|
-
# Try sibling JSON
|
|
421
|
-
json_sibling = parent / f"{stem}.json"
|
|
422
|
-
if json_sibling.exists():
|
|
423
|
-
json_path = json_sibling
|
|
424
|
-
# Try organized pattern (../json/)
|
|
425
|
-
elif parent.name == "csv":
|
|
426
|
-
json_organized = parent.parent / "json" / f"{stem}.json"
|
|
427
|
-
if json_organized.exists():
|
|
428
|
-
json_path = json_organized
|
|
429
|
-
|
|
430
|
-
# Fallback: assume it's the JSON path
|
|
431
|
-
if json_path is None:
|
|
432
|
-
json_path = path if suffix == ".json" else path.with_suffix(".json")
|
|
433
|
-
|
|
434
|
-
return json_path, csv_path
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
def _compute_file_hash(path):
|
|
438
|
-
"""Compute SHA256 hash of a file."""
|
|
439
|
-
import hashlib
|
|
440
|
-
from pathlib import Path
|
|
441
|
-
|
|
442
|
-
path = Path(path)
|
|
443
|
-
sha256 = hashlib.sha256()
|
|
444
|
-
with open(path, "rb") as f:
|
|
445
|
-
for chunk in iter(lambda: f.read(8192), b""):
|
|
446
|
-
sha256.update(chunk)
|
|
447
|
-
return f"sha256:{sha256.hexdigest()}"
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
def _reconstruct_figure(metadata, csv_data, manual_overrides=None):
|
|
451
|
-
"""
|
|
452
|
-
Reconstruct figure from metadata and CSV data.
|
|
453
|
-
|
|
454
|
-
Parameters
|
|
455
|
-
----------
|
|
456
|
-
metadata : dict
|
|
457
|
-
JSON metadata from stx.io.save()
|
|
458
|
-
csv_data : DataFrame or None
|
|
459
|
-
CSV data with plot values
|
|
460
|
-
manual_overrides : dict or None
|
|
461
|
-
Manual style/annotation overrides
|
|
462
|
-
|
|
463
|
-
Returns
|
|
464
|
-
-------
|
|
465
|
-
tuple
|
|
466
|
-
(fig, axes)
|
|
467
|
-
"""
|
|
468
|
-
import numpy as np
|
|
469
|
-
|
|
470
|
-
# Extract dimensions from metadata
|
|
471
|
-
dims = metadata.get("dimensions", {})
|
|
472
|
-
fig_size_mm = dims.get("figure_size_mm", [80, 68])
|
|
473
|
-
dpi = dims.get("dpi", 300)
|
|
474
|
-
|
|
475
|
-
# Get style from metadata
|
|
476
|
-
scitex_meta = metadata.get("scitex", {})
|
|
477
|
-
style_mm = scitex_meta.get("style_mm", {})
|
|
478
|
-
|
|
479
|
-
# Create figure with same dimensions
|
|
480
|
-
fig, axes = subplots(
|
|
481
|
-
axes_width_mm=style_mm.get(
|
|
482
|
-
"axes_width_mm", dims.get("axes_size_mm", [40, 28])[0]
|
|
483
|
-
),
|
|
484
|
-
axes_height_mm=style_mm.get(
|
|
485
|
-
"axes_height_mm", dims.get("axes_size_mm", [40, 28])[1]
|
|
486
|
-
),
|
|
487
|
-
dpi=dpi,
|
|
488
|
-
)
|
|
489
|
-
|
|
490
|
-
# Handle single vs multiple axes
|
|
491
|
-
ax = axes if not hasattr(axes, "flat") else list(axes.flat)[0]
|
|
492
|
-
|
|
493
|
-
# Set axis labels from metadata
|
|
494
|
-
axes_meta = metadata.get("axes", {})
|
|
495
|
-
x_meta = axes_meta.get("x", {})
|
|
496
|
-
y_meta = axes_meta.get("y", {})
|
|
497
|
-
|
|
498
|
-
xlabel = x_meta.get("label", "")
|
|
499
|
-
ylabel = y_meta.get("label", "")
|
|
500
|
-
x_unit = x_meta.get("unit", "")
|
|
501
|
-
y_unit = y_meta.get("unit", "")
|
|
502
|
-
|
|
503
|
-
if xlabel:
|
|
504
|
-
full_xlabel = f"{xlabel} [{x_unit}]" if x_unit else xlabel
|
|
505
|
-
ax.set_xlabel(full_xlabel)
|
|
506
|
-
if ylabel:
|
|
507
|
-
full_ylabel = f"{ylabel} [{y_unit}]" if y_unit else ylabel
|
|
508
|
-
ax.set_ylabel(full_ylabel)
|
|
509
|
-
|
|
510
|
-
# Reconstruct plots from CSV data
|
|
511
|
-
if csv_data is not None and not csv_data.empty:
|
|
512
|
-
_reconstruct_plots_from_csv(ax, csv_data, metadata)
|
|
513
|
-
|
|
514
|
-
# Apply manual overrides
|
|
515
|
-
if manual_overrides:
|
|
516
|
-
_apply_manual_overrides(fig, axes, manual_overrides)
|
|
517
|
-
|
|
518
|
-
return fig, axes
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
def _reconstruct_plots_from_csv(ax, csv_data, metadata):
|
|
522
|
-
"""
|
|
523
|
-
Reconstruct plot elements from CSV data.
|
|
524
|
-
|
|
525
|
-
CSV columns follow pattern: ax_00_<type>_<name>
|
|
526
|
-
"""
|
|
527
|
-
import numpy as np
|
|
528
|
-
import pandas as pd
|
|
529
|
-
|
|
530
|
-
# Group columns by plot type
|
|
531
|
-
plot_type = metadata.get("plot_type", metadata.get("method", "line"))
|
|
532
|
-
|
|
533
|
-
# Parse column names to find plot data
|
|
534
|
-
columns = csv_data.columns.tolist()
|
|
535
|
-
|
|
536
|
-
# Find x/y data columns
|
|
537
|
-
x_cols = [c for c in columns if "_x" in c.lower() and "text" not in c.lower()]
|
|
538
|
-
y_cols = [c for c in columns if "_y" in c.lower() and "text" not in c.lower()]
|
|
539
|
-
|
|
540
|
-
if plot_type == "line" or plot_type == "plot":
|
|
541
|
-
# For line plots, look for paired x/y or just y with index
|
|
542
|
-
if x_cols and y_cols:
|
|
543
|
-
for x_col, y_col in zip(x_cols, y_cols):
|
|
544
|
-
x = csv_data[x_col].dropna().values
|
|
545
|
-
y = csv_data[y_col].dropna().values
|
|
546
|
-
if len(x) > 0 and len(y) > 0:
|
|
547
|
-
ax.plot(x[: len(y)], y[: len(x)])
|
|
548
|
-
elif y_cols:
|
|
549
|
-
for y_col in y_cols:
|
|
550
|
-
y = csv_data[y_col].dropna().values
|
|
551
|
-
if len(y) > 0:
|
|
552
|
-
ax.plot(y)
|
|
553
|
-
|
|
554
|
-
elif plot_type == "scatter":
|
|
555
|
-
if x_cols and y_cols:
|
|
556
|
-
x = csv_data[x_cols[0]].dropna().values
|
|
557
|
-
y = csv_data[y_cols[0]].dropna().values
|
|
558
|
-
ax.scatter(x[: min(len(x), len(y))], y[: min(len(x), len(y))])
|
|
559
|
-
|
|
560
|
-
# Add text annotations
|
|
561
|
-
text_cols = [c for c in columns if "text" in c.lower() and "content" in c.lower()]
|
|
562
|
-
for text_col in text_cols:
|
|
563
|
-
# Find corresponding x, y columns
|
|
564
|
-
prefix = text_col.rsplit("_content", 1)[0]
|
|
565
|
-
x_col = f"{prefix}_x"
|
|
566
|
-
y_col = f"{prefix}_y"
|
|
567
|
-
|
|
568
|
-
if x_col in columns and y_col in columns:
|
|
569
|
-
x_vals = csv_data[x_col].dropna()
|
|
570
|
-
y_vals = csv_data[y_col].dropna()
|
|
571
|
-
text_vals = csv_data[text_col].dropna()
|
|
572
|
-
|
|
573
|
-
for i in range(min(len(x_vals), len(y_vals), len(text_vals))):
|
|
574
|
-
ax.text(
|
|
575
|
-
x_vals.iloc[i],
|
|
576
|
-
y_vals.iloc[i],
|
|
577
|
-
str(text_vals.iloc[i]),
|
|
578
|
-
transform=ax.transAxes,
|
|
579
|
-
fontsize=6,
|
|
580
|
-
verticalalignment="top",
|
|
581
|
-
horizontalalignment="right",
|
|
582
|
-
)
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
def _apply_manual_overrides(fig, axes, overrides):
|
|
586
|
-
"""
|
|
587
|
-
Apply manual style/annotation overrides to figure.
|
|
588
|
-
|
|
589
|
-
Parameters
|
|
590
|
-
----------
|
|
591
|
-
fig : FigWrapper
|
|
592
|
-
Figure to modify
|
|
593
|
-
axes : AxisWrapper or array
|
|
594
|
-
Axes to modify
|
|
595
|
-
overrides : dict
|
|
596
|
-
Override specifications like {"axes[0].style.linewidth": 0.5}
|
|
597
|
-
"""
|
|
598
|
-
# Simple override application - can be extended
|
|
599
|
-
for key, value in overrides.items():
|
|
600
|
-
# Parse key like "axes[0].title" or "style.linewidth"
|
|
601
|
-
parts = key.split(".")
|
|
602
|
-
|
|
603
|
-
if parts[0].startswith("axes["):
|
|
604
|
-
# Extract axis index
|
|
605
|
-
import re
|
|
606
|
-
|
|
607
|
-
match = re.match(r"axes\[(\d+)\]", parts[0])
|
|
608
|
-
if match:
|
|
609
|
-
idx = int(match.group(1))
|
|
610
|
-
ax = axes if not hasattr(axes, "flat") else list(axes.flat)[idx]
|
|
611
|
-
|
|
612
|
-
if len(parts) > 1:
|
|
613
|
-
attr = parts[1]
|
|
614
|
-
if attr == "title":
|
|
615
|
-
ax.set_title(value)
|
|
616
|
-
elif attr == "xlabel":
|
|
617
|
-
ax.set_xlabel(value)
|
|
618
|
-
elif attr == "ylabel":
|
|
619
|
-
ax.set_ylabel(value)
|
|
325
|
+
fig_mpl = _plt.figure(*args, **kwargs)
|
|
326
|
+
return FigWrapper(fig_mpl)
|
|
620
327
|
|
|
621
328
|
|
|
622
329
|
def tight_layout(**kwargs):
|
|
@@ -627,9 +334,6 @@ def tight_layout(**kwargs):
|
|
|
627
334
|
1. UserWarning: "The figure layout has changed to tight" - informational only
|
|
628
335
|
2. RuntimeError: Colorbar layout incompatibility - occurs when colorbars exist with old engine
|
|
629
336
|
|
|
630
|
-
When a colorbar layout error occurs, the function silently continues as the layout
|
|
631
|
-
is still functional even if the engine cannot be changed.
|
|
632
|
-
|
|
633
337
|
Parameters
|
|
634
338
|
----------
|
|
635
339
|
**kwargs
|
|
@@ -637,18 +341,14 @@ def tight_layout(**kwargs):
|
|
|
637
341
|
"""
|
|
638
342
|
import warnings
|
|
639
343
|
|
|
640
|
-
import matplotlib.pyplot as plt
|
|
641
|
-
|
|
642
344
|
with warnings.catch_warnings():
|
|
643
345
|
warnings.filterwarnings(
|
|
644
346
|
"ignore", message="The figure layout has changed to tight"
|
|
645
347
|
)
|
|
646
348
|
try:
|
|
647
|
-
|
|
349
|
+
_plt.tight_layout(**kwargs)
|
|
648
350
|
except RuntimeError as e:
|
|
649
351
|
# Silently handle colorbar layout engine incompatibility
|
|
650
|
-
# This occurs when colorbars were created before tight_layout is called
|
|
651
|
-
# The layout is still usable, so we can safely ignore this error
|
|
652
352
|
if "Colorbar layout" not in str(e):
|
|
653
353
|
raise
|
|
654
354
|
|
|
@@ -664,12 +364,10 @@ def colorbar(mappable=None, cax=None, ax=None, **kwargs):
|
|
|
664
364
|
----------
|
|
665
365
|
mappable : ScalarMappable, optional
|
|
666
366
|
The image, contour set, etc. to which the colorbar applies.
|
|
667
|
-
If None, uses the current image.
|
|
668
367
|
cax : Axes, optional
|
|
669
368
|
Axes into which the colorbar will be drawn.
|
|
670
369
|
ax : Axes or AxisWrapper or list thereof, optional
|
|
671
370
|
Parent axes from which space for the colorbar will be stolen.
|
|
672
|
-
If None, uses current axes.
|
|
673
371
|
**kwargs
|
|
674
372
|
Additional keyword arguments passed to matplotlib.pyplot.colorbar()
|
|
675
373
|
|
|
@@ -678,8 +376,6 @@ def colorbar(mappable=None, cax=None, ax=None, **kwargs):
|
|
|
678
376
|
Colorbar
|
|
679
377
|
The created colorbar object
|
|
680
378
|
"""
|
|
681
|
-
import matplotlib.pyplot as plt
|
|
682
|
-
|
|
683
379
|
# Unwrap ax if it's a SciTeX AxisWrapper
|
|
684
380
|
if ax is not None:
|
|
685
381
|
if hasattr(ax, "__iter__") and not isinstance(ax, str):
|
|
@@ -694,7 +390,7 @@ def colorbar(mappable=None, cax=None, ax=None, **kwargs):
|
|
|
694
390
|
cax = cax._axis_mpl if hasattr(cax, "_axis_mpl") else cax
|
|
695
391
|
|
|
696
392
|
# Call matplotlib's colorbar with unwrapped axes
|
|
697
|
-
return
|
|
393
|
+
return _plt.colorbar(mappable=mappable, cax=cax, ax=ax, **kwargs)
|
|
698
394
|
|
|
699
395
|
|
|
700
396
|
def close(fig=None):
|
|
@@ -712,54 +408,72 @@ def close(fig=None):
|
|
|
712
408
|
- Figure or FigWrapper: close the specified figure
|
|
713
409
|
- int: close figure with that number
|
|
714
410
|
- str: close figure with that label, or 'all' to close all figures
|
|
715
|
-
|
|
716
|
-
Examples
|
|
717
|
-
--------
|
|
718
|
-
>>> import scitex.plt as splt
|
|
719
|
-
>>> fig, ax = splt.subplots()
|
|
720
|
-
>>> ax.plot([1, 2, 3])
|
|
721
|
-
>>> splt.close(fig) # Works with FigWrapper
|
|
722
|
-
|
|
723
|
-
>>> splt.close('all') # Close all figures
|
|
724
|
-
>>> splt.close() # Close current figure
|
|
725
|
-
|
|
726
|
-
See Also
|
|
727
|
-
--------
|
|
728
|
-
matplotlib.pyplot.close : Standard matplotlib close function
|
|
729
411
|
"""
|
|
730
|
-
import matplotlib.pyplot as plt
|
|
731
|
-
|
|
732
412
|
if fig is None:
|
|
733
|
-
|
|
734
|
-
plt.close()
|
|
413
|
+
_plt.close()
|
|
735
414
|
elif isinstance(fig, (int, str)):
|
|
736
|
-
|
|
737
|
-
plt.close(fig)
|
|
415
|
+
_plt.close(fig)
|
|
738
416
|
elif hasattr(fig, "_fig_mpl"):
|
|
739
417
|
# FigWrapper object - unwrap and close
|
|
740
|
-
|
|
418
|
+
_plt.close(fig._fig_mpl)
|
|
741
419
|
elif hasattr(fig, "figure"):
|
|
742
420
|
# Alternative attribute name (backward compatibility)
|
|
743
|
-
|
|
421
|
+
_plt.close(fig.figure)
|
|
422
|
+
elif hasattr(fig, "fig"):
|
|
423
|
+
# figrecipe RecordingFigure - unwrap and close
|
|
424
|
+
_plt.close(fig.fig)
|
|
744
425
|
else:
|
|
745
426
|
# Assume it's a matplotlib Figure
|
|
746
|
-
|
|
427
|
+
_plt.close(fig)
|
|
747
428
|
|
|
748
429
|
|
|
430
|
+
# ============================================================================
|
|
431
|
+
# Public API
|
|
432
|
+
# ============================================================================
|
|
433
|
+
|
|
749
434
|
__all__ = [
|
|
750
|
-
|
|
751
|
-
"
|
|
752
|
-
"
|
|
753
|
-
"
|
|
435
|
+
# Figrecipe core (re-exported with branding)
|
|
436
|
+
"subplots",
|
|
437
|
+
"save",
|
|
438
|
+
"reproduce",
|
|
439
|
+
"load", # Alias for reproduce
|
|
440
|
+
"crop",
|
|
441
|
+
"validate",
|
|
442
|
+
"extract_data",
|
|
443
|
+
"info",
|
|
754
444
|
"edit",
|
|
445
|
+
# Style management
|
|
446
|
+
"STYLE",
|
|
447
|
+
"load_style",
|
|
448
|
+
"unload_style",
|
|
449
|
+
"list_presets",
|
|
450
|
+
"apply_style",
|
|
451
|
+
# Composition
|
|
452
|
+
"compose",
|
|
453
|
+
"align_panels",
|
|
454
|
+
"distribute_panels",
|
|
455
|
+
"smart_align",
|
|
456
|
+
# Graph visualization
|
|
457
|
+
"draw_graph",
|
|
458
|
+
"get_graph_preset",
|
|
459
|
+
"list_graph_presets",
|
|
460
|
+
"register_graph_preset",
|
|
461
|
+
# Extensions
|
|
462
|
+
"sns",
|
|
463
|
+
"enable_svg",
|
|
464
|
+
# SciTeX-specific wrappers
|
|
755
465
|
"figure",
|
|
466
|
+
"colorbar",
|
|
467
|
+
"close",
|
|
468
|
+
"tight_layout",
|
|
469
|
+
# Local submodules
|
|
470
|
+
"ax",
|
|
471
|
+
"color",
|
|
756
472
|
"gallery",
|
|
757
|
-
"
|
|
473
|
+
"utils",
|
|
474
|
+
"styles",
|
|
758
475
|
"presets",
|
|
759
|
-
"subplots",
|
|
760
476
|
"termplot",
|
|
761
|
-
"tight_layout",
|
|
762
|
-
"utils",
|
|
763
477
|
]
|
|
764
478
|
|
|
765
479
|
|
|
@@ -768,35 +482,19 @@ def __getattr__(name):
|
|
|
768
482
|
Fallback to matplotlib.pyplot for any missing attributes.
|
|
769
483
|
This makes scitex.plt a complete drop-in replacement for matplotlib.pyplot.
|
|
770
484
|
"""
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
if hasattr(plt, name):
|
|
775
|
-
return getattr(plt, name)
|
|
776
|
-
else:
|
|
777
|
-
raise AttributeError(f"module 'scitex.plt' has no attribute '{name}'")
|
|
778
|
-
except ImportError:
|
|
779
|
-
raise AttributeError(
|
|
780
|
-
f"module 'scitex.plt' has no attribute '{name}' (matplotlib not available)"
|
|
781
|
-
)
|
|
485
|
+
if hasattr(_plt, name):
|
|
486
|
+
return getattr(_plt, name)
|
|
487
|
+
raise AttributeError(f"module 'scitex.plt' has no attribute '{name}'")
|
|
782
488
|
|
|
783
489
|
|
|
784
490
|
def __dir__():
|
|
785
491
|
"""
|
|
786
492
|
Provide comprehensive directory listing including matplotlib.pyplot functions.
|
|
787
493
|
"""
|
|
788
|
-
|
|
789
|
-
local_attrs = __all__.copy()
|
|
790
|
-
|
|
494
|
+
local_attrs = list(__all__)
|
|
791
495
|
# Add matplotlib.pyplot attributes
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
mpl_attrs = [attr for attr in dir(plt) if not attr.startswith("_")]
|
|
796
|
-
local_attrs.extend(mpl_attrs)
|
|
797
|
-
except ImportError:
|
|
798
|
-
pass
|
|
799
|
-
|
|
496
|
+
mpl_attrs = [attr for attr in dir(_plt) if not attr.startswith("_")]
|
|
497
|
+
local_attrs.extend(mpl_attrs)
|
|
800
498
|
return sorted(set(local_attrs))
|
|
801
499
|
|
|
802
500
|
|