scitex 2.15.1__py3-none-any.whl → 2.15.2__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 +68 -61
- scitex/_mcp_tools/introspect.py +42 -23
- scitex/_mcp_tools/template.py +24 -0
- scitex/ai/classification/timeseries/_TimeSeriesSlidingWindowSplit.py +30 -1550
- scitex/ai/classification/timeseries/_sliding_window_core.py +467 -0
- scitex/ai/classification/timeseries/_sliding_window_plotting.py +369 -0
- scitex/audio/__init__.py +2 -2
- scitex/audio/_tts.py +18 -10
- scitex/audio/engines/base.py +17 -10
- scitex/audio/engines/elevenlabs_engine.py +1 -1
- scitex/canvas/editor/flask_editor/_core/__init__.py +27 -0
- scitex/canvas/editor/flask_editor/_core/_bbox_extraction.py +200 -0
- scitex/canvas/editor/flask_editor/_core/_editor.py +173 -0
- scitex/canvas/editor/flask_editor/_core/_export_helpers.py +353 -0
- scitex/canvas/editor/flask_editor/_core/_routes_basic.py +190 -0
- scitex/canvas/editor/flask_editor/_core/_routes_export.py +332 -0
- scitex/canvas/editor/flask_editor/_core/_routes_panels.py +252 -0
- scitex/canvas/editor/flask_editor/_core/_routes_save.py +218 -0
- scitex/canvas/editor/flask_editor/_core.py +25 -1684
- scitex/cli/introspect.py +112 -74
- scitex/cli/main.py +2 -0
- scitex/cli/plt.py +357 -0
- scitex/cli/repro.py +15 -8
- scitex/cli/resource.py +15 -8
- scitex/cli/scholar/__init__.py +15 -8
- scitex/cli/social.py +6 -6
- scitex/cli/stats.py +15 -8
- scitex/cli/template.py +129 -12
- scitex/cli/tex.py +15 -8
- scitex/cli/writer.py +15 -8
- scitex/cloud/__init__.py +41 -2
- scitex/config/_env_registry.py +84 -19
- scitex/context/__init__.py +22 -0
- scitex/dev/__init__.py +20 -1
- scitex/gen/__init__.py +50 -14
- scitex/gen/_list_packages.py +4 -4
- scitex/introspect/__init__.py +16 -9
- scitex/introspect/_core.py +7 -8
- scitex/{gen/_inspect_module.py → introspect/_list_api.py} +43 -54
- scitex/introspect/_mcp/__init__.py +10 -6
- scitex/introspect/_mcp/handlers.py +37 -12
- scitex/introspect/_members.py +7 -3
- scitex/introspect/_signature.py +3 -3
- scitex/introspect/_source.py +2 -2
- scitex/io/_save.py +1 -2
- scitex/logging/_formatters.py +19 -9
- scitex/mcp_server.py +1 -1
- scitex/os/__init__.py +4 -0
- scitex/{gen → os}/_check_host.py +4 -5
- scitex/plt/__init__.py +11 -14
- scitex/session/__init__.py +26 -7
- scitex/session/_decorator.py +1 -1
- scitex/sh/__init__.py +7 -4
- scitex/social/__init__.py +10 -8
- scitex/stats/_mcp/_handlers/__init__.py +31 -0
- scitex/stats/_mcp/_handlers/_corrections.py +113 -0
- scitex/stats/_mcp/_handlers/_descriptive.py +78 -0
- scitex/stats/_mcp/_handlers/_effect_size.py +106 -0
- scitex/stats/_mcp/_handlers/_format.py +94 -0
- scitex/stats/_mcp/_handlers/_normality.py +110 -0
- scitex/stats/_mcp/_handlers/_posthoc.py +224 -0
- scitex/stats/_mcp/_handlers/_power.py +247 -0
- scitex/stats/_mcp/_handlers/_recommend.py +102 -0
- scitex/stats/_mcp/_handlers/_run_test.py +279 -0
- scitex/stats/_mcp/_handlers/_stars.py +48 -0
- scitex/stats/_mcp/handlers.py +19 -1171
- scitex/stats/auto/_stat_style.py +175 -0
- scitex/stats/auto/_style_definitions.py +411 -0
- scitex/stats/auto/_styles.py +22 -620
- scitex/stats/descriptive/__init__.py +11 -8
- scitex/stats/descriptive/_ci.py +39 -0
- scitex/stats/power/_power.py +15 -4
- scitex/str/__init__.py +2 -1
- scitex/str/_title_case.py +63 -0
- scitex/template/__init__.py +25 -10
- scitex/template/_code_templates.py +147 -0
- scitex/template/_mcp/handlers.py +81 -0
- scitex/template/_mcp/tool_schemas.py +55 -0
- scitex/template/_templates/__init__.py +51 -0
- scitex/template/_templates/audio.py +233 -0
- scitex/template/_templates/canvas.py +312 -0
- scitex/template/_templates/capture.py +268 -0
- scitex/template/_templates/config.py +43 -0
- scitex/template/_templates/diagram.py +294 -0
- scitex/template/_templates/io.py +107 -0
- scitex/template/_templates/module.py +53 -0
- scitex/template/_templates/plt.py +202 -0
- scitex/template/_templates/scholar.py +267 -0
- scitex/template/_templates/session.py +130 -0
- scitex/template/_templates/session_minimal.py +43 -0
- scitex/template/_templates/session_plot.py +67 -0
- scitex/template/_templates/session_stats.py +77 -0
- scitex/template/_templates/stats.py +323 -0
- scitex/template/_templates/writer.py +296 -0
- scitex/ui/_backends/_email.py +10 -2
- scitex/ui/_backends/_webhook.py +5 -1
- scitex/web/_search_pubmed.py +10 -6
- {scitex-2.15.1.dist-info → scitex-2.15.2.dist-info}/METADATA +1 -1
- {scitex-2.15.1.dist-info → scitex-2.15.2.dist-info}/RECORD +105 -64
- scitex/gen/_ci.py +0 -12
- scitex/gen/_title_case.py +0 -89
- /scitex/{gen → context}/_detect_environment.py +0 -0
- /scitex/{gen → context}/_get_notebook_path.py +0 -0
- /scitex/{gen/_shell.py → sh/_shell_legacy.py} +0 -0
- {scitex-2.15.1.dist-info → scitex-2.15.2.dist-info}/WHEEL +0 -0
- {scitex-2.15.1.dist-info → scitex-2.15.2.dist-info}/entry_points.txt +0 -0
- {scitex-2.15.1.dist-info → scitex-2.15.2.dist-info}/licenses/LICENSE +0 -0
scitex/cli/introspect.py
CHANGED
|
@@ -23,20 +23,20 @@ def introspect(ctx, help_recursive):
|
|
|
23
23
|
|
|
24
24
|
\b
|
|
25
25
|
IPython-like introspection for any Python package:
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
26
|
+
q - Function/class signature (like func?)
|
|
27
|
+
qq - Full source code (like func??)
|
|
28
|
+
dir - List module/class members (like dir())
|
|
29
|
+
api - Full module API tree
|
|
30
|
+
docstring - Extract docstrings
|
|
31
|
+
exports - Show __all__ exports
|
|
32
|
+
examples - Find usage examples
|
|
32
33
|
|
|
33
34
|
\b
|
|
34
35
|
Examples:
|
|
35
|
-
scitex introspect
|
|
36
|
-
scitex introspect
|
|
37
|
-
scitex introspect
|
|
38
|
-
scitex introspect
|
|
39
|
-
scitex introspect exports scitex.audio
|
|
36
|
+
scitex introspect q scitex.plt.plot
|
|
37
|
+
scitex introspect qq scitex.stats.run_test --max-lines 50
|
|
38
|
+
scitex introspect dir scitex.plt --kind functions
|
|
39
|
+
scitex introspect api scitex --max-depth 2
|
|
40
40
|
"""
|
|
41
41
|
if help_recursive:
|
|
42
42
|
from . import print_help_recursive
|
|
@@ -52,19 +52,19 @@ def introspect(ctx, help_recursive):
|
|
|
52
52
|
@click.option("--no-defaults", is_flag=True, help="Exclude default values")
|
|
53
53
|
@click.option("--no-annotations", is_flag=True, help="Exclude type annotations")
|
|
54
54
|
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
|
|
55
|
-
def
|
|
55
|
+
def q(dotted_path, no_defaults, no_annotations, as_json):
|
|
56
56
|
"""
|
|
57
57
|
Get function/class signature (like IPython's func?)
|
|
58
58
|
|
|
59
59
|
\b
|
|
60
60
|
Examples:
|
|
61
|
-
scitex introspect
|
|
62
|
-
scitex introspect
|
|
63
|
-
scitex introspect
|
|
61
|
+
scitex introspect q scitex.plt.plot
|
|
62
|
+
scitex introspect q scitex.audio.speak --json
|
|
63
|
+
scitex introspect q json.dumps
|
|
64
64
|
"""
|
|
65
|
-
from scitex.introspect import
|
|
65
|
+
from scitex.introspect import q as get_q
|
|
66
66
|
|
|
67
|
-
result =
|
|
67
|
+
result = get_q(
|
|
68
68
|
dotted_path,
|
|
69
69
|
include_defaults=not no_defaults,
|
|
70
70
|
include_annotations=not no_annotations,
|
|
@@ -89,68 +89,23 @@ def signature(dotted_path, no_defaults, no_annotations, as_json):
|
|
|
89
89
|
click.echo(line)
|
|
90
90
|
|
|
91
91
|
|
|
92
|
-
@introspect.command()
|
|
93
|
-
@click.argument("dotted_path")
|
|
94
|
-
@click.option(
|
|
95
|
-
"--format",
|
|
96
|
-
"-f",
|
|
97
|
-
type=click.Choice(["raw", "parsed", "summary"]),
|
|
98
|
-
default="raw",
|
|
99
|
-
help="Output format",
|
|
100
|
-
)
|
|
101
|
-
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
|
|
102
|
-
def docstring(dotted_path, format, as_json):
|
|
103
|
-
"""
|
|
104
|
-
Get docstring of a Python object
|
|
105
|
-
|
|
106
|
-
\b
|
|
107
|
-
Formats:
|
|
108
|
-
raw - Full docstring as-is
|
|
109
|
-
parsed - Parse into sections (summary, parameters, returns, etc.)
|
|
110
|
-
summary - First line/paragraph only
|
|
111
|
-
|
|
112
|
-
\b
|
|
113
|
-
Examples:
|
|
114
|
-
scitex introspect docstring scitex.plt.plot
|
|
115
|
-
scitex introspect docstring scitex.audio.speak --format parsed
|
|
116
|
-
"""
|
|
117
|
-
from scitex.introspect import get_docstring
|
|
118
|
-
|
|
119
|
-
result = get_docstring(dotted_path, format=format)
|
|
120
|
-
|
|
121
|
-
if not result.get("success", False):
|
|
122
|
-
click.secho(f"Error: {result.get('error', 'Unknown error')}", fg="red")
|
|
123
|
-
sys.exit(1)
|
|
124
|
-
|
|
125
|
-
if as_json:
|
|
126
|
-
click.echo(json.dumps(result, indent=2))
|
|
127
|
-
else:
|
|
128
|
-
click.echo(result["docstring"])
|
|
129
|
-
if format == "parsed" and result.get("sections"):
|
|
130
|
-
click.echo("\n--- Parsed Sections ---")
|
|
131
|
-
for key, value in result["sections"].items():
|
|
132
|
-
if value:
|
|
133
|
-
click.secho(f"\n[{key}]", fg="cyan", bold=True)
|
|
134
|
-
click.echo(value)
|
|
135
|
-
|
|
136
|
-
|
|
137
92
|
@introspect.command()
|
|
138
93
|
@click.argument("dotted_path")
|
|
139
94
|
@click.option("--max-lines", "-n", type=int, help="Limit output to N lines")
|
|
140
95
|
@click.option("--no-decorators", is_flag=True, help="Exclude decorator lines")
|
|
141
96
|
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
|
|
142
|
-
def
|
|
97
|
+
def qq(dotted_path, max_lines, no_decorators, as_json):
|
|
143
98
|
"""
|
|
144
99
|
Get source code of a Python object (like IPython's func??)
|
|
145
100
|
|
|
146
101
|
\b
|
|
147
102
|
Examples:
|
|
148
|
-
scitex introspect
|
|
149
|
-
scitex introspect
|
|
103
|
+
scitex introspect qq scitex.plt.plot
|
|
104
|
+
scitex introspect qq scitex.audio.speak --max-lines 50
|
|
150
105
|
"""
|
|
151
|
-
from scitex.introspect import
|
|
106
|
+
from scitex.introspect import qq as get_qq
|
|
152
107
|
|
|
153
|
-
result =
|
|
108
|
+
result = get_qq(
|
|
154
109
|
dotted_path,
|
|
155
110
|
max_lines=max_lines,
|
|
156
111
|
include_decorators=not no_decorators,
|
|
@@ -169,7 +124,7 @@ def source(dotted_path, max_lines, no_decorators, as_json):
|
|
|
169
124
|
click.echo(result["source"])
|
|
170
125
|
|
|
171
126
|
|
|
172
|
-
@introspect.command()
|
|
127
|
+
@introspect.command("dir")
|
|
173
128
|
@click.argument("dotted_path")
|
|
174
129
|
@click.option(
|
|
175
130
|
"--filter",
|
|
@@ -186,19 +141,19 @@ def source(dotted_path, max_lines, no_decorators, as_json):
|
|
|
186
141
|
)
|
|
187
142
|
@click.option("--inherited", is_flag=True, help="Include inherited members (classes)")
|
|
188
143
|
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
|
|
189
|
-
def
|
|
144
|
+
def dir_cmd(dotted_path, filter, kind, inherited, as_json):
|
|
190
145
|
"""
|
|
191
146
|
List members of a module or class (like dir())
|
|
192
147
|
|
|
193
148
|
\b
|
|
194
149
|
Examples:
|
|
195
|
-
scitex introspect
|
|
196
|
-
scitex introspect
|
|
197
|
-
scitex introspect
|
|
150
|
+
scitex introspect dir scitex.plt
|
|
151
|
+
scitex introspect dir scitex.audio --kind functions
|
|
152
|
+
scitex introspect dir scitex.plt.AxisWrapper --filter all
|
|
198
153
|
"""
|
|
199
|
-
from scitex.introspect import
|
|
154
|
+
from scitex.introspect import dir as get_dir
|
|
200
155
|
|
|
201
|
-
result =
|
|
156
|
+
result = get_dir(
|
|
202
157
|
dotted_path,
|
|
203
158
|
filter=filter,
|
|
204
159
|
kind=kind,
|
|
@@ -220,6 +175,89 @@ def members(dotted_path, filter, kind, inherited, as_json):
|
|
|
220
175
|
click.echo(f" {kind_str} {name_str}{summary}")
|
|
221
176
|
|
|
222
177
|
|
|
178
|
+
@introspect.command()
|
|
179
|
+
@click.argument("dotted_path")
|
|
180
|
+
@click.option("--max-depth", "-d", type=int, default=5, help="Max recursion depth")
|
|
181
|
+
@click.option("--docstring", is_flag=True, help="Include docstrings")
|
|
182
|
+
@click.option("--root-only", is_flag=True, help="Show only root-level items")
|
|
183
|
+
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
|
|
184
|
+
def api(dotted_path, max_depth, docstring, root_only, as_json):
|
|
185
|
+
"""
|
|
186
|
+
List the full API tree of a module recursively
|
|
187
|
+
|
|
188
|
+
\b
|
|
189
|
+
Examples:
|
|
190
|
+
scitex introspect api scitex --max-depth 2
|
|
191
|
+
scitex introspect api scitex.plt --docstring
|
|
192
|
+
scitex introspect api scitex.audio --root-only
|
|
193
|
+
"""
|
|
194
|
+
from scitex.introspect import list_api
|
|
195
|
+
|
|
196
|
+
df = list_api(
|
|
197
|
+
dotted_path,
|
|
198
|
+
max_depth=max_depth,
|
|
199
|
+
docstring=docstring,
|
|
200
|
+
root_only=root_only,
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
if as_json:
|
|
204
|
+
click.echo(json.dumps(df.to_dict(orient="records"), indent=2))
|
|
205
|
+
else:
|
|
206
|
+
click.secho(f"API tree of {dotted_path} ({len(df)} items):", fg="cyan")
|
|
207
|
+
for _, row in df.iterrows():
|
|
208
|
+
indent = " " * row["Depth"]
|
|
209
|
+
type_str = click.style(f"[{row['Type']}]", fg="yellow")
|
|
210
|
+
name = row["Name"].split(".")[-1]
|
|
211
|
+
name_str = click.style(name, fg="green", bold=True)
|
|
212
|
+
doc = f" - {row['Docstring'][:50]}..." if row.get("Docstring") else ""
|
|
213
|
+
click.echo(f"{indent}{type_str} {name_str}{doc}")
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
@introspect.command()
|
|
217
|
+
@click.argument("dotted_path")
|
|
218
|
+
@click.option(
|
|
219
|
+
"--format",
|
|
220
|
+
"-f",
|
|
221
|
+
type=click.Choice(["raw", "parsed", "summary"]),
|
|
222
|
+
default="raw",
|
|
223
|
+
help="Output format",
|
|
224
|
+
)
|
|
225
|
+
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
|
|
226
|
+
def docstring(dotted_path, format, as_json):
|
|
227
|
+
"""
|
|
228
|
+
Get docstring of a Python object
|
|
229
|
+
|
|
230
|
+
\b
|
|
231
|
+
Formats:
|
|
232
|
+
raw - Full docstring as-is
|
|
233
|
+
parsed - Parse into sections (summary, parameters, returns, etc.)
|
|
234
|
+
summary - First line/paragraph only
|
|
235
|
+
|
|
236
|
+
\b
|
|
237
|
+
Examples:
|
|
238
|
+
scitex introspect docstring scitex.plt.plot
|
|
239
|
+
scitex introspect docstring scitex.audio.speak --format parsed
|
|
240
|
+
"""
|
|
241
|
+
from scitex.introspect import get_docstring
|
|
242
|
+
|
|
243
|
+
result = get_docstring(dotted_path, format=format)
|
|
244
|
+
|
|
245
|
+
if not result.get("success", False):
|
|
246
|
+
click.secho(f"Error: {result.get('error', 'Unknown error')}", fg="red")
|
|
247
|
+
sys.exit(1)
|
|
248
|
+
|
|
249
|
+
if as_json:
|
|
250
|
+
click.echo(json.dumps(result, indent=2))
|
|
251
|
+
else:
|
|
252
|
+
click.echo(result["docstring"])
|
|
253
|
+
if format == "parsed" and result.get("sections"):
|
|
254
|
+
click.echo("\n--- Parsed Sections ---")
|
|
255
|
+
for key, value in result["sections"].items():
|
|
256
|
+
if value:
|
|
257
|
+
click.secho(f"\n[{key}]", fg="cyan", bold=True)
|
|
258
|
+
click.echo(value)
|
|
259
|
+
|
|
260
|
+
|
|
223
261
|
@introspect.command()
|
|
224
262
|
@click.argument("dotted_path")
|
|
225
263
|
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
|
scitex/cli/main.py
CHANGED
|
@@ -17,6 +17,7 @@ from . import (
|
|
|
17
17
|
convert,
|
|
18
18
|
introspect,
|
|
19
19
|
mcp,
|
|
20
|
+
plt,
|
|
20
21
|
repro,
|
|
21
22
|
resource,
|
|
22
23
|
scholar,
|
|
@@ -71,6 +72,7 @@ cli.add_command(config.config)
|
|
|
71
72
|
cli.add_command(convert.convert)
|
|
72
73
|
cli.add_command(introspect.introspect)
|
|
73
74
|
cli.add_command(mcp.mcp)
|
|
75
|
+
cli.add_command(plt.plt)
|
|
74
76
|
cli.add_command(repro.repro)
|
|
75
77
|
cli.add_command(resource.resource)
|
|
76
78
|
cli.add_command(scholar.scholar)
|
scitex/cli/plt.py
ADDED
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Timestamp: 2026-01-25
|
|
3
|
+
# File: /home/ywatanabe/proj/scitex-python/src/scitex/cli/plt.py
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
SciTeX CLI - Plot Commands
|
|
7
|
+
|
|
8
|
+
Thin wrapper around figrecipe CLI for reproducible matplotlib figures.
|
|
9
|
+
All commands delegate to figrecipe for reproducibility.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import subprocess
|
|
13
|
+
import sys
|
|
14
|
+
|
|
15
|
+
import click
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _run_figrecipe(*args) -> int:
|
|
19
|
+
"""Run figrecipe CLI command."""
|
|
20
|
+
cmd = [sys.executable, "-m", "figrecipe"]
|
|
21
|
+
cmd.extend(args)
|
|
22
|
+
|
|
23
|
+
result = subprocess.run(cmd)
|
|
24
|
+
return result.returncode
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _check_figrecipe() -> bool:
|
|
28
|
+
"""Check if figrecipe is available."""
|
|
29
|
+
try:
|
|
30
|
+
import figrecipe # noqa: F401
|
|
31
|
+
|
|
32
|
+
return True
|
|
33
|
+
except ImportError:
|
|
34
|
+
return False
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@click.group(
|
|
38
|
+
context_settings={"help_option_names": ["-h", "--help"]},
|
|
39
|
+
invoke_without_command=True,
|
|
40
|
+
)
|
|
41
|
+
@click.option("--help-recursive", is_flag=True, help="Show help for all subcommands")
|
|
42
|
+
@click.pass_context
|
|
43
|
+
def plt(ctx, help_recursive):
|
|
44
|
+
"""
|
|
45
|
+
Plot and figure management (powered by figrecipe)
|
|
46
|
+
|
|
47
|
+
\b
|
|
48
|
+
Commands:
|
|
49
|
+
plot - Create figure from YAML/JSON spec
|
|
50
|
+
edit - Launch interactive GUI editor
|
|
51
|
+
compose - Combine multiple figures
|
|
52
|
+
crop - Crop whitespace from images
|
|
53
|
+
reproduce - Reproduce figure from recipe
|
|
54
|
+
validate - Validate recipe reproducibility
|
|
55
|
+
diagram - Create diagrams (flowcharts, etc.)
|
|
56
|
+
|
|
57
|
+
\b
|
|
58
|
+
Examples:
|
|
59
|
+
scitex plt plot spec.yaml -o fig.png
|
|
60
|
+
scitex plt edit
|
|
61
|
+
scitex plt compose a.png b.png -o combined.png
|
|
62
|
+
scitex plt reproduce recipe.yaml
|
|
63
|
+
|
|
64
|
+
\b
|
|
65
|
+
Note: Wraps figrecipe CLI. Run 'figrecipe --help' for full options.
|
|
66
|
+
"""
|
|
67
|
+
if not _check_figrecipe():
|
|
68
|
+
click.secho("Error: figrecipe not installed", fg="red", err=True)
|
|
69
|
+
click.echo("\nInstall with: pip install figrecipe")
|
|
70
|
+
ctx.exit(1)
|
|
71
|
+
|
|
72
|
+
if help_recursive:
|
|
73
|
+
from . import print_help_recursive
|
|
74
|
+
|
|
75
|
+
print_help_recursive(ctx, plt)
|
|
76
|
+
ctx.exit(0)
|
|
77
|
+
elif ctx.invoked_subcommand is None:
|
|
78
|
+
click.echo(ctx.get_help())
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@plt.command()
|
|
82
|
+
@click.argument("spec_file", type=click.Path(exists=True))
|
|
83
|
+
@click.option("-o", "--output", required=True, help="Output file path")
|
|
84
|
+
@click.option("--dpi", type=int, default=300, help="DPI for raster output")
|
|
85
|
+
@click.option("--no-recipe", is_flag=True, help="Don't save YAML recipe")
|
|
86
|
+
def plot(spec_file, output, dpi, no_recipe):
|
|
87
|
+
"""
|
|
88
|
+
Create a figure from a declarative YAML/JSON spec.
|
|
89
|
+
|
|
90
|
+
\b
|
|
91
|
+
Examples:
|
|
92
|
+
scitex plt plot spec.yaml -o fig.png
|
|
93
|
+
scitex plt plot spec.json -o fig.pdf --dpi 600
|
|
94
|
+
"""
|
|
95
|
+
args = ["plot", spec_file, "-o", output, "--dpi", str(dpi)]
|
|
96
|
+
if no_recipe:
|
|
97
|
+
args.append("--no-recipe")
|
|
98
|
+
sys.exit(_run_figrecipe(*args))
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@plt.command()
|
|
102
|
+
@click.argument("recipe_file", type=click.Path(exists=True), required=False)
|
|
103
|
+
def edit(recipe_file):
|
|
104
|
+
"""
|
|
105
|
+
Launch interactive GUI editor.
|
|
106
|
+
|
|
107
|
+
\b
|
|
108
|
+
Examples:
|
|
109
|
+
scitex plt edit # Open blank editor
|
|
110
|
+
scitex plt edit recipe.yaml # Open existing recipe
|
|
111
|
+
"""
|
|
112
|
+
args = ["edit"]
|
|
113
|
+
if recipe_file:
|
|
114
|
+
args.append(recipe_file)
|
|
115
|
+
sys.exit(_run_figrecipe(*args))
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@plt.command()
|
|
119
|
+
@click.argument("sources", nargs=-1, required=True, type=click.Path(exists=True))
|
|
120
|
+
@click.option("-o", "--output", required=True, help="Output file path")
|
|
121
|
+
@click.option(
|
|
122
|
+
"-l",
|
|
123
|
+
"--layout",
|
|
124
|
+
type=click.Choice(["horizontal", "vertical", "grid"]),
|
|
125
|
+
default="horizontal",
|
|
126
|
+
help="Layout mode",
|
|
127
|
+
)
|
|
128
|
+
@click.option("--gap", type=float, default=5, help="Gap between panels (mm)")
|
|
129
|
+
@click.option("--dpi", type=int, default=300, help="DPI for output")
|
|
130
|
+
@click.option("--no-labels", is_flag=True, help="Don't add panel labels (A, B, C)")
|
|
131
|
+
@click.option("--caption", help="Figure caption")
|
|
132
|
+
def compose(sources, output, layout, gap, dpi, no_labels, caption):
|
|
133
|
+
"""
|
|
134
|
+
Compose multiple figures into one.
|
|
135
|
+
|
|
136
|
+
\b
|
|
137
|
+
Examples:
|
|
138
|
+
scitex plt compose a.png b.png -o combined.png
|
|
139
|
+
scitex plt compose *.png -o fig.pdf --layout grid
|
|
140
|
+
scitex plt compose a.yaml b.yaml -o fig.png --gap 10
|
|
141
|
+
"""
|
|
142
|
+
args = ["compose"]
|
|
143
|
+
args.extend(sources)
|
|
144
|
+
args.extend(
|
|
145
|
+
["-o", output, "--layout", layout, "--gap", str(gap), "--dpi", str(dpi)]
|
|
146
|
+
)
|
|
147
|
+
if no_labels:
|
|
148
|
+
args.append("--no-labels")
|
|
149
|
+
if caption:
|
|
150
|
+
args.extend(["--caption", caption])
|
|
151
|
+
sys.exit(_run_figrecipe(*args))
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
@plt.command()
|
|
155
|
+
@click.argument("input_file", type=click.Path(exists=True))
|
|
156
|
+
@click.option("-o", "--output", help="Output file (default: overwrites input)")
|
|
157
|
+
@click.option("--margin", type=float, default=1, help="Margin to keep (mm)")
|
|
158
|
+
def crop(input_file, output, margin):
|
|
159
|
+
"""
|
|
160
|
+
Crop whitespace from an image.
|
|
161
|
+
|
|
162
|
+
\b
|
|
163
|
+
Examples:
|
|
164
|
+
scitex plt crop figure.png
|
|
165
|
+
scitex plt crop figure.png -o cropped.png --margin 2
|
|
166
|
+
"""
|
|
167
|
+
args = ["crop", input_file, "--margin", str(margin)]
|
|
168
|
+
if output:
|
|
169
|
+
args.extend(["-o", output])
|
|
170
|
+
sys.exit(_run_figrecipe(*args))
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
@plt.command()
|
|
174
|
+
@click.argument("recipe_file", type=click.Path(exists=True))
|
|
175
|
+
@click.option("-o", "--output", help="Output file path")
|
|
176
|
+
@click.option(
|
|
177
|
+
"--format", "fmt", type=click.Choice(["png", "pdf", "svg"]), help="Output format"
|
|
178
|
+
)
|
|
179
|
+
@click.option("--dpi", type=int, default=300, help="DPI for raster output")
|
|
180
|
+
def reproduce(recipe_file, output, fmt, dpi):
|
|
181
|
+
"""
|
|
182
|
+
Reproduce a figure from a YAML recipe.
|
|
183
|
+
|
|
184
|
+
\b
|
|
185
|
+
Examples:
|
|
186
|
+
scitex plt reproduce recipe.yaml
|
|
187
|
+
scitex plt reproduce recipe.yaml -o new_fig.pdf
|
|
188
|
+
"""
|
|
189
|
+
args = ["reproduce", recipe_file, "--dpi", str(dpi)]
|
|
190
|
+
if output:
|
|
191
|
+
args.extend(["-o", output])
|
|
192
|
+
if fmt:
|
|
193
|
+
args.extend(["--format", fmt])
|
|
194
|
+
sys.exit(_run_figrecipe(*args))
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@plt.command()
|
|
198
|
+
@click.argument("recipe_file", type=click.Path(exists=True))
|
|
199
|
+
@click.option("--threshold", type=float, default=100, help="MSE threshold")
|
|
200
|
+
def validate(recipe_file, threshold):
|
|
201
|
+
"""
|
|
202
|
+
Validate that a recipe reproduces its original figure.
|
|
203
|
+
|
|
204
|
+
\b
|
|
205
|
+
Examples:
|
|
206
|
+
scitex plt validate recipe.yaml
|
|
207
|
+
scitex plt validate recipe.yaml --threshold 50
|
|
208
|
+
"""
|
|
209
|
+
args = ["validate", recipe_file, "--threshold", str(threshold)]
|
|
210
|
+
sys.exit(_run_figrecipe(*args))
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
@plt.command()
|
|
214
|
+
@click.argument("spec_file", type=click.Path(exists=True))
|
|
215
|
+
@click.option("-o", "--output", help="Output file path")
|
|
216
|
+
@click.option(
|
|
217
|
+
"--format", "fmt", type=click.Choice(["mermaid", "graphviz"]), default="mermaid"
|
|
218
|
+
)
|
|
219
|
+
def diagram(spec_file, output, fmt):
|
|
220
|
+
"""
|
|
221
|
+
Create diagrams (flowcharts, pipelines, etc.)
|
|
222
|
+
|
|
223
|
+
\b
|
|
224
|
+
Examples:
|
|
225
|
+
scitex plt diagram workflow.yaml
|
|
226
|
+
scitex plt diagram pipeline.yaml -o diagram.png --format graphviz
|
|
227
|
+
"""
|
|
228
|
+
args = ["diagram", spec_file, "--format", fmt]
|
|
229
|
+
if output:
|
|
230
|
+
args.extend(["-o", output])
|
|
231
|
+
sys.exit(_run_figrecipe(*args))
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
@plt.command()
|
|
235
|
+
@click.argument("recipe_file", type=click.Path(exists=True))
|
|
236
|
+
@click.option("-v", "--verbose", is_flag=True, help="Show detailed info")
|
|
237
|
+
def info(recipe_file, verbose):
|
|
238
|
+
"""
|
|
239
|
+
Show information about a recipe.
|
|
240
|
+
|
|
241
|
+
\b
|
|
242
|
+
Examples:
|
|
243
|
+
scitex plt info recipe.yaml
|
|
244
|
+
scitex plt info recipe.yaml --verbose
|
|
245
|
+
"""
|
|
246
|
+
args = ["info", recipe_file]
|
|
247
|
+
if verbose:
|
|
248
|
+
args.append("--verbose")
|
|
249
|
+
sys.exit(_run_figrecipe(*args))
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
@plt.command()
|
|
253
|
+
@click.argument("recipe_file", type=click.Path(exists=True))
|
|
254
|
+
@click.option("-o", "--output", help="Output file for extracted data")
|
|
255
|
+
def extract(recipe_file, output):
|
|
256
|
+
"""
|
|
257
|
+
Extract plotted data arrays from a recipe.
|
|
258
|
+
|
|
259
|
+
\b
|
|
260
|
+
Examples:
|
|
261
|
+
scitex plt extract recipe.yaml
|
|
262
|
+
scitex plt extract recipe.yaml -o data.json
|
|
263
|
+
"""
|
|
264
|
+
args = ["extract", recipe_file]
|
|
265
|
+
if output:
|
|
266
|
+
args.extend(["-o", output])
|
|
267
|
+
sys.exit(_run_figrecipe(*args))
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
@plt.command()
|
|
271
|
+
@click.argument("action", type=click.Choice(["list", "show", "set"]), default="list")
|
|
272
|
+
@click.argument("style_name", required=False)
|
|
273
|
+
def style(action, style_name):
|
|
274
|
+
"""
|
|
275
|
+
Manage figure styles and presets.
|
|
276
|
+
|
|
277
|
+
\b
|
|
278
|
+
Examples:
|
|
279
|
+
scitex plt style list # List available styles
|
|
280
|
+
scitex plt style show nature # Show style details
|
|
281
|
+
scitex plt style set publication # Set default style
|
|
282
|
+
"""
|
|
283
|
+
args = ["style", action]
|
|
284
|
+
if style_name:
|
|
285
|
+
args.append(style_name)
|
|
286
|
+
sys.exit(_run_figrecipe(*args))
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
@plt.command()
|
|
290
|
+
@click.option("--check", is_flag=True, help="Check font availability")
|
|
291
|
+
def fonts(check):
|
|
292
|
+
"""
|
|
293
|
+
List or check available fonts.
|
|
294
|
+
|
|
295
|
+
\b
|
|
296
|
+
Examples:
|
|
297
|
+
scitex plt fonts
|
|
298
|
+
scitex plt fonts --check
|
|
299
|
+
"""
|
|
300
|
+
args = ["fonts"]
|
|
301
|
+
if check:
|
|
302
|
+
args.append("--check")
|
|
303
|
+
sys.exit(_run_figrecipe(*args))
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
@plt.command()
|
|
307
|
+
@click.argument("input_file", type=click.Path(exists=True))
|
|
308
|
+
@click.option("-o", "--output", required=True, help="Output file path")
|
|
309
|
+
@click.option("--format", "fmt", help="Output format (inferred from extension)")
|
|
310
|
+
def convert(input_file, output, fmt):
|
|
311
|
+
"""
|
|
312
|
+
Convert between figure formats.
|
|
313
|
+
|
|
314
|
+
\b
|
|
315
|
+
Examples:
|
|
316
|
+
scitex plt convert fig.png -o fig.pdf
|
|
317
|
+
scitex plt convert fig.svg -o fig.png
|
|
318
|
+
"""
|
|
319
|
+
args = ["convert", input_file, "-o", output]
|
|
320
|
+
if fmt:
|
|
321
|
+
args.extend(["--format", fmt])
|
|
322
|
+
sys.exit(_run_figrecipe(*args))
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
@plt.command()
|
|
326
|
+
@click.option(
|
|
327
|
+
"-t",
|
|
328
|
+
"--transport",
|
|
329
|
+
type=click.Choice(["stdio", "sse", "http"]),
|
|
330
|
+
default="stdio",
|
|
331
|
+
help="Transport protocol",
|
|
332
|
+
)
|
|
333
|
+
@click.option("--host", default="0.0.0.0", help="Host for HTTP/SSE transport")
|
|
334
|
+
@click.option("--port", default=8087, type=int, help="Port for HTTP/SSE transport")
|
|
335
|
+
def serve(transport, host, port):
|
|
336
|
+
"""
|
|
337
|
+
Run figrecipe MCP server
|
|
338
|
+
|
|
339
|
+
\b
|
|
340
|
+
Examples:
|
|
341
|
+
scitex plt serve # stdio for Claude Desktop
|
|
342
|
+
scitex plt serve -t http --port 8087
|
|
343
|
+
"""
|
|
344
|
+
args = ["mcp", "run", "--transport", transport]
|
|
345
|
+
if transport != "stdio":
|
|
346
|
+
args.extend(["--host", host, "--port", str(port)])
|
|
347
|
+
click.secho(f"Starting figrecipe MCP server ({transport})", fg="cyan")
|
|
348
|
+
click.echo(f" Host: {host}")
|
|
349
|
+
click.echo(f" Port: {port}")
|
|
350
|
+
|
|
351
|
+
sys.exit(_run_figrecipe(*args))
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
if __name__ == "__main__":
|
|
355
|
+
plt()
|
|
356
|
+
|
|
357
|
+
# EOF
|
scitex/cli/repro.py
CHANGED
|
@@ -10,8 +10,13 @@ import sys
|
|
|
10
10
|
import click
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
@click.group(
|
|
14
|
-
|
|
13
|
+
@click.group(
|
|
14
|
+
context_settings={"help_option_names": ["-h", "--help"]},
|
|
15
|
+
invoke_without_command=True,
|
|
16
|
+
)
|
|
17
|
+
@click.option("--help-recursive", is_flag=True, help="Show help for all subcommands")
|
|
18
|
+
@click.pass_context
|
|
19
|
+
def repro(ctx, help_recursive):
|
|
15
20
|
"""
|
|
16
21
|
Reproducibility utilities
|
|
17
22
|
|
|
@@ -29,20 +34,22 @@ def repro():
|
|
|
29
34
|
scitex repro hash data.npy # Hash array file
|
|
30
35
|
scitex repro seed 42 # Set random seed
|
|
31
36
|
"""
|
|
32
|
-
|
|
37
|
+
if help_recursive:
|
|
38
|
+
_print_help_recursive(ctx)
|
|
39
|
+
ctx.exit(0)
|
|
40
|
+
elif ctx.invoked_subcommand is None:
|
|
41
|
+
click.echo(ctx.get_help())
|
|
33
42
|
|
|
34
43
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def help_recursive(ctx):
|
|
38
|
-
"""Show help for all commands recursively."""
|
|
44
|
+
def _print_help_recursive(ctx):
|
|
45
|
+
"""Print help for all commands recursively."""
|
|
39
46
|
fake_parent = click.Context(click.Group(), info_name="scitex")
|
|
40
47
|
parent_ctx = click.Context(repro, info_name="repro", parent=fake_parent)
|
|
41
48
|
click.secho("━━━ scitex repro ━━━", fg="cyan", bold=True)
|
|
42
49
|
click.echo(repro.get_help(parent_ctx))
|
|
43
50
|
for name in sorted(repro.list_commands(ctx) or []):
|
|
44
51
|
cmd = repro.get_command(ctx, name)
|
|
45
|
-
if cmd is None
|
|
52
|
+
if cmd is None:
|
|
46
53
|
continue
|
|
47
54
|
click.echo()
|
|
48
55
|
click.secho(f"━━━ scitex repro {name} ━━━", fg="cyan", bold=True)
|