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
scitex/plt/__init__.py CHANGED
@@ -1,46 +1,197 @@
1
1
  #!/usr/bin/env python3
2
- # Timestamp: "2025-12-02 12:30:00 (ywatanabe)"
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 from SCITEX_STYLE.yaml:
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
- import scitex.plt as splt
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
- No need to call scitex.session() or configure_mpl() - it's automatic.
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
- Style values can be customized by:
24
- 1. Editing SCITEX_STYLE.yaml
25
- 2. Setting environment variables (SCITEX_PLT_FONTS_AXIS_LABEL_PT=8)
26
- 3. Passing parameters directly to subplots()
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 plt
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
- # Auto-configure matplotlib with SciTeX style on import
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 public API
74
+ from figrecipe import __version__ as _figrecipe_version
75
+ from figrecipe import (
76
+ compose,
77
+ crop,
78
+ edit,
79
+ extract_data,
80
+ info,
81
+ list_presets,
82
+ load_style,
83
+ reproduce,
84
+ save,
85
+ subplots,
86
+ unload_style,
87
+ validate,
88
+ )
89
+
90
+ # Internal imports (not part of figrecipe public API)
91
+ from figrecipe._api._notebook import enable_svg
92
+ from figrecipe._api._seaborn_proxy import sns
93
+ from figrecipe._api._style_manager import STYLE, apply_style
94
+ from figrecipe._composition import align_panels, distribute_panels, smart_align
95
+ from figrecipe._graph_presets import get_preset as get_graph_preset
96
+ from figrecipe._graph_presets import list_presets as list_graph_presets
97
+ from figrecipe._graph_presets import register_preset as register_graph_preset
98
+
99
+ # Also export load as alias for reproduce
100
+ load = reproduce
101
+ else:
102
+ # Provide stub versions when figrecipe is not available
103
+ _figrecipe_version = "0.0.0"
104
+
105
+ def _not_available(*args, **kwargs):
106
+ raise ImportError(
107
+ "figrecipe is required for this feature. Install with: pip install figrecipe"
108
+ )
109
+
110
+ STYLE = None
111
+ load_style = _not_available
112
+ unload_style = _not_available
113
+ list_presets = _not_available
114
+ apply_style = _not_available
115
+ subplots = _not_available
116
+ save = _not_available
117
+ reproduce = _not_available
118
+ load = _not_available
119
+ crop = _not_available
120
+ validate = _not_available
121
+ extract_data = _not_available
122
+ info = _not_available
123
+ edit = _not_available
124
+ compose = _not_available
125
+ align_panels = _not_available
126
+ distribute_panels = _not_available
127
+ smart_align = _not_available
128
+ sns = None
129
+ enable_svg = _not_available
130
+ get_graph_preset = _not_available
131
+ list_graph_presets = _not_available
132
+ register_graph_preset = _not_available
133
+
134
+ # ============================================================================
135
+ # Local scitex submodules (kept for compatibility)
136
+ # ============================================================================
137
+ try:
138
+ from ._tpl import termplot
139
+ except ImportError:
140
+ termplot = None
141
+
142
+ # Backward compatibility: expose styles submodule (deprecated, use figrecipe)
143
+ from . import ax, color, gallery, styles, utils
144
+
145
+ # Import draw_graph from figrecipe integration (handles AxisWrapper)
146
+ from ._figrecipe_integration import draw_graph
147
+ from .styles import presets
148
+
149
+ # ============================================================================
150
+ # Auto-configure matplotlib with SciTeX defaults on import
151
+ # ============================================================================
152
+
153
+
154
+ def _register_arial_fonts():
155
+ """Register Arial fonts if available."""
156
+ try:
157
+ fm.findfont("Arial", fallback_to_default=False)
158
+ return True
159
+ except Exception:
160
+ # Search for Arial font files and register them
161
+ arial_paths = [
162
+ f
163
+ for f in fm.findSystemFonts()
164
+ if os.path.basename(f).lower().startswith("arial")
165
+ ]
166
+
167
+ if arial_paths:
168
+ for path in arial_paths:
169
+ try:
170
+ fm.fontManager.addfont(path)
171
+ except Exception:
172
+ pass
173
+
174
+ # Verify Arial is now available
175
+ try:
176
+ fm.findfont("Arial", fallback_to_default=False)
177
+ return True
178
+ except Exception:
179
+ pass
180
+ return False
40
181
 
41
182
 
42
183
  def _auto_configure_mpl():
43
184
  """Apply SciTeX style configuration automatically on import."""
185
+ # Try to use figrecipe's style system first
186
+ if _FIGRECIPE_AVAILABLE:
187
+ try:
188
+ # Load SCITEX style preset from figrecipe
189
+ load_style("SCITEX")
190
+ return
191
+ except Exception:
192
+ pass
193
+
194
+ # Fallback: use local style loader
44
195
  from .styles import resolve_style_value
45
196
 
46
197
  # mm to pt conversion factor
@@ -111,32 +262,8 @@ def _auto_configure_mpl():
111
262
  mpl.rcParams.update(mpl_config)
112
263
 
113
264
 
114
- # Register Arial fonts eagerly (before style configuration)
115
- _arial_enabled = False
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
265
+ # Register Arial fonts eagerly
266
+ _arial_enabled = _register_arial_fonts()
140
267
 
141
268
  # Configure font family
142
269
  if _arial_enabled:
@@ -165,458 +292,35 @@ _auto_configure_mpl()
165
292
 
166
293
  # Set up color cycle from scitex colors
167
294
  try:
168
- from . import color as _color_module
169
-
170
295
  _rgba_norm_cycle = {
171
- k: tuple(_color_module.update_alpha(v, 1.0))
172
- for k, v in _color_module.PARAMS.get("RGBA_NORM_FOR_CYCLE", {}).items()
296
+ k: tuple(color.update_alpha(v, 1.0))
297
+ for k, v in color.PARAMS.get("RGBA_NORM_FOR_CYCLE", {}).items()
173
298
  }
174
299
  if _rgba_norm_cycle:
175
- mpl.rcParams["axes.prop_cycle"] = plt.cycler(
300
+ mpl.rcParams["axes.prop_cycle"] = _plt.cycler(
176
301
  color=list(_rgba_norm_cycle.values())
177
302
  )
178
303
  except Exception:
179
304
  pass # Use matplotlib default colors if color module fails
180
305
 
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
-
198
- def subplots(*args, **kwargs):
199
- """Lazy-loaded subplots function."""
200
- global _subplots_func_cached
201
- if _subplots_func_cached is None:
202
- from ._subplots._SubplotsWrapper import subplots as _subplots_impl
203
306
 
204
- _subplots_func_cached = _subplots_impl
205
- return _subplots_func_cached(*args, **kwargs)
307
+ # ============================================================================
308
+ # SciTeX-specific wrapper functions (for AxisWrapper/FigWrapper compatibility)
309
+ # ============================================================================
206
310
 
207
311
 
208
312
  def figure(*args, **kwargs):
209
- """Lazy-loaded figure function that returns a FigWrapper."""
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)
313
+ """Create a figure that returns a FigWrapper.
219
314
 
220
- _figure_func_cached = _figure_impl
221
- return _figure_func_cached(*args, **kwargs)
315
+ This is the scitex-specific figure function that creates FigWrapper
316
+ objects for compatibility with scitex.plt.ax utilities.
222
317
 
223
-
224
- def crop(input_path, output_path=None, margin=12, overwrite=False, verbose=False):
318
+ For figrecipe-style recording figures, use subplots() instead.
225
319
  """
226
- Auto-crop a figure to its content area.
320
+ from ._subplots._FigWrapper import FigWrapper
227
321
 
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)
264
-
265
-
266
- def load(path, apply_manual=True):
267
- """
268
- Load a figure from saved JSON + CSV files.
269
-
270
- Parameters
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)
322
+ fig_mpl = _plt.figure(*args, **kwargs)
323
+ return FigWrapper(fig_mpl)
620
324
 
621
325
 
622
326
  def tight_layout(**kwargs):
@@ -627,9 +331,6 @@ def tight_layout(**kwargs):
627
331
  1. UserWarning: "The figure layout has changed to tight" - informational only
628
332
  2. RuntimeError: Colorbar layout incompatibility - occurs when colorbars exist with old engine
629
333
 
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
334
  Parameters
634
335
  ----------
635
336
  **kwargs
@@ -637,18 +338,14 @@ def tight_layout(**kwargs):
637
338
  """
638
339
  import warnings
639
340
 
640
- import matplotlib.pyplot as plt
641
-
642
341
  with warnings.catch_warnings():
643
342
  warnings.filterwarnings(
644
343
  "ignore", message="The figure layout has changed to tight"
645
344
  )
646
345
  try:
647
- plt.tight_layout(**kwargs)
346
+ _plt.tight_layout(**kwargs)
648
347
  except RuntimeError as e:
649
348
  # 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
349
  if "Colorbar layout" not in str(e):
653
350
  raise
654
351
 
@@ -664,12 +361,10 @@ def colorbar(mappable=None, cax=None, ax=None, **kwargs):
664
361
  ----------
665
362
  mappable : ScalarMappable, optional
666
363
  The image, contour set, etc. to which the colorbar applies.
667
- If None, uses the current image.
668
364
  cax : Axes, optional
669
365
  Axes into which the colorbar will be drawn.
670
366
  ax : Axes or AxisWrapper or list thereof, optional
671
367
  Parent axes from which space for the colorbar will be stolen.
672
- If None, uses current axes.
673
368
  **kwargs
674
369
  Additional keyword arguments passed to matplotlib.pyplot.colorbar()
675
370
 
@@ -678,8 +373,6 @@ def colorbar(mappable=None, cax=None, ax=None, **kwargs):
678
373
  Colorbar
679
374
  The created colorbar object
680
375
  """
681
- import matplotlib.pyplot as plt
682
-
683
376
  # Unwrap ax if it's a SciTeX AxisWrapper
684
377
  if ax is not None:
685
378
  if hasattr(ax, "__iter__") and not isinstance(ax, str):
@@ -694,7 +387,7 @@ def colorbar(mappable=None, cax=None, ax=None, **kwargs):
694
387
  cax = cax._axis_mpl if hasattr(cax, "_axis_mpl") else cax
695
388
 
696
389
  # Call matplotlib's colorbar with unwrapped axes
697
- return plt.colorbar(mappable=mappable, cax=cax, ax=ax, **kwargs)
390
+ return _plt.colorbar(mappable=mappable, cax=cax, ax=ax, **kwargs)
698
391
 
699
392
 
700
393
  def close(fig=None):
@@ -712,54 +405,72 @@ def close(fig=None):
712
405
  - Figure or FigWrapper: close the specified figure
713
406
  - int: close figure with that number
714
407
  - 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
408
  """
730
- import matplotlib.pyplot as plt
731
-
732
409
  if fig is None:
733
- # Close current figure
734
- plt.close()
410
+ _plt.close()
735
411
  elif isinstance(fig, (int, str)):
736
- # Close by figure number or label (including 'all')
737
- plt.close(fig)
412
+ _plt.close(fig)
738
413
  elif hasattr(fig, "_fig_mpl"):
739
414
  # FigWrapper object - unwrap and close
740
- plt.close(fig._fig_mpl)
415
+ _plt.close(fig._fig_mpl)
741
416
  elif hasattr(fig, "figure"):
742
417
  # Alternative attribute name (backward compatibility)
743
- plt.close(fig.figure)
418
+ _plt.close(fig.figure)
419
+ elif hasattr(fig, "fig"):
420
+ # figrecipe RecordingFigure - unwrap and close
421
+ _plt.close(fig.fig)
744
422
  else:
745
423
  # Assume it's a matplotlib Figure
746
- plt.close(fig)
424
+ _plt.close(fig)
747
425
 
748
426
 
427
+ # ============================================================================
428
+ # Public API
429
+ # ============================================================================
430
+
749
431
  __all__ = [
750
- "close",
751
- "color",
752
- "colorbar",
753
- "draw_graph",
432
+ # Figrecipe core (re-exported with branding)
433
+ "subplots",
434
+ "save",
435
+ "reproduce",
436
+ "load", # Alias for reproduce
437
+ "crop",
438
+ "validate",
439
+ "extract_data",
440
+ "info",
754
441
  "edit",
442
+ # Style management
443
+ "STYLE",
444
+ "load_style",
445
+ "unload_style",
446
+ "list_presets",
447
+ "apply_style",
448
+ # Composition
449
+ "compose",
450
+ "align_panels",
451
+ "distribute_panels",
452
+ "smart_align",
453
+ # Graph visualization
454
+ "draw_graph",
455
+ "get_graph_preset",
456
+ "list_graph_presets",
457
+ "register_graph_preset",
458
+ # Extensions
459
+ "sns",
460
+ "enable_svg",
461
+ # SciTeX-specific wrappers
755
462
  "figure",
463
+ "colorbar",
464
+ "close",
465
+ "tight_layout",
466
+ # Local submodules
467
+ "ax",
468
+ "color",
756
469
  "gallery",
757
- "load",
470
+ "utils",
471
+ "styles",
758
472
  "presets",
759
- "subplots",
760
473
  "termplot",
761
- "tight_layout",
762
- "utils",
763
474
  ]
764
475
 
765
476
 
@@ -768,35 +479,19 @@ def __getattr__(name):
768
479
  Fallback to matplotlib.pyplot for any missing attributes.
769
480
  This makes scitex.plt a complete drop-in replacement for matplotlib.pyplot.
770
481
  """
771
- try:
772
- import matplotlib.pyplot as plt
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
- )
482
+ if hasattr(_plt, name):
483
+ return getattr(_plt, name)
484
+ raise AttributeError(f"module 'scitex.plt' has no attribute '{name}'")
782
485
 
783
486
 
784
487
  def __dir__():
785
488
  """
786
489
  Provide comprehensive directory listing including matplotlib.pyplot functions.
787
490
  """
788
- # Get local attributes
789
- local_attrs = __all__.copy()
790
-
491
+ local_attrs = list(__all__)
791
492
  # Add matplotlib.pyplot attributes
792
- try:
793
- import matplotlib.pyplot as plt
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
-
493
+ mpl_attrs = [attr for attr in dir(_plt) if not attr.startswith("_")]
494
+ local_attrs.extend(mpl_attrs)
800
495
  return sorted(set(local_attrs))
801
496
 
802
497