scitex 2.14.0__py3-none-any.whl → 2.15.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (264) hide show
  1. scitex/__init__.py +71 -17
  2. scitex/_env_loader.py +156 -0
  3. scitex/_mcp_resources/__init__.py +37 -0
  4. scitex/_mcp_resources/_cheatsheet.py +135 -0
  5. scitex/_mcp_resources/_figrecipe.py +138 -0
  6. scitex/_mcp_resources/_formats.py +102 -0
  7. scitex/_mcp_resources/_modules.py +337 -0
  8. scitex/_mcp_resources/_session.py +149 -0
  9. scitex/_mcp_tools/__init__.py +4 -0
  10. scitex/_mcp_tools/audio.py +66 -0
  11. scitex/_mcp_tools/diagram.py +11 -95
  12. scitex/_mcp_tools/introspect.py +210 -0
  13. scitex/_mcp_tools/plt.py +260 -305
  14. scitex/_mcp_tools/scholar.py +74 -0
  15. scitex/_mcp_tools/social.py +27 -0
  16. scitex/_mcp_tools/template.py +24 -0
  17. scitex/_mcp_tools/writer.py +17 -210
  18. scitex/ai/_gen_ai/_PARAMS.py +10 -7
  19. scitex/ai/classification/reporters/_SingleClassificationReporter.py +45 -1603
  20. scitex/ai/classification/reporters/_mixins/__init__.py +36 -0
  21. scitex/ai/classification/reporters/_mixins/_constants.py +67 -0
  22. scitex/ai/classification/reporters/_mixins/_cv_summary.py +387 -0
  23. scitex/ai/classification/reporters/_mixins/_feature_importance.py +119 -0
  24. scitex/ai/classification/reporters/_mixins/_metrics.py +275 -0
  25. scitex/ai/classification/reporters/_mixins/_plotting.py +179 -0
  26. scitex/ai/classification/reporters/_mixins/_reports.py +153 -0
  27. scitex/ai/classification/reporters/_mixins/_storage.py +160 -0
  28. scitex/ai/classification/timeseries/_TimeSeriesSlidingWindowSplit.py +30 -1550
  29. scitex/ai/classification/timeseries/_sliding_window_core.py +467 -0
  30. scitex/ai/classification/timeseries/_sliding_window_plotting.py +369 -0
  31. scitex/audio/README.md +40 -36
  32. scitex/audio/__init__.py +129 -61
  33. scitex/audio/_branding.py +185 -0
  34. scitex/audio/_mcp/__init__.py +32 -0
  35. scitex/audio/_mcp/handlers.py +59 -6
  36. scitex/audio/_mcp/speak_handlers.py +238 -0
  37. scitex/audio/_relay.py +225 -0
  38. scitex/audio/_tts.py +18 -10
  39. scitex/audio/engines/base.py +17 -10
  40. scitex/audio/engines/elevenlabs_engine.py +7 -2
  41. scitex/audio/mcp_server.py +228 -75
  42. scitex/canvas/README.md +1 -1
  43. scitex/canvas/editor/_dearpygui/__init__.py +25 -0
  44. scitex/canvas/editor/_dearpygui/_editor.py +147 -0
  45. scitex/canvas/editor/_dearpygui/_handlers.py +476 -0
  46. scitex/canvas/editor/_dearpygui/_panels/__init__.py +17 -0
  47. scitex/canvas/editor/_dearpygui/_panels/_control.py +119 -0
  48. scitex/canvas/editor/_dearpygui/_panels/_element_controls.py +190 -0
  49. scitex/canvas/editor/_dearpygui/_panels/_preview.py +43 -0
  50. scitex/canvas/editor/_dearpygui/_panels/_sections.py +390 -0
  51. scitex/canvas/editor/_dearpygui/_plotting.py +187 -0
  52. scitex/canvas/editor/_dearpygui/_rendering.py +504 -0
  53. scitex/canvas/editor/_dearpygui/_selection.py +295 -0
  54. scitex/canvas/editor/_dearpygui/_state.py +93 -0
  55. scitex/canvas/editor/_dearpygui/_utils.py +61 -0
  56. scitex/canvas/editor/flask_editor/_core/__init__.py +27 -0
  57. scitex/canvas/editor/flask_editor/_core/_bbox_extraction.py +200 -0
  58. scitex/canvas/editor/flask_editor/_core/_editor.py +173 -0
  59. scitex/canvas/editor/flask_editor/_core/_export_helpers.py +353 -0
  60. scitex/canvas/editor/flask_editor/_core/_routes_basic.py +190 -0
  61. scitex/canvas/editor/flask_editor/_core/_routes_export.py +332 -0
  62. scitex/canvas/editor/flask_editor/_core/_routes_panels.py +252 -0
  63. scitex/canvas/editor/flask_editor/_core/_routes_save.py +218 -0
  64. scitex/canvas/editor/flask_editor/_core.py +25 -1684
  65. scitex/canvas/editor/flask_editor/templates/__init__.py +32 -70
  66. scitex/cli/__init__.py +38 -43
  67. scitex/cli/audio.py +160 -41
  68. scitex/cli/capture.py +133 -20
  69. scitex/cli/introspect.py +488 -0
  70. scitex/cli/main.py +200 -109
  71. scitex/cli/mcp.py +60 -34
  72. scitex/cli/plt.py +414 -0
  73. scitex/cli/repro.py +15 -8
  74. scitex/cli/resource.py +15 -8
  75. scitex/cli/scholar/__init__.py +154 -8
  76. scitex/cli/scholar/_crossref_scitex.py +296 -0
  77. scitex/cli/scholar/_fetch.py +25 -3
  78. scitex/cli/social.py +355 -0
  79. scitex/cli/stats.py +136 -11
  80. scitex/cli/template.py +129 -12
  81. scitex/cli/tex.py +15 -8
  82. scitex/cli/writer.py +49 -299
  83. scitex/cloud/__init__.py +41 -2
  84. scitex/config/README.md +1 -1
  85. scitex/config/__init__.py +16 -2
  86. scitex/config/_env_registry.py +256 -0
  87. scitex/context/__init__.py +22 -0
  88. scitex/dev/__init__.py +20 -1
  89. scitex/diagram/__init__.py +42 -19
  90. scitex/diagram/mcp_server.py +13 -125
  91. scitex/gen/__init__.py +50 -14
  92. scitex/gen/_list_packages.py +4 -4
  93. scitex/introspect/__init__.py +82 -0
  94. scitex/introspect/_call_graph.py +303 -0
  95. scitex/introspect/_class_hierarchy.py +163 -0
  96. scitex/introspect/_core.py +41 -0
  97. scitex/introspect/_docstring.py +131 -0
  98. scitex/introspect/_examples.py +113 -0
  99. scitex/introspect/_imports.py +271 -0
  100. scitex/{gen/_inspect_module.py → introspect/_list_api.py} +48 -56
  101. scitex/introspect/_mcp/__init__.py +41 -0
  102. scitex/introspect/_mcp/handlers.py +233 -0
  103. scitex/introspect/_members.py +155 -0
  104. scitex/introspect/_resolve.py +89 -0
  105. scitex/introspect/_signature.py +131 -0
  106. scitex/introspect/_source.py +80 -0
  107. scitex/introspect/_type_hints.py +172 -0
  108. scitex/io/_save.py +1 -2
  109. scitex/io/bundle/README.md +1 -1
  110. scitex/logging/_formatters.py +19 -9
  111. scitex/mcp_server.py +98 -5
  112. scitex/os/__init__.py +4 -0
  113. scitex/{gen → os}/_check_host.py +4 -5
  114. scitex/plt/__init__.py +245 -550
  115. scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_wrappers.py +5 -10
  116. scitex/plt/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
  117. scitex/plt/gallery/README.md +1 -1
  118. scitex/plt/utils/_hitmap/__init__.py +82 -0
  119. scitex/plt/utils/_hitmap/_artist_extraction.py +343 -0
  120. scitex/plt/utils/_hitmap/_color_application.py +346 -0
  121. scitex/plt/utils/_hitmap/_color_conversion.py +121 -0
  122. scitex/plt/utils/_hitmap/_constants.py +40 -0
  123. scitex/plt/utils/_hitmap/_hitmap_core.py +334 -0
  124. scitex/plt/utils/_hitmap/_path_extraction.py +357 -0
  125. scitex/plt/utils/_hitmap/_query.py +113 -0
  126. scitex/plt/utils/_hitmap.py +46 -1616
  127. scitex/plt/utils/_metadata/__init__.py +80 -0
  128. scitex/plt/utils/_metadata/_artists/__init__.py +25 -0
  129. scitex/plt/utils/_metadata/_artists/_base.py +195 -0
  130. scitex/plt/utils/_metadata/_artists/_collections.py +356 -0
  131. scitex/plt/utils/_metadata/_artists/_extract.py +57 -0
  132. scitex/plt/utils/_metadata/_artists/_images.py +80 -0
  133. scitex/plt/utils/_metadata/_artists/_lines.py +261 -0
  134. scitex/plt/utils/_metadata/_artists/_patches.py +247 -0
  135. scitex/plt/utils/_metadata/_artists/_text.py +106 -0
  136. scitex/plt/utils/_metadata/_csv.py +416 -0
  137. scitex/plt/utils/_metadata/_detect.py +225 -0
  138. scitex/plt/utils/_metadata/_legend.py +127 -0
  139. scitex/plt/utils/_metadata/_rounding.py +117 -0
  140. scitex/plt/utils/_metadata/_verification.py +202 -0
  141. scitex/schema/README.md +1 -1
  142. scitex/scholar/__init__.py +8 -0
  143. scitex/scholar/_mcp/crossref_handlers.py +265 -0
  144. scitex/scholar/core/Scholar.py +63 -1700
  145. scitex/scholar/core/_mixins/__init__.py +36 -0
  146. scitex/scholar/core/_mixins/_enrichers.py +270 -0
  147. scitex/scholar/core/_mixins/_library_handlers.py +100 -0
  148. scitex/scholar/core/_mixins/_loaders.py +103 -0
  149. scitex/scholar/core/_mixins/_pdf_download.py +375 -0
  150. scitex/scholar/core/_mixins/_pipeline.py +312 -0
  151. scitex/scholar/core/_mixins/_project_handlers.py +125 -0
  152. scitex/scholar/core/_mixins/_savers.py +69 -0
  153. scitex/scholar/core/_mixins/_search.py +103 -0
  154. scitex/scholar/core/_mixins/_services.py +88 -0
  155. scitex/scholar/core/_mixins/_url_finding.py +105 -0
  156. scitex/scholar/crossref_scitex.py +367 -0
  157. scitex/scholar/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
  158. scitex/scholar/examples/00_run_all.sh +120 -0
  159. scitex/scholar/jobs/_executors.py +27 -3
  160. scitex/scholar/pdf_download/ScholarPDFDownloader.py +38 -416
  161. scitex/scholar/pdf_download/_cli.py +154 -0
  162. scitex/scholar/pdf_download/strategies/__init__.py +11 -8
  163. scitex/scholar/pdf_download/strategies/manual_download_fallback.py +80 -3
  164. scitex/scholar/pipelines/ScholarPipelineBibTeX.py +73 -121
  165. scitex/scholar/pipelines/ScholarPipelineParallel.py +80 -138
  166. scitex/scholar/pipelines/ScholarPipelineSingle.py +43 -63
  167. scitex/scholar/pipelines/_single_steps.py +71 -36
  168. scitex/scholar/storage/_LibraryManager.py +97 -1695
  169. scitex/scholar/storage/_mixins/__init__.py +30 -0
  170. scitex/scholar/storage/_mixins/_bibtex_handlers.py +128 -0
  171. scitex/scholar/storage/_mixins/_library_operations.py +218 -0
  172. scitex/scholar/storage/_mixins/_metadata_conversion.py +226 -0
  173. scitex/scholar/storage/_mixins/_paper_saving.py +456 -0
  174. scitex/scholar/storage/_mixins/_resolution.py +376 -0
  175. scitex/scholar/storage/_mixins/_storage_helpers.py +121 -0
  176. scitex/scholar/storage/_mixins/_symlink_handlers.py +226 -0
  177. scitex/security/README.md +3 -3
  178. scitex/session/README.md +1 -1
  179. scitex/session/__init__.py +26 -7
  180. scitex/session/_decorator.py +1 -1
  181. scitex/sh/README.md +1 -1
  182. scitex/sh/__init__.py +7 -4
  183. scitex/social/__init__.py +155 -0
  184. scitex/social/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
  185. scitex/stats/_mcp/_handlers/__init__.py +31 -0
  186. scitex/stats/_mcp/_handlers/_corrections.py +113 -0
  187. scitex/stats/_mcp/_handlers/_descriptive.py +78 -0
  188. scitex/stats/_mcp/_handlers/_effect_size.py +106 -0
  189. scitex/stats/_mcp/_handlers/_format.py +94 -0
  190. scitex/stats/_mcp/_handlers/_normality.py +110 -0
  191. scitex/stats/_mcp/_handlers/_posthoc.py +224 -0
  192. scitex/stats/_mcp/_handlers/_power.py +247 -0
  193. scitex/stats/_mcp/_handlers/_recommend.py +102 -0
  194. scitex/stats/_mcp/_handlers/_run_test.py +279 -0
  195. scitex/stats/_mcp/_handlers/_stars.py +48 -0
  196. scitex/stats/_mcp/handlers.py +19 -1171
  197. scitex/stats/auto/_stat_style.py +175 -0
  198. scitex/stats/auto/_style_definitions.py +411 -0
  199. scitex/stats/auto/_styles.py +22 -620
  200. scitex/stats/descriptive/__init__.py +11 -8
  201. scitex/stats/descriptive/_ci.py +39 -0
  202. scitex/stats/power/_power.py +15 -4
  203. scitex/str/__init__.py +2 -1
  204. scitex/str/_title_case.py +63 -0
  205. scitex/template/README.md +1 -1
  206. scitex/template/__init__.py +25 -10
  207. scitex/template/_code_templates.py +147 -0
  208. scitex/template/_mcp/handlers.py +81 -0
  209. scitex/template/_mcp/tool_schemas.py +55 -0
  210. scitex/template/_templates/__init__.py +51 -0
  211. scitex/template/_templates/audio.py +233 -0
  212. scitex/template/_templates/canvas.py +312 -0
  213. scitex/template/_templates/capture.py +268 -0
  214. scitex/template/_templates/config.py +43 -0
  215. scitex/template/_templates/diagram.py +294 -0
  216. scitex/template/_templates/io.py +107 -0
  217. scitex/template/_templates/module.py +53 -0
  218. scitex/template/_templates/plt.py +202 -0
  219. scitex/template/_templates/scholar.py +267 -0
  220. scitex/template/_templates/session.py +130 -0
  221. scitex/template/_templates/session_minimal.py +43 -0
  222. scitex/template/_templates/session_plot.py +67 -0
  223. scitex/template/_templates/session_stats.py +77 -0
  224. scitex/template/_templates/stats.py +323 -0
  225. scitex/template/_templates/writer.py +296 -0
  226. scitex/template/clone_writer_directory.py +5 -5
  227. scitex/ui/_backends/_email.py +10 -2
  228. scitex/ui/_backends/_webhook.py +5 -1
  229. scitex/web/_search_pubmed.py +10 -6
  230. scitex/writer/README.md +1 -1
  231. scitex/writer/__init__.py +43 -34
  232. scitex/writer/_mcp/handlers.py +11 -744
  233. scitex/writer/_mcp/tool_schemas.py +5 -335
  234. scitex-2.15.3.dist-info/METADATA +667 -0
  235. {scitex-2.14.0.dist-info → scitex-2.15.3.dist-info}/RECORD +241 -120
  236. scitex/canvas/editor/flask_editor/templates/_scripts.py +0 -4933
  237. scitex/canvas/editor/flask_editor/templates/_styles.py +0 -1658
  238. scitex/diagram/_compile.py +0 -312
  239. scitex/diagram/_diagram.py +0 -355
  240. scitex/diagram/_mcp/__init__.py +0 -4
  241. scitex/diagram/_mcp/handlers.py +0 -400
  242. scitex/diagram/_mcp/tool_schemas.py +0 -157
  243. scitex/diagram/_presets.py +0 -173
  244. scitex/diagram/_schema.py +0 -182
  245. scitex/diagram/_split.py +0 -278
  246. scitex/gen/_ci.py +0 -12
  247. scitex/gen/_title_case.py +0 -89
  248. scitex/plt/_mcp/__init__.py +0 -4
  249. scitex/plt/_mcp/_handlers_annotation.py +0 -102
  250. scitex/plt/_mcp/_handlers_figure.py +0 -195
  251. scitex/plt/_mcp/_handlers_plot.py +0 -252
  252. scitex/plt/_mcp/_handlers_style.py +0 -219
  253. scitex/plt/_mcp/handlers.py +0 -74
  254. scitex/plt/_mcp/tool_schemas.py +0 -497
  255. scitex/plt/mcp_server.py +0 -231
  256. scitex/scholar/examples/SUGGESTIONS.md +0 -865
  257. scitex/scholar/examples/dev.py +0 -38
  258. scitex-2.14.0.dist-info/METADATA +0 -1238
  259. /scitex/{gen → context}/_detect_environment.py +0 -0
  260. /scitex/{gen → context}/_get_notebook_path.py +0 -0
  261. /scitex/{gen/_shell.py → sh/_shell_legacy.py} +0 -0
  262. {scitex-2.14.0.dist-info → scitex-2.15.3.dist-info}/WHEEL +0 -0
  263. {scitex-2.14.0.dist-info → scitex-2.15.3.dist-info}/entry_points.txt +0 -0
  264. {scitex-2.14.0.dist-info → scitex-2.15.3.dist-info}/licenses/LICENSE +0 -0
@@ -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/README.md CHANGED
@@ -27,6 +27,8 @@ scitex audio speak "Hello world"
27
27
  scitex audio speak "Bonjour" --backend gtts --voice fr
28
28
  scitex audio backends # List available backends
29
29
  scitex audio check # Check audio status (WSL)
30
+ scitex audio relay # Start HTTP relay server (for remote audio)
31
+ scitex audio serve # Start MCP server
30
32
  ```
31
33
 
32
34
  ## MCP Server
@@ -46,75 +48,77 @@ Add to `~/.claude/mcp.json`:
46
48
  }
47
49
  ```
48
50
 
49
- ### Remote Audio (SSH tunnel)
51
+ ### Remote Audio (HTTP Relay)
50
52
 
51
- Enable remote agents to play audio on local speakers.
53
+ Enable remote agents to play audio on local speakers using a simple HTTP relay.
52
54
 
53
55
  **Architecture:**
54
56
  ```
55
57
  ┌─────────────────────────┐ ┌─────────────────────────┐
56
58
  │ Remote (e.g., NAS) │ │ Local (WSL/Windows) │
57
59
  │ │ │ │
58
- │ Claude Agent ──────────┼─ SSH ───────▶│ scitex audio serve
59
- connects to │ RemoteForward│ -t sse --port 8084
60
- localhost:8084 │ │ │
61
- │ ▼ │
60
+ │ Claude Agent uses │ │ scitex audio relay
61
+ audio_speak_relay ─────┼─ SSH ───────▶│ --port 31293
62
+ Reverse │ │ │
63
+ localhost:31293 Tunnel │ ▼ │
62
64
  │ │ │ 🔊 Speakers │
63
65
  └─────────────────────────┘ └─────────────────────────┘
64
66
  ```
65
67
 
66
- **Step 1: Local machine - Start audio server**
68
+ **Step 1: Local machine - Start relay server**
67
69
  ```bash
68
- scitex audio serve -t sse --port 8084
70
+ scitex audio relay --port 31293
69
71
  ```
70
72
 
71
- **Step 2: SSH config - Add RemoteForward**
73
+ **Step 2: SSH with reverse tunnel**
74
+ ```bash
75
+ ssh -R 31293:localhost:31293 remote-server
76
+ ```
72
77
 
73
- In `~/.ssh/config` (on local machine):
78
+ Or add to `~/.ssh/config`:
74
79
  ```
75
80
  Host nas
76
81
  HostName 192.168.x.x
77
82
  User youruser
78
- RemoteForward 8084 127.0.0.1:8084 # Audio: remote -> local speakers
83
+ RemoteForward 31293 127.0.0.1:31293
79
84
  ```
80
85
 
81
- **Step 3: Remote machine - MCP config**
86
+ **Step 3: Remote agent uses relay**
82
87
 
83
- On remote machine, add to `~/.claude/mcp.json`:
84
- ```json
85
- {
86
- "mcpServers": {
87
- "scitex-audio-remote": {
88
- "type": "sse",
89
- "url": "http://localhost:8084/sse"
90
- }
91
- }
92
- }
93
- ```
88
+ The `audio_speak_relay` MCP tool auto-detects:
89
+ 1. `SCITEX_AUDIO_RELAY_URL` env var
90
+ 2. Localhost:31293 (SSH reverse tunnel)
91
+ 3. SSH_CLIENT IP (auto-detected from SSH session)
94
92
 
95
- **Step 4: Connect via SSH**
96
- ```bash
97
- ssh nas # RemoteForward creates tunnel automatically
98
- ```
93
+ ### Environment Variables
99
94
 
100
- Now Claude agents on the remote machine can use `mcp__scitex-audio-remote__speak` to play audio on your local speakers.
95
+ | Variable | Default | Description |
96
+ |----------|---------|-------------|
97
+ | `SCITEX_AUDIO_PORT` | 31293 | Server/relay port |
98
+ | `SCITEX_AUDIO_MODE` | auto | `local`, `remote`, or `auto` |
99
+ | `SCITEX_AUDIO_RELAY_URL` | (auto) | Full relay URL |
100
+ | `SCITEX_AUDIO_RELAY_HOST` | (none) | Relay host |
101
+ | `SCITEX_AUDIO_RELAY_PORT` | 31293 | Relay port |
101
102
 
102
103
  ### Server Transports
103
104
 
104
105
  | Transport | Command | Use Case |
105
106
  |-----------|---------|----------|
106
107
  | stdio | `scitex audio serve` | Claude Desktop (default) |
107
- | sse | `scitex audio serve -t sse --port 8084` | Remote agents via SSH |
108
- | http | `scitex audio serve -t http --port 8084` | HTTP clients |
108
+ | sse | `scitex audio serve -t sse --port 31293` | Remote MCP agents |
109
+ | http | `scitex audio serve -t http --port 31293` | HTTP MCP clients |
110
+ | relay | `scitex audio relay --port 31293` | Simple HTTP relay |
109
111
 
110
- ### Tools
112
+ ### MCP Tools
111
113
 
112
114
  | Tool | Description |
113
115
  |------|-------------|
114
- | `speak` | Text to speech (supports `rate`, `speed` params) |
115
- | `list_backends` | Show available backends |
116
- | `check_audio_status` | Check WSL audio connectivity |
117
- | `announce_context` | Announce current directory and git branch |
116
+ | `audio_speak` | Text to speech (plays on server) |
117
+ | `audio_speak_local` | TTS on server machine |
118
+ | `audio_speak_relay` | TTS via relay (remote playback) |
119
+ | `audio_list_backends` | Show available backends |
120
+ | `audio_check_audio_status` | Check WSL audio connectivity |
121
+ | `audio_announce_context` | Announce current directory and git branch |
118
122
 
119
123
  ## Backends
120
124
 
@@ -126,4 +130,4 @@ Now Claude agents on the remote machine can use `mcp__scitex-audio-remote__speak
126
130
 
127
131
  ## Cross-Process Locking
128
132
 
129
- The MCP server uses FIFO locking to ensure only one audio plays at a time across all Claude Code sessions. This prevents audio overlap when multiple agents are running.
133
+ The relay server uses FIFO locking to ensure only one audio plays at a time across all Claude Code sessions. This prevents audio overlap when multiple agents are running.