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/diagram/_presets.py
DELETED
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
3
|
-
# Timestamp: 2025-12-15
|
|
4
|
-
# Author: ywatanabe / Claude
|
|
5
|
-
# File: scitex/diagram/_presets.py
|
|
6
|
-
|
|
7
|
-
"""
|
|
8
|
-
Presets for common diagram types in scientific papers.
|
|
9
|
-
|
|
10
|
-
Each preset defines rules for compiling the semantic spec to backend formats.
|
|
11
|
-
These encode domain knowledge about "what makes a good paper figure."
|
|
12
|
-
"""
|
|
13
|
-
|
|
14
|
-
from dataclasses import dataclass
|
|
15
|
-
from typing import Dict, Any, List
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
@dataclass
|
|
19
|
-
class DiagramPreset:
|
|
20
|
-
"""Rules for compiling a diagram type."""
|
|
21
|
-
|
|
22
|
-
# Mermaid settings
|
|
23
|
-
mermaid_direction: str # TB, LR, RL, BT
|
|
24
|
-
mermaid_theme: Dict[str, str]
|
|
25
|
-
|
|
26
|
-
# Graphviz settings
|
|
27
|
-
graphviz_rankdir: str # TB, LR, RL, BT
|
|
28
|
-
graphviz_ranksep: float
|
|
29
|
-
graphviz_nodesep: float
|
|
30
|
-
|
|
31
|
-
# Spacing mappings
|
|
32
|
-
spacing_map: Dict[str, Dict[str, float]]
|
|
33
|
-
|
|
34
|
-
# Shape mappings (semantic -> backend)
|
|
35
|
-
mermaid_shapes: Dict[str, str]
|
|
36
|
-
graphviz_shapes: Dict[str, str]
|
|
37
|
-
|
|
38
|
-
# Emphasis styles (colors, borders)
|
|
39
|
-
emphasis_styles: Dict[str, Dict[str, str]]
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
# Workflow preset: sequential processes, emphasize flow
|
|
43
|
-
WORKFLOW_PRESET = DiagramPreset(
|
|
44
|
-
mermaid_direction="LR", # Left-to-right for workflows
|
|
45
|
-
mermaid_theme={
|
|
46
|
-
"primaryColor": "#1a2634",
|
|
47
|
-
"primaryTextColor": "#e0e0e0",
|
|
48
|
-
"primaryBorderColor": "#3a4a5a",
|
|
49
|
-
"lineColor": "#5a9fcf",
|
|
50
|
-
},
|
|
51
|
-
graphviz_rankdir="LR",
|
|
52
|
-
graphviz_ranksep=0.8,
|
|
53
|
-
graphviz_nodesep=0.5,
|
|
54
|
-
spacing_map={
|
|
55
|
-
"tight": {"ranksep": 0.3, "nodesep": 0.2}, # Publication: minimal
|
|
56
|
-
"compact": {"ranksep": 0.4, "nodesep": 0.3},
|
|
57
|
-
"medium": {"ranksep": 0.8, "nodesep": 0.5},
|
|
58
|
-
"large": {"ranksep": 1.2, "nodesep": 0.8},
|
|
59
|
-
},
|
|
60
|
-
mermaid_shapes={
|
|
61
|
-
"box": '["__LABEL__"]',
|
|
62
|
-
"rounded": '("__LABEL__")',
|
|
63
|
-
"diamond": '{"__LABEL__"}',
|
|
64
|
-
"circle": '(("__LABEL__"))',
|
|
65
|
-
"stadium": '(["__LABEL__"])',
|
|
66
|
-
},
|
|
67
|
-
graphviz_shapes={
|
|
68
|
-
"box": "box",
|
|
69
|
-
"rounded": "box", # with style=rounded
|
|
70
|
-
"diamond": "diamond",
|
|
71
|
-
"circle": "circle",
|
|
72
|
-
"stadium": "box", # with style=rounded
|
|
73
|
-
},
|
|
74
|
-
emphasis_styles={
|
|
75
|
-
"primary": {"fill": "#0d4a6b", "stroke": "#5a9fcf", "stroke-width": "2px"},
|
|
76
|
-
"success": {"fill": "#ccffcc", "stroke": "#00cc00"},
|
|
77
|
-
"warning": {"fill": "#ffcccc", "stroke": "#cc0000"},
|
|
78
|
-
"muted": {"fill": "#f0f0f0", "stroke": "#999999"},
|
|
79
|
-
"normal": {"fill": "#1a2634", "stroke": "#3a4a5a"},
|
|
80
|
-
},
|
|
81
|
-
)
|
|
82
|
-
|
|
83
|
-
# Decision tree preset: top-to-bottom with branches
|
|
84
|
-
DECISION_PRESET = DiagramPreset(
|
|
85
|
-
mermaid_direction="TB",
|
|
86
|
-
mermaid_theme={
|
|
87
|
-
"primaryColor": "#f5f5f5",
|
|
88
|
-
"primaryTextColor": "#333333",
|
|
89
|
-
"primaryBorderColor": "#666666",
|
|
90
|
-
"lineColor": "#666666",
|
|
91
|
-
},
|
|
92
|
-
graphviz_rankdir="TB",
|
|
93
|
-
graphviz_ranksep=1.0,
|
|
94
|
-
graphviz_nodesep=0.6,
|
|
95
|
-
spacing_map={
|
|
96
|
-
"compact": {"ranksep": 0.6, "nodesep": 0.4},
|
|
97
|
-
"medium": {"ranksep": 1.0, "nodesep": 0.6},
|
|
98
|
-
"large": {"ranksep": 1.5, "nodesep": 1.0},
|
|
99
|
-
},
|
|
100
|
-
mermaid_shapes={
|
|
101
|
-
"box": '["__LABEL__"]',
|
|
102
|
-
"rounded": '("__LABEL__")',
|
|
103
|
-
"diamond": '{"__LABEL__"}',
|
|
104
|
-
"circle": '(("__LABEL__"))',
|
|
105
|
-
"stadium": '(["__LABEL__"])',
|
|
106
|
-
},
|
|
107
|
-
graphviz_shapes={
|
|
108
|
-
"box": "box",
|
|
109
|
-
"rounded": "box",
|
|
110
|
-
"diamond": "diamond",
|
|
111
|
-
"circle": "circle",
|
|
112
|
-
"stadium": "box",
|
|
113
|
-
},
|
|
114
|
-
emphasis_styles={
|
|
115
|
-
"primary": {"fill": "#e6f3ff", "stroke": "#0066cc", "stroke-width": "2px"},
|
|
116
|
-
"success": {"fill": "#e6ffe6", "stroke": "#00aa00"},
|
|
117
|
-
"warning": {"fill": "#ffe6e6", "stroke": "#cc0000"},
|
|
118
|
-
"muted": {"fill": "#f0f0f0", "stroke": "#aaaaaa"},
|
|
119
|
-
"normal": {"fill": "#ffffff", "stroke": "#666666"},
|
|
120
|
-
},
|
|
121
|
-
)
|
|
122
|
-
|
|
123
|
-
# Pipeline preset: strict horizontal stages
|
|
124
|
-
PIPELINE_PRESET = DiagramPreset(
|
|
125
|
-
mermaid_direction="LR",
|
|
126
|
-
mermaid_theme={
|
|
127
|
-
"primaryColor": "#ffffff",
|
|
128
|
-
"primaryTextColor": "#333333",
|
|
129
|
-
"primaryBorderColor": "#0066cc",
|
|
130
|
-
"lineColor": "#0066cc",
|
|
131
|
-
},
|
|
132
|
-
graphviz_rankdir="LR",
|
|
133
|
-
graphviz_ranksep=1.2,
|
|
134
|
-
graphviz_nodesep=0.4,
|
|
135
|
-
spacing_map={
|
|
136
|
-
"compact": {"ranksep": 0.8, "nodesep": 0.3},
|
|
137
|
-
"medium": {"ranksep": 1.2, "nodesep": 0.4},
|
|
138
|
-
"large": {"ranksep": 1.8, "nodesep": 0.6},
|
|
139
|
-
},
|
|
140
|
-
mermaid_shapes={
|
|
141
|
-
"box": '["__LABEL__"]',
|
|
142
|
-
"rounded": '("__LABEL__")',
|
|
143
|
-
"diamond": '{"__LABEL__"}',
|
|
144
|
-
"circle": '(("__LABEL__"))',
|
|
145
|
-
"stadium": '(["__LABEL__"])',
|
|
146
|
-
},
|
|
147
|
-
graphviz_shapes={
|
|
148
|
-
"box": "box",
|
|
149
|
-
"rounded": "box",
|
|
150
|
-
"diamond": "diamond",
|
|
151
|
-
"circle": "circle",
|
|
152
|
-
"stadium": "box",
|
|
153
|
-
},
|
|
154
|
-
emphasis_styles={
|
|
155
|
-
"primary": {"fill": "#e6f0ff", "stroke": "#0044aa", "stroke-width": "2px"},
|
|
156
|
-
"success": {"fill": "#e6ffe6", "stroke": "#00aa00"},
|
|
157
|
-
"warning": {"fill": "#fff3e6", "stroke": "#ff8800"},
|
|
158
|
-
"muted": {"fill": "#f5f5f5", "stroke": "#cccccc"},
|
|
159
|
-
"normal": {"fill": "#ffffff", "stroke": "#0066cc"},
|
|
160
|
-
},
|
|
161
|
-
)
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
def get_preset(diagram_type: str) -> DiagramPreset:
|
|
165
|
-
"""Get preset for diagram type."""
|
|
166
|
-
presets = {
|
|
167
|
-
"workflow": WORKFLOW_PRESET,
|
|
168
|
-
"decision": DECISION_PRESET,
|
|
169
|
-
"pipeline": PIPELINE_PRESET,
|
|
170
|
-
"hierarchy": DECISION_PRESET, # Same as decision for now
|
|
171
|
-
"comparison": WORKFLOW_PRESET, # Override direction in compiler
|
|
172
|
-
}
|
|
173
|
-
return presets.get(diagram_type.lower(), WORKFLOW_PRESET)
|
scitex/diagram/_schema.py
DELETED
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
3
|
-
# Timestamp: 2025-12-15
|
|
4
|
-
# Author: ywatanabe / Claude
|
|
5
|
-
# File: scitex/diagram/_schema.py
|
|
6
|
-
|
|
7
|
-
"""
|
|
8
|
-
Schema definitions for SciTeX Diagram.
|
|
9
|
-
|
|
10
|
-
The schema defines paper-specific constraints that Mermaid/Graphviz don't know:
|
|
11
|
-
- Paper layout (single/double column, max width)
|
|
12
|
-
- Reading direction preferences
|
|
13
|
-
- Node emphasis for scientific communication
|
|
14
|
-
- Semantic layer grouping
|
|
15
|
-
"""
|
|
16
|
-
|
|
17
|
-
from dataclasses import dataclass, field
|
|
18
|
-
from enum import Enum
|
|
19
|
-
from typing import List, Dict, Optional, Literal
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
class DiagramType(Enum):
|
|
23
|
-
"""Semantic type of diagram - affects layout strategy."""
|
|
24
|
-
WORKFLOW = "workflow" # Sequential process, prefer LR/TB flow
|
|
25
|
-
DECISION = "decision" # Decision tree, prefer TB with branches
|
|
26
|
-
PIPELINE = "pipeline" # Data pipeline, strict LR with stages
|
|
27
|
-
HIERARCHY = "hierarchy" # Tree structure, TB with levels
|
|
28
|
-
COMPARISON = "comparison" # Side-by-side, two columns
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
class ColumnLayout(Enum):
|
|
32
|
-
"""Paper column layout."""
|
|
33
|
-
SINGLE = "single" # Full width (~170mm)
|
|
34
|
-
DOUBLE = "double" # Half width (~85mm)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
class SpacingLevel(Enum):
|
|
38
|
-
"""Abstract spacing levels - mapped to backend-specific values."""
|
|
39
|
-
TIGHT = "tight" # Publication: minimal whitespace
|
|
40
|
-
COMPACT = "compact"
|
|
41
|
-
MEDIUM = "medium"
|
|
42
|
-
LARGE = "large"
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
class PaperMode(Enum):
|
|
46
|
-
"""Paper mode affects layout density and edge visibility."""
|
|
47
|
-
DRAFT = "draft" # Full arrows, visible bidirectional, medium spacing
|
|
48
|
-
PUBLICATION = "publication" # Compact, return edges hidden/dotted
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
@dataclass
|
|
52
|
-
class PaperConstraints:
|
|
53
|
-
"""Paper-specific constraints that affect layout."""
|
|
54
|
-
column: ColumnLayout = ColumnLayout.SINGLE
|
|
55
|
-
max_width_mm: int = 170
|
|
56
|
-
reading_direction: Literal["left_to_right", "top_to_bottom"] = "left_to_right"
|
|
57
|
-
mode: PaperMode = PaperMode.DRAFT # draft: full details, publication: compact
|
|
58
|
-
emphasize: List[str] = field(default_factory=list) # Node IDs to highlight
|
|
59
|
-
|
|
60
|
-
# Scientific communication hints
|
|
61
|
-
main_flow: List[str] = field(default_factory=list) # Critical path nodes
|
|
62
|
-
secondary_flow: List[str] = field(default_factory=list) # Supporting elements
|
|
63
|
-
return_edges: List[tuple] = field(default_factory=list) # Edges to hide in publication
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
@dataclass
|
|
67
|
-
class LayoutHints:
|
|
68
|
-
"""Abstract layout hints - compiled to backend directives."""
|
|
69
|
-
layers: List[List[str]] = field(default_factory=list) # Nodes grouped by rank
|
|
70
|
-
alignment: Dict[str, str] = field(default_factory=dict) # Node alignment hints
|
|
71
|
-
layer_gap: SpacingLevel = SpacingLevel.MEDIUM
|
|
72
|
-
node_gap: SpacingLevel = SpacingLevel.MEDIUM
|
|
73
|
-
|
|
74
|
-
# Subgraph organization
|
|
75
|
-
groups: Dict[str, List[str]] = field(default_factory=dict) # Named groups
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
@dataclass
|
|
79
|
-
class NodeSpec:
|
|
80
|
-
"""Specification for a single node."""
|
|
81
|
-
id: str
|
|
82
|
-
label: str
|
|
83
|
-
shape: Literal["box", "rounded", "diamond", "circle", "stadium"] = "box"
|
|
84
|
-
emphasis: Literal["normal", "primary", "success", "warning", "muted"] = "normal"
|
|
85
|
-
|
|
86
|
-
def short_label(self, max_chars: int = 20) -> str:
|
|
87
|
-
"""Return truncated label for compact layouts."""
|
|
88
|
-
if len(self.label) <= max_chars:
|
|
89
|
-
return self.label
|
|
90
|
-
return self.label[:max_chars-3] + "..."
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
@dataclass
|
|
94
|
-
class EdgeSpec:
|
|
95
|
-
"""Specification for an edge between nodes."""
|
|
96
|
-
source: str
|
|
97
|
-
target: str
|
|
98
|
-
label: Optional[str] = None
|
|
99
|
-
style: Literal["solid", "dashed", "dotted"] = "solid"
|
|
100
|
-
arrow: Literal["normal", "none", "open"] = "normal"
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
@dataclass
|
|
104
|
-
class DiagramSpec:
|
|
105
|
-
"""Complete diagram specification - the semantic layer."""
|
|
106
|
-
|
|
107
|
-
# Metadata
|
|
108
|
-
type: DiagramType = DiagramType.WORKFLOW
|
|
109
|
-
title: str = ""
|
|
110
|
-
|
|
111
|
-
# Paper constraints
|
|
112
|
-
paper: PaperConstraints = field(default_factory=PaperConstraints)
|
|
113
|
-
|
|
114
|
-
# Layout hints
|
|
115
|
-
layout: LayoutHints = field(default_factory=LayoutHints)
|
|
116
|
-
|
|
117
|
-
# Content
|
|
118
|
-
nodes: List[NodeSpec] = field(default_factory=list)
|
|
119
|
-
edges: List[EdgeSpec] = field(default_factory=list)
|
|
120
|
-
|
|
121
|
-
# Theme
|
|
122
|
-
theme: Dict[str, str] = field(default_factory=dict)
|
|
123
|
-
|
|
124
|
-
@classmethod
|
|
125
|
-
def from_dict(cls, data: dict) -> "DiagramSpec":
|
|
126
|
-
"""Create DiagramSpec from dictionary (parsed YAML)."""
|
|
127
|
-
spec = cls()
|
|
128
|
-
|
|
129
|
-
# Parse type
|
|
130
|
-
if "type" in data:
|
|
131
|
-
spec.type = DiagramType(data["type"])
|
|
132
|
-
|
|
133
|
-
spec.title = data.get("title", "")
|
|
134
|
-
|
|
135
|
-
# Parse paper constraints
|
|
136
|
-
if "paper" in data:
|
|
137
|
-
p = data["paper"]
|
|
138
|
-
spec.paper = PaperConstraints(
|
|
139
|
-
column=ColumnLayout(p.get("column", "single")),
|
|
140
|
-
max_width_mm=p.get("max_width_mm", 170),
|
|
141
|
-
reading_direction=p.get("reading_direction", "left_to_right"),
|
|
142
|
-
mode=PaperMode(p.get("mode", "draft")),
|
|
143
|
-
emphasize=p.get("emphasize", []),
|
|
144
|
-
main_flow=p.get("main_flow", []),
|
|
145
|
-
secondary_flow=p.get("secondary_flow", []),
|
|
146
|
-
return_edges=[tuple(e) for e in p.get("return_edges", [])],
|
|
147
|
-
)
|
|
148
|
-
|
|
149
|
-
# Parse layout hints
|
|
150
|
-
if "layout" in data:
|
|
151
|
-
lt = data["layout"]
|
|
152
|
-
spec.layout = LayoutHints(
|
|
153
|
-
layers=lt.get("layers", []),
|
|
154
|
-
alignment=lt.get("alignment", {}),
|
|
155
|
-
layer_gap=SpacingLevel(lt.get("layer_gap", "medium")),
|
|
156
|
-
node_gap=SpacingLevel(lt.get("node_gap", "medium")),
|
|
157
|
-
groups=lt.get("groups", {}),
|
|
158
|
-
)
|
|
159
|
-
|
|
160
|
-
# Parse nodes
|
|
161
|
-
for n in data.get("nodes", []):
|
|
162
|
-
spec.nodes.append(NodeSpec(
|
|
163
|
-
id=n["id"],
|
|
164
|
-
label=n.get("label", n["id"]),
|
|
165
|
-
shape=n.get("shape", "box"),
|
|
166
|
-
emphasis=n.get("emphasis", "normal"),
|
|
167
|
-
))
|
|
168
|
-
|
|
169
|
-
# Parse edges
|
|
170
|
-
for e in data.get("edges", []):
|
|
171
|
-
spec.edges.append(EdgeSpec(
|
|
172
|
-
source=e["from"] if "from" in e else e["source"],
|
|
173
|
-
target=e["to"] if "to" in e else e["target"],
|
|
174
|
-
label=e.get("label"),
|
|
175
|
-
style=e.get("style", "solid"),
|
|
176
|
-
arrow=e.get("arrow", "normal"),
|
|
177
|
-
))
|
|
178
|
-
|
|
179
|
-
# Theme
|
|
180
|
-
spec.theme = data.get("theme", {})
|
|
181
|
-
|
|
182
|
-
return spec
|
scitex/diagram/_split.py
DELETED
|
@@ -1,278 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
3
|
-
# Timestamp: 2025-12-15
|
|
4
|
-
# Author: ywatanabe / Claude
|
|
5
|
-
# File: scitex/diagram/_split.py
|
|
6
|
-
|
|
7
|
-
"""
|
|
8
|
-
Auto-split large diagrams into multiple figures.
|
|
9
|
-
|
|
10
|
-
Strategies:
|
|
11
|
-
- by_groups: Split by existing layout.groups (deterministic, paper-friendly)
|
|
12
|
-
- by_articulation: Split at hub nodes (graph-theoretic)
|
|
13
|
-
|
|
14
|
-
The split preserves "ghost nodes" at boundaries for visual continuity.
|
|
15
|
-
"""
|
|
16
|
-
|
|
17
|
-
from dataclasses import dataclass, field
|
|
18
|
-
from typing import List, Dict, Set, Optional, Tuple
|
|
19
|
-
from copy import deepcopy
|
|
20
|
-
from enum import Enum
|
|
21
|
-
|
|
22
|
-
from scitex.diagram._schema import DiagramSpec, NodeSpec, EdgeSpec
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
class SplitStrategy(Enum):
|
|
26
|
-
BY_GROUPS = "by_groups" # Split by layout.groups
|
|
27
|
-
BY_ARTICULATION = "by_articulation" # Split at hub nodes
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
@dataclass
|
|
31
|
-
class SplitConfig:
|
|
32
|
-
"""Configuration for auto-splitting."""
|
|
33
|
-
enabled: bool = False
|
|
34
|
-
max_nodes: int = 12 # Split if more nodes than this
|
|
35
|
-
strategy: SplitStrategy = SplitStrategy.BY_GROUPS
|
|
36
|
-
keep_hubs: bool = True # Show hub nodes in both parts
|
|
37
|
-
ghost_style: str = "muted" # Style for ghost nodes
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
@dataclass
|
|
41
|
-
class SplitResult:
|
|
42
|
-
"""Result of splitting a diagram."""
|
|
43
|
-
figures: List[DiagramSpec]
|
|
44
|
-
labels: List[str] # Figure labels (A, B, C, ...)
|
|
45
|
-
cut_nodes: Set[str] # Nodes that appear in multiple figures
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def split_diagram(
|
|
49
|
-
spec: DiagramSpec,
|
|
50
|
-
config: Optional[SplitConfig] = None,
|
|
51
|
-
group_assignments: Optional[List[List[str]]] = None,
|
|
52
|
-
) -> SplitResult:
|
|
53
|
-
"""
|
|
54
|
-
Split a diagram into multiple figures.
|
|
55
|
-
|
|
56
|
-
Parameters
|
|
57
|
-
----------
|
|
58
|
-
spec : DiagramSpec
|
|
59
|
-
Original diagram specification.
|
|
60
|
-
config : SplitConfig, optional
|
|
61
|
-
Split configuration.
|
|
62
|
-
group_assignments : List[List[str]], optional
|
|
63
|
-
Manual group assignments for splitting.
|
|
64
|
-
If provided, overrides automatic detection.
|
|
65
|
-
|
|
66
|
-
Returns
|
|
67
|
-
-------
|
|
68
|
-
SplitResult
|
|
69
|
-
List of split diagram specifications.
|
|
70
|
-
"""
|
|
71
|
-
if config is None:
|
|
72
|
-
config = SplitConfig(enabled=True)
|
|
73
|
-
|
|
74
|
-
# Check if split is needed
|
|
75
|
-
if not config.enabled or len(spec.nodes) <= config.max_nodes:
|
|
76
|
-
return SplitResult(figures=[spec], labels=[""], cut_nodes=set())
|
|
77
|
-
|
|
78
|
-
# Determine groups to split by
|
|
79
|
-
if group_assignments:
|
|
80
|
-
groups = group_assignments
|
|
81
|
-
elif config.strategy == SplitStrategy.BY_GROUPS:
|
|
82
|
-
groups = _split_by_groups(spec, max_nodes=config.max_nodes)
|
|
83
|
-
else: # BY_ARTICULATION
|
|
84
|
-
groups = _split_by_articulation(spec)
|
|
85
|
-
|
|
86
|
-
# Create split figures
|
|
87
|
-
figures = []
|
|
88
|
-
labels = []
|
|
89
|
-
cut_nodes = set()
|
|
90
|
-
|
|
91
|
-
for i, group_nodes in enumerate(groups):
|
|
92
|
-
fig, cuts = _create_split_figure(spec, group_nodes, config)
|
|
93
|
-
figures.append(fig)
|
|
94
|
-
labels.append(chr(ord('A') + i))
|
|
95
|
-
cut_nodes.update(cuts)
|
|
96
|
-
|
|
97
|
-
return SplitResult(figures=figures, labels=labels, cut_nodes=cut_nodes)
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
def _split_by_groups(spec: DiagramSpec, max_nodes: int = 12) -> List[List[str]]:
|
|
101
|
-
"""
|
|
102
|
-
Split by existing layout.groups using greedy packing.
|
|
103
|
-
|
|
104
|
-
Packs groups into figures until max_nodes is exceeded,
|
|
105
|
-
then starts a new figure.
|
|
106
|
-
|
|
107
|
-
Returns list of node ID lists, one per split figure.
|
|
108
|
-
"""
|
|
109
|
-
if not spec.layout.groups:
|
|
110
|
-
# No groups defined - try to split in half
|
|
111
|
-
node_ids = [n.id for n in spec.nodes]
|
|
112
|
-
mid = len(node_ids) // 2
|
|
113
|
-
return [node_ids[:mid], node_ids[mid:]]
|
|
114
|
-
|
|
115
|
-
# Group keys in order
|
|
116
|
-
group_names = list(spec.layout.groups.keys())
|
|
117
|
-
|
|
118
|
-
# Greedy packing: add groups until max_nodes exceeded
|
|
119
|
-
figures = []
|
|
120
|
-
current_figure = []
|
|
121
|
-
current_count = 0
|
|
122
|
-
|
|
123
|
-
for group_name in group_names:
|
|
124
|
-
group_nodes = spec.layout.groups[group_name]
|
|
125
|
-
group_size = len(group_nodes)
|
|
126
|
-
|
|
127
|
-
# If adding this group exceeds max and we have something, start new figure
|
|
128
|
-
if current_count + group_size > max_nodes and current_figure:
|
|
129
|
-
figures.append(current_figure)
|
|
130
|
-
current_figure = []
|
|
131
|
-
current_count = 0
|
|
132
|
-
|
|
133
|
-
# Add group to current figure
|
|
134
|
-
current_figure.extend(group_nodes)
|
|
135
|
-
current_count += group_size
|
|
136
|
-
|
|
137
|
-
# Don't forget the last figure
|
|
138
|
-
if current_figure:
|
|
139
|
-
figures.append(current_figure)
|
|
140
|
-
|
|
141
|
-
# Add ungrouped nodes to first figure
|
|
142
|
-
grouped = set()
|
|
143
|
-
for fig in figures:
|
|
144
|
-
grouped.update(fig)
|
|
145
|
-
for n in spec.nodes:
|
|
146
|
-
if n.id not in grouped:
|
|
147
|
-
if figures:
|
|
148
|
-
figures[0].append(n.id)
|
|
149
|
-
else:
|
|
150
|
-
figures.append([n.id])
|
|
151
|
-
|
|
152
|
-
# Ensure at least 2 figures if we have enough nodes
|
|
153
|
-
if len(figures) == 1 and len(figures[0]) > max_nodes:
|
|
154
|
-
# Force split in half
|
|
155
|
-
nodes = figures[0]
|
|
156
|
-
mid = len(nodes) // 2
|
|
157
|
-
figures = [nodes[:mid], nodes[mid:]]
|
|
158
|
-
|
|
159
|
-
return figures
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
def _split_by_articulation(spec: DiagramSpec) -> List[List[str]]:
|
|
163
|
-
"""
|
|
164
|
-
Split at articulation points (hub nodes).
|
|
165
|
-
|
|
166
|
-
This finds nodes that, if removed, would disconnect the graph.
|
|
167
|
-
These are natural split points for large diagrams.
|
|
168
|
-
"""
|
|
169
|
-
# Build adjacency
|
|
170
|
-
adj: Dict[str, Set[str]] = {n.id: set() for n in spec.nodes}
|
|
171
|
-
for e in spec.edges:
|
|
172
|
-
adj[e.source].add(e.target)
|
|
173
|
-
adj[e.target].add(e.source)
|
|
174
|
-
|
|
175
|
-
# Find node with most connections (hub)
|
|
176
|
-
hub = max(adj.keys(), key=lambda x: len(adj[x]))
|
|
177
|
-
|
|
178
|
-
# BFS from first node, stopping at hub
|
|
179
|
-
visited = {hub} # Block the hub
|
|
180
|
-
node_ids = [n.id for n in spec.nodes if n.id != hub]
|
|
181
|
-
|
|
182
|
-
if not node_ids:
|
|
183
|
-
return [[hub]]
|
|
184
|
-
|
|
185
|
-
# Find components when hub is removed
|
|
186
|
-
components = []
|
|
187
|
-
for start in node_ids:
|
|
188
|
-
if start in visited:
|
|
189
|
-
continue
|
|
190
|
-
component = []
|
|
191
|
-
queue = [start]
|
|
192
|
-
while queue:
|
|
193
|
-
curr = queue.pop(0)
|
|
194
|
-
if curr in visited:
|
|
195
|
-
continue
|
|
196
|
-
visited.add(curr)
|
|
197
|
-
component.append(curr)
|
|
198
|
-
for neighbor in adj[curr]:
|
|
199
|
-
if neighbor not in visited:
|
|
200
|
-
queue.append(neighbor)
|
|
201
|
-
if component:
|
|
202
|
-
components.append(component)
|
|
203
|
-
|
|
204
|
-
# Add hub to each component (as ghost)
|
|
205
|
-
for comp in components:
|
|
206
|
-
comp.append(hub)
|
|
207
|
-
|
|
208
|
-
return components if components else [[n.id for n in spec.nodes]]
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
def _create_split_figure(
|
|
212
|
-
spec: DiagramSpec,
|
|
213
|
-
node_ids: List[str],
|
|
214
|
-
config: SplitConfig,
|
|
215
|
-
) -> Tuple[DiagramSpec, Set[str]]:
|
|
216
|
-
"""
|
|
217
|
-
Create a split figure containing specified nodes.
|
|
218
|
-
|
|
219
|
-
Returns (figure_spec, ghost_node_ids).
|
|
220
|
-
"""
|
|
221
|
-
node_id_set = set(node_ids)
|
|
222
|
-
|
|
223
|
-
# Find edges that cross the boundary
|
|
224
|
-
boundary_nodes = set()
|
|
225
|
-
for edge in spec.edges:
|
|
226
|
-
src_in = edge.source in node_id_set
|
|
227
|
-
tgt_in = edge.target in node_id_set
|
|
228
|
-
if src_in and not tgt_in:
|
|
229
|
-
if config.keep_hubs:
|
|
230
|
-
boundary_nodes.add(edge.target)
|
|
231
|
-
elif tgt_in and not src_in:
|
|
232
|
-
if config.keep_hubs:
|
|
233
|
-
boundary_nodes.add(edge.source)
|
|
234
|
-
|
|
235
|
-
# Create new spec
|
|
236
|
-
new_spec = DiagramSpec(
|
|
237
|
-
type=spec.type,
|
|
238
|
-
title=spec.title,
|
|
239
|
-
paper=deepcopy(spec.paper),
|
|
240
|
-
layout=deepcopy(spec.layout),
|
|
241
|
-
theme=dict(spec.theme),
|
|
242
|
-
)
|
|
243
|
-
|
|
244
|
-
# Filter nodes
|
|
245
|
-
node_map = {n.id: n for n in spec.nodes}
|
|
246
|
-
for node_id in node_ids:
|
|
247
|
-
if node_id in node_map:
|
|
248
|
-
new_spec.nodes.append(deepcopy(node_map[node_id]))
|
|
249
|
-
|
|
250
|
-
# Add ghost nodes (boundary nodes not in this split)
|
|
251
|
-
for ghost_id in boundary_nodes:
|
|
252
|
-
if ghost_id in node_map and ghost_id not in node_id_set:
|
|
253
|
-
ghost = deepcopy(node_map[ghost_id])
|
|
254
|
-
ghost.emphasis = config.ghost_style
|
|
255
|
-
ghost.label = f"→ {ghost.label}" # Mark as continuation
|
|
256
|
-
new_spec.nodes.append(ghost)
|
|
257
|
-
|
|
258
|
-
# Filter edges
|
|
259
|
-
all_ids = node_id_set | boundary_nodes
|
|
260
|
-
for edge in spec.edges:
|
|
261
|
-
if edge.source in all_ids and edge.target in all_ids:
|
|
262
|
-
new_spec.edges.append(deepcopy(edge))
|
|
263
|
-
|
|
264
|
-
# Filter groups
|
|
265
|
-
new_spec.layout.groups = {}
|
|
266
|
-
for group_name, group_nodes in spec.layout.groups.items():
|
|
267
|
-
filtered = [n for n in group_nodes if n in all_ids]
|
|
268
|
-
if filtered:
|
|
269
|
-
new_spec.layout.groups[group_name] = filtered
|
|
270
|
-
|
|
271
|
-
# Filter layers
|
|
272
|
-
new_spec.layout.layers = []
|
|
273
|
-
for layer in spec.layout.layers:
|
|
274
|
-
filtered = [n for n in layer if n in all_ids]
|
|
275
|
-
if filtered:
|
|
276
|
-
new_spec.layout.layers.append(filtered)
|
|
277
|
-
|
|
278
|
-
return new_spec, boundary_nodes
|
scitex/plt/_mcp/__init__.py
DELETED