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.
Files changed (107) hide show
  1. scitex/__init__.py +68 -61
  2. scitex/_mcp_tools/introspect.py +42 -23
  3. scitex/_mcp_tools/template.py +24 -0
  4. scitex/ai/classification/timeseries/_TimeSeriesSlidingWindowSplit.py +30 -1550
  5. scitex/ai/classification/timeseries/_sliding_window_core.py +467 -0
  6. scitex/ai/classification/timeseries/_sliding_window_plotting.py +369 -0
  7. scitex/audio/__init__.py +2 -2
  8. scitex/audio/_tts.py +18 -10
  9. scitex/audio/engines/base.py +17 -10
  10. scitex/audio/engines/elevenlabs_engine.py +1 -1
  11. scitex/canvas/editor/flask_editor/_core/__init__.py +27 -0
  12. scitex/canvas/editor/flask_editor/_core/_bbox_extraction.py +200 -0
  13. scitex/canvas/editor/flask_editor/_core/_editor.py +173 -0
  14. scitex/canvas/editor/flask_editor/_core/_export_helpers.py +353 -0
  15. scitex/canvas/editor/flask_editor/_core/_routes_basic.py +190 -0
  16. scitex/canvas/editor/flask_editor/_core/_routes_export.py +332 -0
  17. scitex/canvas/editor/flask_editor/_core/_routes_panels.py +252 -0
  18. scitex/canvas/editor/flask_editor/_core/_routes_save.py +218 -0
  19. scitex/canvas/editor/flask_editor/_core.py +25 -1684
  20. scitex/cli/introspect.py +112 -74
  21. scitex/cli/main.py +2 -0
  22. scitex/cli/plt.py +357 -0
  23. scitex/cli/repro.py +15 -8
  24. scitex/cli/resource.py +15 -8
  25. scitex/cli/scholar/__init__.py +15 -8
  26. scitex/cli/social.py +6 -6
  27. scitex/cli/stats.py +15 -8
  28. scitex/cli/template.py +129 -12
  29. scitex/cli/tex.py +15 -8
  30. scitex/cli/writer.py +15 -8
  31. scitex/cloud/__init__.py +41 -2
  32. scitex/config/_env_registry.py +84 -19
  33. scitex/context/__init__.py +22 -0
  34. scitex/dev/__init__.py +20 -1
  35. scitex/gen/__init__.py +50 -14
  36. scitex/gen/_list_packages.py +4 -4
  37. scitex/introspect/__init__.py +16 -9
  38. scitex/introspect/_core.py +7 -8
  39. scitex/{gen/_inspect_module.py → introspect/_list_api.py} +43 -54
  40. scitex/introspect/_mcp/__init__.py +10 -6
  41. scitex/introspect/_mcp/handlers.py +37 -12
  42. scitex/introspect/_members.py +7 -3
  43. scitex/introspect/_signature.py +3 -3
  44. scitex/introspect/_source.py +2 -2
  45. scitex/io/_save.py +1 -2
  46. scitex/logging/_formatters.py +19 -9
  47. scitex/mcp_server.py +1 -1
  48. scitex/os/__init__.py +4 -0
  49. scitex/{gen → os}/_check_host.py +4 -5
  50. scitex/plt/__init__.py +11 -14
  51. scitex/session/__init__.py +26 -7
  52. scitex/session/_decorator.py +1 -1
  53. scitex/sh/__init__.py +7 -4
  54. scitex/social/__init__.py +10 -8
  55. scitex/stats/_mcp/_handlers/__init__.py +31 -0
  56. scitex/stats/_mcp/_handlers/_corrections.py +113 -0
  57. scitex/stats/_mcp/_handlers/_descriptive.py +78 -0
  58. scitex/stats/_mcp/_handlers/_effect_size.py +106 -0
  59. scitex/stats/_mcp/_handlers/_format.py +94 -0
  60. scitex/stats/_mcp/_handlers/_normality.py +110 -0
  61. scitex/stats/_mcp/_handlers/_posthoc.py +224 -0
  62. scitex/stats/_mcp/_handlers/_power.py +247 -0
  63. scitex/stats/_mcp/_handlers/_recommend.py +102 -0
  64. scitex/stats/_mcp/_handlers/_run_test.py +279 -0
  65. scitex/stats/_mcp/_handlers/_stars.py +48 -0
  66. scitex/stats/_mcp/handlers.py +19 -1171
  67. scitex/stats/auto/_stat_style.py +175 -0
  68. scitex/stats/auto/_style_definitions.py +411 -0
  69. scitex/stats/auto/_styles.py +22 -620
  70. scitex/stats/descriptive/__init__.py +11 -8
  71. scitex/stats/descriptive/_ci.py +39 -0
  72. scitex/stats/power/_power.py +15 -4
  73. scitex/str/__init__.py +2 -1
  74. scitex/str/_title_case.py +63 -0
  75. scitex/template/__init__.py +25 -10
  76. scitex/template/_code_templates.py +147 -0
  77. scitex/template/_mcp/handlers.py +81 -0
  78. scitex/template/_mcp/tool_schemas.py +55 -0
  79. scitex/template/_templates/__init__.py +51 -0
  80. scitex/template/_templates/audio.py +233 -0
  81. scitex/template/_templates/canvas.py +312 -0
  82. scitex/template/_templates/capture.py +268 -0
  83. scitex/template/_templates/config.py +43 -0
  84. scitex/template/_templates/diagram.py +294 -0
  85. scitex/template/_templates/io.py +107 -0
  86. scitex/template/_templates/module.py +53 -0
  87. scitex/template/_templates/plt.py +202 -0
  88. scitex/template/_templates/scholar.py +267 -0
  89. scitex/template/_templates/session.py +130 -0
  90. scitex/template/_templates/session_minimal.py +43 -0
  91. scitex/template/_templates/session_plot.py +67 -0
  92. scitex/template/_templates/session_stats.py +77 -0
  93. scitex/template/_templates/stats.py +323 -0
  94. scitex/template/_templates/writer.py +296 -0
  95. scitex/ui/_backends/_email.py +10 -2
  96. scitex/ui/_backends/_webhook.py +5 -1
  97. scitex/web/_search_pubmed.py +10 -6
  98. {scitex-2.15.1.dist-info → scitex-2.15.2.dist-info}/METADATA +1 -1
  99. {scitex-2.15.1.dist-info → scitex-2.15.2.dist-info}/RECORD +105 -64
  100. scitex/gen/_ci.py +0 -12
  101. scitex/gen/_title_case.py +0 -89
  102. /scitex/{gen → context}/_detect_environment.py +0 -0
  103. /scitex/{gen → context}/_get_notebook_path.py +0 -0
  104. /scitex/{gen/_shell.py → sh/_shell_legacy.py} +0 -0
  105. {scitex-2.15.1.dist-info → scitex-2.15.2.dist-info}/WHEEL +0 -0
  106. {scitex-2.15.1.dist-info → scitex-2.15.2.dist-info}/entry_points.txt +0 -0
  107. {scitex-2.15.1.dist-info → scitex-2.15.2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,200 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: "2026-01-24 (ywatanabe)"
3
+ # File: /home/ywatanabe/proj/scitex-python/src/scitex/canvas/editor/flask_editor/_core/_bbox_extraction.py
4
+
5
+ """Bounding box extraction from pltz metadata."""
6
+
7
+ from typing import Any, Dict, Optional
8
+
9
+ __all__ = ["extract_bboxes_from_metadata"]
10
+
11
+
12
+ def extract_bboxes_from_metadata(
13
+ metadata: Dict[str, Any],
14
+ display_width: Optional[float] = None,
15
+ display_height: Optional[float] = None,
16
+ ) -> Dict[str, Any]:
17
+ """Extract element bounding boxes from pltz metadata.
18
+
19
+ Builds bboxes from selectable_regions in the metadata for click detection.
20
+ This allows the editor to highlight elements when clicked.
21
+
22
+ Coordinate system (new layered format):
23
+ - selectable_regions bbox_px: Already in final image space (figure_px)
24
+ - Display size: Actual displayed image size (PNG pixels or SVG viewBox)
25
+ - Scale = display_size / figure_px (usually 1:1, but may differ for scaled display)
26
+
27
+ Parameters
28
+ ----------
29
+ metadata : dict
30
+ The pltz JSON metadata containing selectable_regions
31
+ display_width : float, optional
32
+ Actual display image width (from PNG size or SVG viewBox)
33
+ display_height : float, optional
34
+ Actual display image height (from PNG size or SVG viewBox)
35
+
36
+ Returns
37
+ -------
38
+ dict
39
+ Mapping of element IDs to their bounding box coordinates (in display pixels)
40
+ """
41
+ bboxes = {}
42
+ selectable = metadata.get("selectable_regions", {})
43
+
44
+ # Figure dimensions from new layered format (bbox_px are in this space)
45
+ figure_px = metadata.get("figure_px", [])
46
+ if isinstance(figure_px, list) and len(figure_px) >= 2:
47
+ fig_width = figure_px[0]
48
+ fig_height = figure_px[1]
49
+ else:
50
+ # Fallback for old format: try hit_regions.path_data.figure
51
+ hit_regions = metadata.get("hit_regions", {})
52
+ path_data = hit_regions.get("path_data", {})
53
+ orig_fig = path_data.get("figure", {})
54
+ fig_width = orig_fig.get("width_px", 944)
55
+ fig_height = orig_fig.get("height_px", 803)
56
+
57
+ # Use actual display dimensions if provided, else use figure_px
58
+ if display_width is None:
59
+ display_width = fig_width
60
+ if display_height is None:
61
+ display_height = fig_height
62
+
63
+ # Scale factor: display / figure_px
64
+ scale_x = display_width / fig_width if fig_width > 0 else 1
65
+ scale_y = display_height / fig_height if fig_height > 0 else 1
66
+
67
+ def to_display_bbox(bbox, is_list=True):
68
+ """Convert bbox to display pixels."""
69
+ if is_list:
70
+ x0, y0, x1, y1 = bbox[0], bbox[1], bbox[2], bbox[3]
71
+ else:
72
+ x0 = bbox.get("x0", 0)
73
+ y0 = bbox.get("y0", 0)
74
+ x1 = bbox.get("x1", bbox.get("x0", 0) + bbox.get("width", 0))
75
+ y1 = bbox.get("y1", bbox.get("y0", 0) + bbox.get("height", 0))
76
+
77
+ disp_x0 = x0 * scale_x
78
+ disp_x1 = x1 * scale_x
79
+ disp_y0 = y0 * scale_y
80
+ disp_y1 = y1 * scale_y
81
+
82
+ return {
83
+ "x0": disp_x0,
84
+ "y0": disp_y0,
85
+ "x1": disp_x1,
86
+ "y1": disp_y1,
87
+ "x": disp_x0,
88
+ "y": disp_y0,
89
+ "width": disp_x1 - disp_x0,
90
+ "height": disp_y1 - disp_y0,
91
+ }
92
+
93
+ # Extract from selectable_regions.axes
94
+ axes_regions = selectable.get("axes", [])
95
+ for ax_idx, ax in enumerate(axes_regions):
96
+ ax_key = f"ax_{ax_idx:02d}"
97
+
98
+ # Title
99
+ title = ax.get("title", {})
100
+ if title and "bbox_px" in title:
101
+ bbox_disp = to_display_bbox(title["bbox_px"])
102
+ bboxes[f"{ax_key}_title"] = {
103
+ **bbox_disp,
104
+ "type": "title",
105
+ "text": title.get("text", ""),
106
+ }
107
+
108
+ # X label
109
+ xlabel = ax.get("xlabel", {})
110
+ if xlabel and "bbox_px" in xlabel:
111
+ bbox_disp = to_display_bbox(xlabel["bbox_px"])
112
+ bboxes[f"{ax_key}_xlabel"] = {
113
+ **bbox_disp,
114
+ "type": "xlabel",
115
+ "text": xlabel.get("text", ""),
116
+ }
117
+
118
+ # Y label
119
+ ylabel = ax.get("ylabel", {})
120
+ if ylabel and "bbox_px" in ylabel:
121
+ bbox_disp = to_display_bbox(ylabel["bbox_px"])
122
+ bboxes[f"{ax_key}_ylabel"] = {
123
+ **bbox_disp,
124
+ "type": "ylabel",
125
+ "text": ylabel.get("text", ""),
126
+ }
127
+
128
+ # Legend
129
+ legend = ax.get("legend", {})
130
+ if legend and "bbox_px" in legend:
131
+ bbox_disp = to_display_bbox(legend["bbox_px"])
132
+ bboxes[f"{ax_key}_legend"] = {
133
+ **bbox_disp,
134
+ "type": "legend",
135
+ }
136
+
137
+ # X-axis spine
138
+ xaxis = ax.get("xaxis", {})
139
+ if xaxis:
140
+ spine = xaxis.get("spine", {})
141
+ if spine and "bbox_px" in spine:
142
+ bbox_disp = to_display_bbox(spine["bbox_px"])
143
+ bboxes[f"{ax_key}_xaxis_spine"] = {
144
+ **bbox_disp,
145
+ "type": "xaxis",
146
+ }
147
+
148
+ # Y-axis spine
149
+ yaxis = ax.get("yaxis", {})
150
+ if yaxis:
151
+ spine = yaxis.get("spine", {})
152
+ if spine and "bbox_px" in spine:
153
+ bbox_disp = to_display_bbox(spine["bbox_px"])
154
+ bboxes[f"{ax_key}_yaxis_spine"] = {
155
+ **bbox_disp,
156
+ "type": "yaxis",
157
+ }
158
+
159
+ # Extract traces from artists
160
+ artists = metadata.get("artists", [])
161
+ if not artists:
162
+ hit_regions = metadata.get("hit_regions", {})
163
+ path_data = hit_regions.get("path_data", {})
164
+ artists = path_data.get("artists", [])
165
+
166
+ for artist in artists:
167
+ artist_id = artist.get("id", 0)
168
+ artist_type = artist.get("type", "line")
169
+ bbox_px = artist.get("bbox_px", {})
170
+ if bbox_px:
171
+ bbox_disp = to_display_bbox(bbox_px, is_list=False)
172
+ trace_entry = {
173
+ **bbox_disp,
174
+ "type": artist_type,
175
+ "label": artist.get("label", f"Trace {artist_id}"),
176
+ "element_type": artist_type,
177
+ }
178
+
179
+ path_px = artist.get("path_px", [])
180
+ if path_px:
181
+ scaled_points = [
182
+ [pt[0] * scale_x, pt[1] * scale_y] for pt in path_px if len(pt) >= 2
183
+ ]
184
+ trace_entry["points"] = scaled_points
185
+
186
+ bboxes[f"trace_{artist_id}"] = trace_entry
187
+
188
+ bboxes["_meta"] = {
189
+ "display_width": display_width,
190
+ "display_height": display_height,
191
+ "figure_px_width": fig_width,
192
+ "figure_px_height": fig_height,
193
+ "scale_x": scale_x,
194
+ "scale_y": scale_y,
195
+ }
196
+
197
+ return bboxes
198
+
199
+
200
+ # EOF
@@ -0,0 +1,173 @@
1
+ #!/usr/bin/env python3
2
+ # Timestamp: "2026-01-24 (ywatanabe)"
3
+ # File: /home/ywatanabe/proj/scitex-python/src/scitex/canvas/editor/flask_editor/_core/_editor.py
4
+
5
+ """Core WebEditor class for Flask-based figure editing."""
6
+
7
+ import copy
8
+ import threading
9
+ import time
10
+ import webbrowser
11
+ from pathlib import Path
12
+ from typing import Any, Dict, Optional
13
+
14
+ from .._utils import check_port_available, kill_process_on_port
15
+
16
+ __all__ = ["WebEditor"]
17
+
18
+
19
+ class WebEditor:
20
+ """Browser-based figure editor using Flask.
21
+
22
+ Features:
23
+ - Displays existing PNG from plot bundle (no re-rendering)
24
+ - Hitmap-based element selection for precise clicking
25
+ - Property editors with sliders and color pickers
26
+ - Save to .manual.json
27
+ - SciTeX style defaults pre-filled
28
+ - Auto-finds available port if default is in use
29
+ """
30
+
31
+ def __init__(
32
+ self,
33
+ json_path: Path,
34
+ metadata: Dict[str, Any],
35
+ csv_data: Optional[Any] = None,
36
+ png_path: Optional[Path] = None,
37
+ hitmap_path: Optional[Path] = None,
38
+ manual_overrides: Optional[Dict[str, Any]] = None,
39
+ port: int = 5050,
40
+ panel_info: Optional[Dict[str, Any]] = None,
41
+ ):
42
+ self.json_path = Path(json_path)
43
+ self.metadata = metadata
44
+ self.csv_data = csv_data
45
+ self.png_path = Path(png_path) if png_path else None
46
+ self.hitmap_path = Path(hitmap_path) if hitmap_path else None
47
+ self.manual_overrides = manual_overrides or {}
48
+ self._requested_port = port
49
+ self.port = port
50
+ self.panel_info = panel_info
51
+
52
+ # Extract hit_regions from metadata
53
+ self.hit_regions = metadata.get("hit_regions", {})
54
+ self.color_map = self.hit_regions.get("color_map", {})
55
+
56
+ # Get SciTeX defaults and merge with metadata
57
+ from ..._defaults import extract_defaults_from_metadata, get_scitex_defaults
58
+
59
+ self.scitex_defaults = get_scitex_defaults()
60
+ self.metadata_defaults = extract_defaults_from_metadata(metadata)
61
+
62
+ # Start with defaults, then overlay manual overrides
63
+ self.current_overrides = copy.deepcopy(self.scitex_defaults)
64
+ self.current_overrides.update(self.metadata_defaults)
65
+ self.current_overrides.update(self.manual_overrides)
66
+
67
+ # Track initial state to detect modifications
68
+ self._initial_overrides = copy.deepcopy(self.current_overrides)
69
+ self._user_modified = False
70
+
71
+ def run(self):
72
+ """Launch the web editor."""
73
+ try:
74
+ from flask import Flask
75
+ except ImportError:
76
+ raise ImportError(
77
+ "Flask is required for web editor. Install: pip install flask"
78
+ )
79
+
80
+ # Handle port conflicts
81
+ self._setup_port()
82
+
83
+ # Configure Flask
84
+ import os
85
+
86
+ static_folder = os.path.join(
87
+ os.path.dirname(os.path.dirname(__file__)), "static"
88
+ )
89
+ app = Flask(__name__, static_folder=static_folder, static_url_path="/static")
90
+
91
+ # Register all routes
92
+ self._register_routes(app)
93
+
94
+ # Open browser after short delay
95
+ def open_browser():
96
+ time.sleep(0.5)
97
+ webbrowser.open(f"http://127.0.0.1:{self.port}")
98
+
99
+ threading.Thread(target=open_browser, daemon=True).start()
100
+
101
+ print(f"Starting SciTeX Figure Editor at http://127.0.0.1:{self.port}")
102
+ print("Press Ctrl+C to stop")
103
+
104
+ app.run(host="127.0.0.1", port=self.port, debug=False, use_reloader=False)
105
+
106
+ def _setup_port(self):
107
+ """Handle port conflicts."""
108
+ max_retries = 3
109
+ for attempt in range(max_retries):
110
+ if check_port_available(self._requested_port):
111
+ self.port = self._requested_port
112
+ break
113
+ print(
114
+ f"Port {self._requested_port} in use. Freeing... "
115
+ f"(attempt {attempt + 1}/{max_retries})"
116
+ )
117
+ kill_process_on_port(self._requested_port)
118
+ time.sleep(1.0)
119
+ else:
120
+ print(f"Warning: Port {self._requested_port} may still be in use")
121
+ self.port = self._requested_port
122
+
123
+ def _register_routes(self, app):
124
+ """Register all Flask routes."""
125
+ from ._routes_basic import (
126
+ create_colormap_route,
127
+ create_hitmap_route,
128
+ create_index_route,
129
+ create_preview_route,
130
+ create_shutdown_route,
131
+ create_stats_route,
132
+ create_update_route,
133
+ )
134
+ from ._routes_export import (
135
+ create_download_figz_route,
136
+ create_download_route,
137
+ create_export_route,
138
+ )
139
+ from ._routes_panels import (
140
+ create_panels_route,
141
+ create_switch_panel_route,
142
+ )
143
+ from ._routes_save import (
144
+ create_save_element_position_route,
145
+ create_save_layout_route,
146
+ create_save_route,
147
+ )
148
+
149
+ # Basic routes
150
+ create_index_route(app, self)
151
+ create_preview_route(app, self)
152
+ create_hitmap_route(app, self)
153
+ create_colormap_route(app, self)
154
+ create_update_route(app, self)
155
+ create_stats_route(app, self)
156
+ create_shutdown_route(app, self)
157
+
158
+ # Panel routes
159
+ create_panels_route(app, self)
160
+ create_switch_panel_route(app, self)
161
+
162
+ # Save routes
163
+ create_save_route(app, self)
164
+ create_save_layout_route(app, self)
165
+ create_save_element_position_route(app, self)
166
+
167
+ # Export routes
168
+ create_export_route(app, self)
169
+ create_download_route(app, self)
170
+ create_download_figz_route(app, self)
171
+
172
+
173
+ # EOF