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
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Timestamp: "2026-01-24 (ywatanabe)"
|
|
3
|
+
# File: /home/ywatanabe/proj/scitex-python/src/scitex/ai/classification/timeseries/_sliding_window_plotting.py
|
|
4
|
+
|
|
5
|
+
"""Plotting mixin for TimeSeriesSlidingWindowSplit visualization."""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import TYPE_CHECKING
|
|
10
|
+
|
|
11
|
+
import matplotlib.patches as patches
|
|
12
|
+
import matplotlib.pyplot as plt
|
|
13
|
+
import numpy as np
|
|
14
|
+
|
|
15
|
+
import scitex as stx
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from ._sliding_window_core import TimeSeriesSlidingWindowSplitCore
|
|
19
|
+
|
|
20
|
+
__all__ = ["SlidingWindowPlottingMixin"]
|
|
21
|
+
|
|
22
|
+
COLORS = stx.plt.color.PARAMS
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class SlidingWindowPlottingMixin:
|
|
26
|
+
"""Mixin class providing plot_splits visualization for sliding window splitters."""
|
|
27
|
+
|
|
28
|
+
def plot_splits(
|
|
29
|
+
self: TimeSeriesSlidingWindowSplitCore,
|
|
30
|
+
X,
|
|
31
|
+
y=None,
|
|
32
|
+
timestamps=None,
|
|
33
|
+
figsize=(12, 6),
|
|
34
|
+
save_path=None,
|
|
35
|
+
):
|
|
36
|
+
"""Visualize the sliding window splits as rectangles.
|
|
37
|
+
|
|
38
|
+
Shows train (blue), validation (green), and test (red) sets.
|
|
39
|
+
When val_ratio=0, only shows train and test.
|
|
40
|
+
When undersampling is enabled, shows dropped samples in gray.
|
|
41
|
+
|
|
42
|
+
Parameters
|
|
43
|
+
----------
|
|
44
|
+
X : array-like
|
|
45
|
+
Training data
|
|
46
|
+
y : array-like, optional
|
|
47
|
+
Target variable (required for undersampling visualization)
|
|
48
|
+
timestamps : array-like, optional
|
|
49
|
+
Timestamps (if None, uses sample indices)
|
|
50
|
+
figsize : tuple, default (12, 6)
|
|
51
|
+
Figure size
|
|
52
|
+
save_path : str, optional
|
|
53
|
+
Path to save the plot
|
|
54
|
+
|
|
55
|
+
Returns
|
|
56
|
+
-------
|
|
57
|
+
fig : matplotlib.figure.Figure
|
|
58
|
+
The created figure
|
|
59
|
+
"""
|
|
60
|
+
if timestamps is None:
|
|
61
|
+
timestamps = np.arange(len(X))
|
|
62
|
+
|
|
63
|
+
time_order = np.argsort(timestamps)
|
|
64
|
+
|
|
65
|
+
# Get splits WITH undersampling (if enabled)
|
|
66
|
+
if self.val_ratio > 0:
|
|
67
|
+
splits = list(self.split_with_val(X, y, timestamps))[:10]
|
|
68
|
+
split_type = "train-val-test"
|
|
69
|
+
else:
|
|
70
|
+
splits = list(self.split(X, y, timestamps))[:10]
|
|
71
|
+
split_type = "train-test"
|
|
72
|
+
|
|
73
|
+
if not splits:
|
|
74
|
+
raise ValueError("No splits generated")
|
|
75
|
+
|
|
76
|
+
# Get splits WITHOUT undersampling to show dropped samples
|
|
77
|
+
splits_no_undersample = None
|
|
78
|
+
if self.undersample and y is not None:
|
|
79
|
+
original_undersample = self.undersample
|
|
80
|
+
self.undersample = False
|
|
81
|
+
if self.val_ratio > 0:
|
|
82
|
+
splits_no_undersample = list(self.split_with_val(X, y, timestamps))[:10]
|
|
83
|
+
else:
|
|
84
|
+
splits_no_undersample = list(self.split(X, y, timestamps))[:10]
|
|
85
|
+
self.undersample = original_undersample
|
|
86
|
+
|
|
87
|
+
fig, ax = stx.plt.subplots(figsize=figsize)
|
|
88
|
+
|
|
89
|
+
# Plot each fold
|
|
90
|
+
for fold, split_indices in enumerate(splits):
|
|
91
|
+
y_pos = fold
|
|
92
|
+
self._plot_fold_rectangles(ax, split_indices, time_order, y_pos, fold)
|
|
93
|
+
|
|
94
|
+
# Plot dropped samples if undersampling
|
|
95
|
+
if splits_no_undersample is not None:
|
|
96
|
+
self._plot_dropped_samples(ax, splits, splits_no_undersample, time_order, y)
|
|
97
|
+
|
|
98
|
+
# Plot kept samples
|
|
99
|
+
self._plot_kept_samples(ax, splits, time_order, y)
|
|
100
|
+
|
|
101
|
+
# Format plot
|
|
102
|
+
self._format_plot(ax, X, splits, split_type, y)
|
|
103
|
+
|
|
104
|
+
plt.tight_layout()
|
|
105
|
+
|
|
106
|
+
if save_path:
|
|
107
|
+
fig.savefig(save_path, dpi=150, bbox_inches="tight")
|
|
108
|
+
|
|
109
|
+
return fig
|
|
110
|
+
|
|
111
|
+
def _plot_fold_rectangles(self, ax, split_indices, time_order, y_pos, fold):
|
|
112
|
+
"""Plot train/val/test rectangles for a fold."""
|
|
113
|
+
if len(split_indices) == 3:
|
|
114
|
+
train_idx, val_idx, test_idx = split_indices
|
|
115
|
+
self._plot_train_rect(ax, train_idx, time_order, y_pos, fold)
|
|
116
|
+
if len(val_idx) > 0:
|
|
117
|
+
self._plot_val_rect(ax, val_idx, time_order, y_pos, fold)
|
|
118
|
+
self._plot_test_rect(ax, test_idx, time_order, y_pos, fold, is_3way=True)
|
|
119
|
+
else:
|
|
120
|
+
train_idx, test_idx = split_indices
|
|
121
|
+
self._plot_train_rect(ax, train_idx, time_order, y_pos, fold)
|
|
122
|
+
self._plot_test_rect(ax, test_idx, time_order, y_pos, fold, is_3way=False)
|
|
123
|
+
|
|
124
|
+
def _plot_train_rect(self, ax, train_idx, time_order, y_pos, fold):
|
|
125
|
+
"""Plot training window rectangle."""
|
|
126
|
+
train_positions = [np.where(time_order == idx)[0][0] for idx in train_idx]
|
|
127
|
+
if train_positions:
|
|
128
|
+
train_start = min(train_positions)
|
|
129
|
+
train_end = max(train_positions)
|
|
130
|
+
train_rect = patches.Rectangle(
|
|
131
|
+
(train_start, y_pos - 0.3),
|
|
132
|
+
train_end - train_start + 1,
|
|
133
|
+
0.6,
|
|
134
|
+
linewidth=1,
|
|
135
|
+
edgecolor="blue",
|
|
136
|
+
facecolor="lightblue",
|
|
137
|
+
alpha=0.7,
|
|
138
|
+
label="Train" if fold == 0 else "",
|
|
139
|
+
)
|
|
140
|
+
ax.add_patch(train_rect)
|
|
141
|
+
|
|
142
|
+
def _plot_val_rect(self, ax, val_idx, time_order, y_pos, fold):
|
|
143
|
+
"""Plot validation window rectangle."""
|
|
144
|
+
val_positions = [np.where(time_order == idx)[0][0] for idx in val_idx]
|
|
145
|
+
if val_positions:
|
|
146
|
+
val_start = min(val_positions)
|
|
147
|
+
val_end = max(val_positions)
|
|
148
|
+
val_rect = patches.Rectangle(
|
|
149
|
+
(val_start, y_pos - 0.3),
|
|
150
|
+
val_end - val_start + 1,
|
|
151
|
+
0.6,
|
|
152
|
+
linewidth=1,
|
|
153
|
+
edgecolor="green",
|
|
154
|
+
facecolor="lightgreen",
|
|
155
|
+
alpha=0.7,
|
|
156
|
+
label="Validation" if fold == 0 else "",
|
|
157
|
+
)
|
|
158
|
+
ax.add_patch(val_rect)
|
|
159
|
+
|
|
160
|
+
def _plot_test_rect(self, ax, test_idx, time_order, y_pos, fold, is_3way):
|
|
161
|
+
"""Plot test window rectangle."""
|
|
162
|
+
test_positions = [np.where(time_order == idx)[0][0] for idx in test_idx]
|
|
163
|
+
if test_positions:
|
|
164
|
+
test_start = min(test_positions)
|
|
165
|
+
test_end = max(test_positions)
|
|
166
|
+
if is_3way:
|
|
167
|
+
edgecolor = COLORS["RGBA_NORM"]["red"]
|
|
168
|
+
facecolor = COLORS["RGBA_NORM"]["red"]
|
|
169
|
+
else:
|
|
170
|
+
edgecolor = "red"
|
|
171
|
+
facecolor = "lightcoral"
|
|
172
|
+
test_rect = patches.Rectangle(
|
|
173
|
+
(test_start, y_pos - 0.3),
|
|
174
|
+
test_end - test_start + 1,
|
|
175
|
+
0.6,
|
|
176
|
+
linewidth=1,
|
|
177
|
+
edgecolor=edgecolor,
|
|
178
|
+
facecolor=facecolor,
|
|
179
|
+
alpha=0.7,
|
|
180
|
+
label="Test" if fold == 0 else "",
|
|
181
|
+
)
|
|
182
|
+
ax.add_patch(test_rect)
|
|
183
|
+
|
|
184
|
+
def _plot_dropped_samples(self, ax, splits, splits_no_undersample, time_order, y):
|
|
185
|
+
"""Plot dropped samples from undersampling."""
|
|
186
|
+
np.random.seed(42)
|
|
187
|
+
jitter_strength = 0.15
|
|
188
|
+
|
|
189
|
+
for fold, split_indices_no_us in enumerate(splits_no_undersample):
|
|
190
|
+
y_pos = fold
|
|
191
|
+
split_indices_us = splits[fold]
|
|
192
|
+
|
|
193
|
+
if len(split_indices_no_us) == 3:
|
|
194
|
+
train_idx_no_us, val_idx_no_us, _ = split_indices_no_us
|
|
195
|
+
train_idx_us, val_idx_us, _ = split_indices_us
|
|
196
|
+
|
|
197
|
+
dropped_train = np.setdiff1d(train_idx_no_us, train_idx_us)
|
|
198
|
+
if len(dropped_train) > 0:
|
|
199
|
+
positions = [
|
|
200
|
+
np.where(time_order == idx)[0][0] for idx in dropped_train
|
|
201
|
+
]
|
|
202
|
+
jitter = np.random.normal(0, jitter_strength, len(positions))
|
|
203
|
+
ax.scatter(
|
|
204
|
+
positions,
|
|
205
|
+
y_pos + jitter,
|
|
206
|
+
c="gray",
|
|
207
|
+
s=15,
|
|
208
|
+
alpha=0.3,
|
|
209
|
+
marker="x",
|
|
210
|
+
label="Dropped (train)" if fold == 0 else "",
|
|
211
|
+
zorder=2,
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
dropped_val = np.setdiff1d(val_idx_no_us, val_idx_us)
|
|
215
|
+
if len(dropped_val) > 0:
|
|
216
|
+
positions = [
|
|
217
|
+
np.where(time_order == idx)[0][0] for idx in dropped_val
|
|
218
|
+
]
|
|
219
|
+
jitter = np.random.normal(0, jitter_strength, len(positions))
|
|
220
|
+
ax.scatter(
|
|
221
|
+
positions,
|
|
222
|
+
y_pos + jitter,
|
|
223
|
+
c="gray",
|
|
224
|
+
s=15,
|
|
225
|
+
alpha=0.3,
|
|
226
|
+
marker="x",
|
|
227
|
+
label="Dropped (val)" if fold == 0 else "",
|
|
228
|
+
zorder=2,
|
|
229
|
+
)
|
|
230
|
+
else:
|
|
231
|
+
train_idx_no_us, _ = split_indices_no_us
|
|
232
|
+
train_idx_us, _ = split_indices_us
|
|
233
|
+
|
|
234
|
+
dropped_train = np.setdiff1d(train_idx_no_us, train_idx_us)
|
|
235
|
+
if len(dropped_train) > 0:
|
|
236
|
+
positions = [
|
|
237
|
+
np.where(time_order == idx)[0][0] for idx in dropped_train
|
|
238
|
+
]
|
|
239
|
+
jitter = np.random.normal(0, jitter_strength, len(positions))
|
|
240
|
+
ax.scatter(
|
|
241
|
+
positions,
|
|
242
|
+
y_pos + jitter,
|
|
243
|
+
c="gray",
|
|
244
|
+
s=15,
|
|
245
|
+
alpha=0.3,
|
|
246
|
+
marker="x",
|
|
247
|
+
label="Dropped samples" if fold == 0 else "",
|
|
248
|
+
zorder=2,
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
def _plot_kept_samples(self, ax, splits, time_order, y):
|
|
252
|
+
"""Plot kept samples with colors."""
|
|
253
|
+
np.random.seed(42)
|
|
254
|
+
jitter_strength = 0.15
|
|
255
|
+
|
|
256
|
+
for fold, split_indices in enumerate(splits):
|
|
257
|
+
y_pos = fold
|
|
258
|
+
|
|
259
|
+
if len(split_indices) == 3:
|
|
260
|
+
train_idx, val_idx, test_idx = split_indices
|
|
261
|
+
self._scatter_indices(
|
|
262
|
+
ax, train_idx, time_order, y_pos, y, fold, "train"
|
|
263
|
+
)
|
|
264
|
+
if len(val_idx) > 0:
|
|
265
|
+
self._scatter_indices(
|
|
266
|
+
ax, val_idx, time_order, y_pos, y, fold, "val"
|
|
267
|
+
)
|
|
268
|
+
self._scatter_indices(ax, test_idx, time_order, y_pos, y, fold, "test")
|
|
269
|
+
else:
|
|
270
|
+
train_idx, test_idx = split_indices
|
|
271
|
+
self._scatter_indices(
|
|
272
|
+
ax, train_idx, time_order, y_pos, y, fold, "train"
|
|
273
|
+
)
|
|
274
|
+
self._scatter_indices(ax, test_idx, time_order, y_pos, y, fold, "test")
|
|
275
|
+
|
|
276
|
+
def _scatter_indices(self, ax, indices, time_order, y_pos, y, fold, set_type):
|
|
277
|
+
"""Scatter plot indices with jittering."""
|
|
278
|
+
jitter_strength = 0.15
|
|
279
|
+
positions = [np.where(time_order == idx)[0][0] for idx in indices]
|
|
280
|
+
|
|
281
|
+
if not positions:
|
|
282
|
+
return
|
|
283
|
+
|
|
284
|
+
jitter = np.random.normal(0, jitter_strength, len(positions))
|
|
285
|
+
|
|
286
|
+
color_map = {
|
|
287
|
+
"train": ("darkblue", "o", "Train"),
|
|
288
|
+
"val": ("darkgreen", "^", "Val"),
|
|
289
|
+
"test": ("darkred", "s", "Test"),
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if y is not None:
|
|
293
|
+
class_colors = {
|
|
294
|
+
"train": (
|
|
295
|
+
COLORS["RGBA_NORM"]["blue"],
|
|
296
|
+
COLORS["RGBA_NORM"]["lightblue"],
|
|
297
|
+
),
|
|
298
|
+
"val": (COLORS["RGBA_NORM"]["yellow"], COLORS["RGBA_NORM"]["orange"]),
|
|
299
|
+
"test": (COLORS["RGBA_NORM"]["red"], COLORS["RGBA_NORM"]["brown"]),
|
|
300
|
+
}
|
|
301
|
+
colors = [
|
|
302
|
+
class_colors[set_type][0] if y[idx] == 0 else class_colors[set_type][1]
|
|
303
|
+
for idx in indices
|
|
304
|
+
]
|
|
305
|
+
ax.scatter(
|
|
306
|
+
positions,
|
|
307
|
+
y_pos + jitter,
|
|
308
|
+
c=colors,
|
|
309
|
+
s=20,
|
|
310
|
+
alpha=0.7,
|
|
311
|
+
marker=color_map[set_type][1],
|
|
312
|
+
label=f"{color_map[set_type][2]} (class 0)" if fold == 0 else "",
|
|
313
|
+
zorder=3,
|
|
314
|
+
)
|
|
315
|
+
else:
|
|
316
|
+
ax.scatter(
|
|
317
|
+
positions,
|
|
318
|
+
y_pos + jitter,
|
|
319
|
+
c=color_map[set_type][0],
|
|
320
|
+
s=20,
|
|
321
|
+
alpha=0.7,
|
|
322
|
+
marker=color_map[set_type][1],
|
|
323
|
+
label=f"{color_map[set_type][2]} points" if fold == 0 else "",
|
|
324
|
+
zorder=3,
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
def _format_plot(self, ax, X, splits, split_type, y):
|
|
328
|
+
"""Format the plot with labels and legend."""
|
|
329
|
+
ax.set_ylim(-0.5, len(splits) - 0.5)
|
|
330
|
+
ax.set_xlim(0, len(X))
|
|
331
|
+
ax.set_xlabel("Temporal Position (sorted by timestamp)")
|
|
332
|
+
ax.set_ylabel("Fold")
|
|
333
|
+
|
|
334
|
+
gap_text = f", Gap: {self.gap}" if self.gap > 0 else ""
|
|
335
|
+
val_text = f", Val ratio: {self.val_ratio:.1%}" if self.val_ratio > 0 else ""
|
|
336
|
+
ax.set_title(
|
|
337
|
+
f"Sliding Window Split Visualization ({split_type})\\n"
|
|
338
|
+
f"Window: {self.window_size}, Step: {self.step_size}, Test: {self.test_size}{gap_text}{val_text}\\n"
|
|
339
|
+
f"Rectangles show windows, dots show actual data points"
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
ax.set_yticks(range(len(splits)))
|
|
343
|
+
ax.set_yticklabels([f"Fold {i}" for i in range(len(splits))])
|
|
344
|
+
|
|
345
|
+
if y is not None:
|
|
346
|
+
unique_classes, class_counts = np.unique(y, return_counts=True)
|
|
347
|
+
total_class_info = ", ".join(
|
|
348
|
+
[
|
|
349
|
+
f"Class {cls}: n={count}"
|
|
350
|
+
for cls, count in zip(unique_classes, class_counts)
|
|
351
|
+
]
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
first_split = splits[0]
|
|
355
|
+
if len(first_split) == 3:
|
|
356
|
+
train_idx, val_idx, test_idx = first_split
|
|
357
|
+
fold_info = f"Fold 0: Train n={len(train_idx)}, Val n={len(val_idx)}, Test n={len(test_idx)}"
|
|
358
|
+
else:
|
|
359
|
+
train_idx, test_idx = first_split
|
|
360
|
+
fold_info = f"Fold 0: Train n={len(train_idx)}, Test n={len(test_idx)}"
|
|
361
|
+
|
|
362
|
+
handles, labels = ax.get_legend_handles_labels()
|
|
363
|
+
legend_title = f"Total: {total_class_info}\\n{fold_info}"
|
|
364
|
+
ax.legend(handles, labels, loc="upper right", title=legend_title)
|
|
365
|
+
else:
|
|
366
|
+
ax.legend(loc="upper right")
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
# EOF
|
scitex/audio/__init__.py
CHANGED
|
@@ -173,8 +173,8 @@ def available_backends() -> List[str]:
|
|
|
173
173
|
if ElevenLabsTTS:
|
|
174
174
|
import os
|
|
175
175
|
|
|
176
|
-
api_key = os.environ.get("
|
|
177
|
-
"
|
|
176
|
+
api_key = os.environ.get("SCITEX_AUDIO_ELEVENLABS_API_KEY") or os.environ.get(
|
|
177
|
+
"ELEVENLABS_API_KEY"
|
|
178
178
|
)
|
|
179
179
|
if api_key:
|
|
180
180
|
backends.append("elevenlabs")
|
scitex/audio/_tts.py
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
3
2
|
# Timestamp: "2025-12-11 (ywatanabe)"
|
|
4
3
|
# File: /home/ywatanabe/proj/scitex-code/src/scitex/audio/_tts.py
|
|
5
4
|
# ----------------------------------------
|
|
@@ -20,7 +19,7 @@ from __future__ import annotations
|
|
|
20
19
|
import os
|
|
21
20
|
import subprocess
|
|
22
21
|
import tempfile
|
|
23
|
-
from dataclasses import dataclass
|
|
22
|
+
from dataclasses import dataclass
|
|
24
23
|
from pathlib import Path
|
|
25
24
|
from typing import Optional
|
|
26
25
|
|
|
@@ -44,7 +43,8 @@ class TTSConfig:
|
|
|
44
43
|
class TTS:
|
|
45
44
|
"""Text-to-Speech using ElevenLabs API.
|
|
46
45
|
|
|
47
|
-
Examples
|
|
46
|
+
Examples
|
|
47
|
+
--------
|
|
48
48
|
# Basic usage
|
|
49
49
|
tts = TTS()
|
|
50
50
|
tts.speak("Hello, world!")
|
|
@@ -84,7 +84,11 @@ class TTS:
|
|
|
84
84
|
voice_id: Direct voice ID (overrides voice_name).
|
|
85
85
|
**kwargs: Additional config options (stability, speed, etc.)
|
|
86
86
|
"""
|
|
87
|
-
self.api_key =
|
|
87
|
+
self.api_key = (
|
|
88
|
+
api_key
|
|
89
|
+
or os.environ.get("SCITEX_AUDIO_ELEVENLABS_API_KEY")
|
|
90
|
+
or os.environ.get("ELEVENLABS_API_KEY")
|
|
91
|
+
)
|
|
88
92
|
self.config = TTSConfig(**kwargs)
|
|
89
93
|
|
|
90
94
|
if voice_id:
|
|
@@ -129,7 +133,8 @@ class TTS:
|
|
|
129
133
|
voice_name: Override voice name for this call.
|
|
130
134
|
voice_id: Override voice ID for this call.
|
|
131
135
|
|
|
132
|
-
Returns
|
|
136
|
+
Returns
|
|
137
|
+
-------
|
|
133
138
|
Path to the generated audio file, or None if only played.
|
|
134
139
|
"""
|
|
135
140
|
# Determine voice
|
|
@@ -228,14 +233,15 @@ class TTS:
|
|
|
228
233
|
try:
|
|
229
234
|
# SoundPlayer only supports WAV, so convert if needed
|
|
230
235
|
wav_path = path
|
|
231
|
-
if path.suffix.lower() in (
|
|
236
|
+
if path.suffix.lower() in (".mp3", ".ogg", ".m4a"):
|
|
232
237
|
try:
|
|
233
238
|
from pydub import AudioSegment
|
|
234
|
-
|
|
239
|
+
|
|
240
|
+
fd, tmp_wav = tempfile.mkstemp(suffix=".wav", prefix="scitex_")
|
|
235
241
|
os.close(fd)
|
|
236
242
|
wav_path = Path(tmp_wav)
|
|
237
243
|
audio = AudioSegment.from_file(str(path))
|
|
238
|
-
audio.export(str(wav_path), format=
|
|
244
|
+
audio.export(str(wav_path), format="wav")
|
|
239
245
|
except ImportError:
|
|
240
246
|
pass
|
|
241
247
|
|
|
@@ -303,10 +309,12 @@ def speak(
|
|
|
303
309
|
output_path: Optional path to save audio.
|
|
304
310
|
**kwargs: Additional TTS config options.
|
|
305
311
|
|
|
306
|
-
Returns
|
|
312
|
+
Returns
|
|
313
|
+
-------
|
|
307
314
|
Path to audio file if output_path specified, else None.
|
|
308
315
|
|
|
309
|
-
Examples
|
|
316
|
+
Examples
|
|
317
|
+
--------
|
|
310
318
|
import scitex
|
|
311
319
|
|
|
312
320
|
# Simple speak
|
scitex/audio/engines/base.py
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
3
2
|
# Timestamp: "2025-12-11 (ywatanabe)"
|
|
4
3
|
# File: /home/ywatanabe/proj/scitex-code/src/scitex/audio/engines/base.py
|
|
5
4
|
# ----------------------------------------
|
|
@@ -49,10 +48,13 @@ class TTSBackend:
|
|
|
49
48
|
|
|
50
49
|
# Check ElevenLabs
|
|
51
50
|
try:
|
|
52
|
-
import elevenlabs
|
|
53
51
|
import os
|
|
54
52
|
|
|
55
|
-
|
|
53
|
+
import elevenlabs
|
|
54
|
+
|
|
55
|
+
if os.environ.get("SCITEX_AUDIO_ELEVENLABS_API_KEY") or os.environ.get(
|
|
56
|
+
"ELEVENLABS_API_KEY"
|
|
57
|
+
):
|
|
56
58
|
backends.append(cls.ELEVENLABS)
|
|
57
59
|
except ImportError:
|
|
58
60
|
pass
|
|
@@ -74,7 +76,8 @@ class BaseTTS(ABC):
|
|
|
74
76
|
text: Text to convert to speech.
|
|
75
77
|
output_path: Path to save the audio file.
|
|
76
78
|
|
|
77
|
-
Returns
|
|
79
|
+
Returns
|
|
80
|
+
-------
|
|
78
81
|
Path to the generated audio file.
|
|
79
82
|
"""
|
|
80
83
|
pass
|
|
@@ -83,7 +86,8 @@ class BaseTTS(ABC):
|
|
|
83
86
|
def get_voices(self) -> List[dict]:
|
|
84
87
|
"""Get available voices for this backend.
|
|
85
88
|
|
|
86
|
-
Returns
|
|
89
|
+
Returns
|
|
90
|
+
-------
|
|
87
91
|
List of voice dictionaries with 'name' and 'id' keys.
|
|
88
92
|
"""
|
|
89
93
|
pass
|
|
@@ -119,7 +123,8 @@ class BaseTTS(ABC):
|
|
|
119
123
|
play: Whether to play the audio.
|
|
120
124
|
voice: Optional voice name/id.
|
|
121
125
|
|
|
122
|
-
Returns
|
|
126
|
+
Returns
|
|
127
|
+
-------
|
|
123
128
|
Path to audio file if output_path specified, else None.
|
|
124
129
|
"""
|
|
125
130
|
import tempfile
|
|
@@ -201,7 +206,8 @@ class BaseTTS(ABC):
|
|
|
201
206
|
Args:
|
|
202
207
|
path: Path to audio file (in WSL filesystem)
|
|
203
208
|
|
|
204
|
-
Returns
|
|
209
|
+
Returns
|
|
210
|
+
-------
|
|
205
211
|
True if playback succeeded, False otherwise
|
|
206
212
|
"""
|
|
207
213
|
import os
|
|
@@ -220,16 +226,17 @@ class BaseTTS(ABC):
|
|
|
220
226
|
try:
|
|
221
227
|
# SoundPlayer only supports WAV, so convert if needed
|
|
222
228
|
wav_path = path
|
|
223
|
-
if path.suffix.lower() in (
|
|
229
|
+
if path.suffix.lower() in (".mp3", ".ogg", ".m4a"):
|
|
224
230
|
try:
|
|
225
231
|
from pydub import AudioSegment
|
|
232
|
+
|
|
226
233
|
# Create temp WAV file
|
|
227
|
-
fd, tmp_wav = tempfile.mkstemp(suffix=
|
|
234
|
+
fd, tmp_wav = tempfile.mkstemp(suffix=".wav", prefix="scitex_")
|
|
228
235
|
os.close(fd)
|
|
229
236
|
wav_path = Path(tmp_wav)
|
|
230
237
|
|
|
231
238
|
audio = AudioSegment.from_file(str(path))
|
|
232
|
-
audio.export(str(wav_path), format=
|
|
239
|
+
audio.export(str(wav_path), format="wav")
|
|
233
240
|
except ImportError:
|
|
234
241
|
# pydub not available, try direct playback anyway
|
|
235
242
|
pass
|
|
@@ -55,8 +55,8 @@ class ElevenLabsTTS(BaseTTS):
|
|
|
55
55
|
super().__init__(**kwargs)
|
|
56
56
|
self.api_key = (
|
|
57
57
|
api_key
|
|
58
|
+
or os.environ.get("SCITEX_AUDIO_ELEVENLABS_API_KEY")
|
|
58
59
|
or os.environ.get("ELEVENLABS_API_KEY")
|
|
59
|
-
or os.environ.get("ELEVENLABS_API_KEY_SCITEX_AUDIO")
|
|
60
60
|
)
|
|
61
61
|
self.voice = voice
|
|
62
62
|
self.model_id = model_id
|
|
@@ -0,0 +1,27 @@
|
|
|
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/__init__.py
|
|
4
|
+
|
|
5
|
+
"""Core WebEditor package for Flask-based figure editing.
|
|
6
|
+
|
|
7
|
+
This package provides the WebEditor class and supporting modules for
|
|
8
|
+
browser-based figure editing functionality.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from ._bbox_extraction import extract_bboxes_from_metadata
|
|
12
|
+
from ._editor import WebEditor
|
|
13
|
+
from ._export_helpers import compose_panels_to_figure, export_composed_figure
|
|
14
|
+
|
|
15
|
+
# Backward compatibility alias
|
|
16
|
+
_extract_bboxes_from_metadata = extract_bboxes_from_metadata
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"WebEditor",
|
|
20
|
+
"extract_bboxes_from_metadata",
|
|
21
|
+
"export_composed_figure",
|
|
22
|
+
"compose_panels_to_figure",
|
|
23
|
+
"_extract_bboxes_from_metadata",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# EOF
|