scitex 2.14.0__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 (300) 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 +244 -0
  16. scitex/_mcp_tools/template.py +24 -0
  17. scitex/_mcp_tools/writer.py +21 -204
  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 +76 -27
  68. scitex/cli/capture.py +13 -20
  69. scitex/cli/introspect.py +481 -0
  70. scitex/cli/main.py +200 -109
  71. scitex/cli/mcp.py +60 -34
  72. scitex/cli/plt.py +357 -0
  73. scitex/cli/repro.py +15 -8
  74. scitex/cli/resource.py +15 -8
  75. scitex/cli/scholar/__init__.py +23 -8
  76. scitex/cli/scholar/_crossref_scitex.py +296 -0
  77. scitex/cli/scholar/_fetch.py +25 -3
  78. scitex/cli/social.py +314 -0
  79. scitex/cli/stats.py +15 -8
  80. scitex/cli/template.py +129 -12
  81. scitex/cli/tex.py +15 -8
  82. scitex/cli/writer.py +132 -8
  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} +43 -54
  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/scholar/url_finder/.tmp/open_url/KNOWN_RESOLVERS.py +462 -0
  178. scitex/scholar/url_finder/.tmp/open_url/README.md +223 -0
  179. scitex/scholar/url_finder/.tmp/open_url/_DOIToURLResolver.py +694 -0
  180. scitex/scholar/url_finder/.tmp/open_url/_OpenURLResolver.py +1160 -0
  181. scitex/scholar/url_finder/.tmp/open_url/_ResolverLinkFinder.py +344 -0
  182. scitex/scholar/url_finder/.tmp/open_url/__init__.py +24 -0
  183. scitex/security/README.md +3 -3
  184. scitex/session/README.md +1 -1
  185. scitex/session/__init__.py +26 -7
  186. scitex/session/_decorator.py +1 -1
  187. scitex/sh/README.md +1 -1
  188. scitex/sh/__init__.py +7 -4
  189. scitex/social/__init__.py +155 -0
  190. scitex/social/docs/EXTERNAL_PACKAGE_BRANDING.md +149 -0
  191. scitex/stats/_mcp/_handlers/__init__.py +31 -0
  192. scitex/stats/_mcp/_handlers/_corrections.py +113 -0
  193. scitex/stats/_mcp/_handlers/_descriptive.py +78 -0
  194. scitex/stats/_mcp/_handlers/_effect_size.py +106 -0
  195. scitex/stats/_mcp/_handlers/_format.py +94 -0
  196. scitex/stats/_mcp/_handlers/_normality.py +110 -0
  197. scitex/stats/_mcp/_handlers/_posthoc.py +224 -0
  198. scitex/stats/_mcp/_handlers/_power.py +247 -0
  199. scitex/stats/_mcp/_handlers/_recommend.py +102 -0
  200. scitex/stats/_mcp/_handlers/_run_test.py +279 -0
  201. scitex/stats/_mcp/_handlers/_stars.py +48 -0
  202. scitex/stats/_mcp/handlers.py +19 -1171
  203. scitex/stats/auto/_stat_style.py +175 -0
  204. scitex/stats/auto/_style_definitions.py +411 -0
  205. scitex/stats/auto/_styles.py +22 -620
  206. scitex/stats/descriptive/__init__.py +11 -8
  207. scitex/stats/descriptive/_ci.py +39 -0
  208. scitex/stats/power/_power.py +15 -4
  209. scitex/str/__init__.py +2 -1
  210. scitex/str/_title_case.py +63 -0
  211. scitex/template/README.md +1 -1
  212. scitex/template/__init__.py +25 -10
  213. scitex/template/_code_templates.py +147 -0
  214. scitex/template/_mcp/handlers.py +81 -0
  215. scitex/template/_mcp/tool_schemas.py +55 -0
  216. scitex/template/_templates/__init__.py +51 -0
  217. scitex/template/_templates/audio.py +233 -0
  218. scitex/template/_templates/canvas.py +312 -0
  219. scitex/template/_templates/capture.py +268 -0
  220. scitex/template/_templates/config.py +43 -0
  221. scitex/template/_templates/diagram.py +294 -0
  222. scitex/template/_templates/io.py +107 -0
  223. scitex/template/_templates/module.py +53 -0
  224. scitex/template/_templates/plt.py +202 -0
  225. scitex/template/_templates/scholar.py +267 -0
  226. scitex/template/_templates/session.py +130 -0
  227. scitex/template/_templates/session_minimal.py +43 -0
  228. scitex/template/_templates/session_plot.py +67 -0
  229. scitex/template/_templates/session_stats.py +77 -0
  230. scitex/template/_templates/stats.py +323 -0
  231. scitex/template/_templates/writer.py +296 -0
  232. scitex/template/clone_writer_directory.py +5 -5
  233. scitex/ui/_backends/_email.py +10 -2
  234. scitex/ui/_backends/_webhook.py +5 -1
  235. scitex/web/_search_pubmed.py +10 -6
  236. scitex/writer/README.md +1 -1
  237. scitex/writer/_mcp/handlers.py +11 -744
  238. scitex/writer/_mcp/tool_schemas.py +5 -335
  239. scitex-2.15.2.dist-info/METADATA +648 -0
  240. {scitex-2.14.0.dist-info → scitex-2.15.2.dist-info}/RECORD +246 -150
  241. scitex/canvas/editor/flask_editor/templates/_scripts.py +0 -4933
  242. scitex/canvas/editor/flask_editor/templates/_styles.py +0 -1658
  243. scitex/dev/plt/data/mpl/PLOTTING_FUNCTIONS.yaml +0 -90
  244. scitex/dev/plt/data/mpl/PLOTTING_SIGNATURES.yaml +0 -1571
  245. scitex/dev/plt/data/mpl/PLOTTING_SIGNATURES_DETAILED.yaml +0 -6262
  246. scitex/dev/plt/data/mpl/SIGNATURES_FLATTENED.yaml +0 -1274
  247. scitex/dev/plt/data/mpl/dir_ax.txt +0 -459
  248. scitex/diagram/_compile.py +0 -312
  249. scitex/diagram/_diagram.py +0 -355
  250. scitex/diagram/_mcp/__init__.py +0 -4
  251. scitex/diagram/_mcp/handlers.py +0 -400
  252. scitex/diagram/_mcp/tool_schemas.py +0 -157
  253. scitex/diagram/_presets.py +0 -173
  254. scitex/diagram/_schema.py +0 -182
  255. scitex/diagram/_split.py +0 -278
  256. scitex/gen/_ci.py +0 -12
  257. scitex/gen/_title_case.py +0 -89
  258. scitex/plt/_mcp/__init__.py +0 -4
  259. scitex/plt/_mcp/_handlers_annotation.py +0 -102
  260. scitex/plt/_mcp/_handlers_figure.py +0 -195
  261. scitex/plt/_mcp/_handlers_plot.py +0 -252
  262. scitex/plt/_mcp/_handlers_style.py +0 -219
  263. scitex/plt/_mcp/handlers.py +0 -74
  264. scitex/plt/_mcp/tool_schemas.py +0 -497
  265. scitex/plt/mcp_server.py +0 -231
  266. scitex/scholar/data/.gitkeep +0 -0
  267. scitex/scholar/data/README.md +0 -44
  268. scitex/scholar/data/bib_files/bibliography.bib +0 -1952
  269. scitex/scholar/data/bib_files/neurovista.bib +0 -277
  270. scitex/scholar/data/bib_files/neurovista_enriched.bib +0 -441
  271. scitex/scholar/data/bib_files/neurovista_enriched_enriched.bib +0 -441
  272. scitex/scholar/data/bib_files/neurovista_processed.bib +0 -338
  273. scitex/scholar/data/bib_files/openaccess.bib +0 -89
  274. scitex/scholar/data/bib_files/pac-seizure_prediction_enriched.bib +0 -2178
  275. scitex/scholar/data/bib_files/pac.bib +0 -698
  276. scitex/scholar/data/bib_files/pac_enriched.bib +0 -1061
  277. scitex/scholar/data/bib_files/pac_processed.bib +0 -0
  278. scitex/scholar/data/bib_files/pac_titles.txt +0 -75
  279. scitex/scholar/data/bib_files/paywalled.bib +0 -98
  280. scitex/scholar/data/bib_files/related-papers-by-coauthors.bib +0 -58
  281. scitex/scholar/data/bib_files/related-papers-by-coauthors_enriched.bib +0 -87
  282. scitex/scholar/data/bib_files/seizure_prediction.bib +0 -694
  283. scitex/scholar/data/bib_files/seizure_prediction_processed.bib +0 -0
  284. scitex/scholar/data/bib_files/test_complete_enriched.bib +0 -437
  285. scitex/scholar/data/bib_files/test_final_enriched.bib +0 -437
  286. scitex/scholar/data/bib_files/test_seizure.bib +0 -46
  287. scitex/scholar/data/impact_factor/JCR_IF_2022.xlsx +0 -0
  288. scitex/scholar/data/impact_factor/JCR_IF_2024.db +0 -0
  289. scitex/scholar/data/impact_factor/JCR_IF_2024.xlsx +0 -0
  290. scitex/scholar/data/impact_factor/JCR_IF_2024_v01.db +0 -0
  291. scitex/scholar/data/impact_factor.db +0 -0
  292. scitex/scholar/examples/SUGGESTIONS.md +0 -865
  293. scitex/scholar/examples/dev.py +0 -38
  294. scitex-2.14.0.dist-info/METADATA +0 -1238
  295. /scitex/{gen → context}/_detect_environment.py +0 -0
  296. /scitex/{gen → context}/_get_notebook_path.py +0 -0
  297. /scitex/{gen/_shell.py → sh/_shell_legacy.py} +0 -0
  298. {scitex-2.14.0.dist-info → scitex-2.15.2.dist-info}/WHEEL +0 -0
  299. {scitex-2.14.0.dist-info → scitex-2.15.2.dist-info}/entry_points.txt +0 -0
  300. {scitex-2.14.0.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/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.