scitex 2.11.0__py3-none-any.whl → 2.13.0__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 (148) hide show
  1. scitex/__main__.py +24 -5
  2. scitex/__version__.py +1 -1
  3. scitex/_optional_deps.py +33 -0
  4. scitex/ai/classification/reporters/_ClassificationReporter.py +1 -1
  5. scitex/ai/classification/timeseries/_TimeSeriesBlockingSplit.py +2 -2
  6. scitex/ai/classification/timeseries/_TimeSeriesCalendarSplit.py +2 -2
  7. scitex/ai/classification/timeseries/_TimeSeriesSlidingWindowSplit.py +2 -2
  8. scitex/ai/classification/timeseries/_TimeSeriesSlidingWindowSplit_v01-not-using-n_splits.py +2 -2
  9. scitex/ai/classification/timeseries/_TimeSeriesStratifiedSplit.py +2 -2
  10. scitex/ai/classification/timeseries/_normalize_timestamp.py +1 -1
  11. scitex/ai/metrics/_calc_seizure_prediction_metrics.py +1 -1
  12. scitex/ai/plt/_plot_feature_importance.py +1 -1
  13. scitex/ai/plt/_plot_learning_curve.py +1 -1
  14. scitex/ai/plt/_plot_optuna_study.py +1 -1
  15. scitex/ai/plt/_plot_pre_rec_curve.py +1 -1
  16. scitex/ai/plt/_plot_roc_curve.py +1 -1
  17. scitex/ai/plt/_stx_conf_mat.py +1 -1
  18. scitex/ai/training/_LearningCurveLogger.py +1 -1
  19. scitex/audio/mcp_server.py +38 -8
  20. scitex/browser/automation/CookieHandler.py +1 -1
  21. scitex/browser/core/BrowserMixin.py +1 -1
  22. scitex/browser/core/ChromeProfileManager.py +1 -1
  23. scitex/browser/debugging/_browser_logger.py +1 -1
  24. scitex/browser/debugging/_highlight_element.py +1 -1
  25. scitex/browser/debugging/_show_grid.py +1 -1
  26. scitex/browser/interaction/click_center.py +1 -1
  27. scitex/browser/interaction/click_with_fallbacks.py +1 -1
  28. scitex/browser/interaction/close_popups.py +1 -1
  29. scitex/browser/interaction/fill_with_fallbacks.py +1 -1
  30. scitex/browser/pdf/click_download_for_chrome_pdf_viewer.py +1 -1
  31. scitex/browser/pdf/detect_chrome_pdf_viewer.py +1 -1
  32. scitex/browser/stealth/HumanBehavior.py +1 -1
  33. scitex/browser/stealth/StealthManager.py +1 -1
  34. scitex/canvas/_mcp_handlers.py +372 -0
  35. scitex/canvas/_mcp_tool_schemas.py +219 -0
  36. scitex/canvas/mcp_server.py +151 -0
  37. scitex/capture/mcp_server.py +41 -12
  38. scitex/cli/audio.py +233 -0
  39. scitex/cli/capture.py +307 -0
  40. scitex/cli/main.py +27 -4
  41. scitex/cli/repro.py +233 -0
  42. scitex/cli/resource.py +240 -0
  43. scitex/cli/stats.py +325 -0
  44. scitex/cli/template.py +236 -0
  45. scitex/cli/tex.py +286 -0
  46. scitex/cli/web.py +11 -12
  47. scitex/dev/__init__.py +3 -0
  48. scitex/dev/_pyproject.py +405 -0
  49. scitex/dev/plt/__init__.py +2 -2
  50. scitex/dev/plt/mpl/get_dir_ax.py +1 -1
  51. scitex/dev/plt/mpl/get_signatures.py +1 -1
  52. scitex/dev/plt/mpl/get_signatures_details.py +1 -1
  53. scitex/diagram/_mcp_handlers.py +400 -0
  54. scitex/diagram/_mcp_tool_schemas.py +157 -0
  55. scitex/diagram/mcp_server.py +151 -0
  56. scitex/dsp/_demo_sig.py +51 -5
  57. scitex/dsp/_mne.py +13 -2
  58. scitex/dsp/_modulation_index.py +15 -3
  59. scitex/dsp/_pac.py +23 -5
  60. scitex/dsp/_psd.py +16 -4
  61. scitex/dsp/_resample.py +24 -4
  62. scitex/dsp/_transform.py +16 -3
  63. scitex/dsp/add_noise.py +15 -1
  64. scitex/dsp/norm.py +17 -2
  65. scitex/dsp/reference.py +17 -1
  66. scitex/dsp/utils/_differential_bandpass_filters.py +20 -2
  67. scitex/dsp/utils/_zero_pad.py +18 -4
  68. scitex/dt/_normalize_timestamp.py +1 -1
  69. scitex/git/_session.py +1 -1
  70. scitex/io/_load_modules/_con.py +12 -1
  71. scitex/io/_load_modules/_eeg.py +12 -1
  72. scitex/io/_load_modules/_optuna.py +21 -63
  73. scitex/io/_load_modules/_torch.py +11 -3
  74. scitex/io/_save_modules/_optuna_study_as_csv_and_pngs.py +13 -2
  75. scitex/io/_save_modules/_torch.py +11 -3
  76. scitex/mcp_server.py +159 -0
  77. scitex/plt/_mcp_handlers.py +361 -0
  78. scitex/plt/_mcp_tool_schemas.py +169 -0
  79. scitex/plt/mcp_server.py +205 -0
  80. scitex/repro/README_RandomStateManager.md +3 -3
  81. scitex/repro/_RandomStateManager.py +14 -14
  82. scitex/repro/_gen_ID.py +1 -1
  83. scitex/repro/_gen_timestamp.py +1 -1
  84. scitex/repro/_hash_array.py +4 -4
  85. scitex/scholar/__main__.py +24 -2
  86. scitex/scholar/_mcp_handlers.py +685 -0
  87. scitex/scholar/_mcp_tool_schemas.py +339 -0
  88. scitex/scholar/docs/template.py +1 -1
  89. scitex/scholar/examples/07_storage_integration.py +1 -1
  90. scitex/scholar/impact_factor/jcr/ImpactFactorJCREngine.py +1 -1
  91. scitex/scholar/impact_factor/jcr/build_database.py +1 -1
  92. scitex/scholar/mcp_server.py +315 -0
  93. scitex/scholar/pdf_download/ScholarPDFDownloader.py +1 -1
  94. scitex/scholar/pipelines/ScholarPipelineBibTeX.py +1 -1
  95. scitex/scholar/pipelines/ScholarPipelineParallel.py +1 -1
  96. scitex/scholar/pipelines/ScholarPipelineSingle.py +1 -1
  97. scitex/scholar/storage/PaperIO.py +1 -1
  98. scitex/session/README.md +4 -4
  99. scitex/session/__init__.py +1 -1
  100. scitex/session/_decorator.py +9 -9
  101. scitex/session/_lifecycle.py +5 -5
  102. scitex/session/template.py +1 -1
  103. scitex/stats/__main__.py +281 -0
  104. scitex/stats/_mcp_handlers.py +1191 -0
  105. scitex/stats/_mcp_tool_schemas.py +384 -0
  106. scitex/stats/correct/_correct_bonferroni.py +1 -1
  107. scitex/stats/correct/_correct_fdr.py +1 -1
  108. scitex/stats/correct/_correct_fdr_.py +1 -1
  109. scitex/stats/correct/_correct_holm.py +1 -1
  110. scitex/stats/correct/_correct_sidak.py +1 -1
  111. scitex/stats/effect_sizes/_cliffs_delta.py +1 -1
  112. scitex/stats/effect_sizes/_cohens_d.py +1 -1
  113. scitex/stats/effect_sizes/_epsilon_squared.py +1 -1
  114. scitex/stats/effect_sizes/_eta_squared.py +1 -1
  115. scitex/stats/effect_sizes/_prob_superiority.py +1 -1
  116. scitex/stats/mcp_server.py +405 -0
  117. scitex/stats/posthoc/_dunnett.py +1 -1
  118. scitex/stats/posthoc/_games_howell.py +1 -1
  119. scitex/stats/posthoc/_tukey_hsd.py +1 -1
  120. scitex/stats/power/_power.py +1 -1
  121. scitex/stats/utils/_effect_size.py +1 -1
  122. scitex/stats/utils/_formatters.py +1 -1
  123. scitex/stats/utils/_power.py +1 -1
  124. scitex/template/_mcp_handlers.py +259 -0
  125. scitex/template/_mcp_tool_schemas.py +112 -0
  126. scitex/template/mcp_server.py +186 -0
  127. scitex/utils/_verify_scitex_format.py +2 -2
  128. scitex/utils/template.py +1 -1
  129. scitex/web/__init__.py +12 -11
  130. scitex/web/_scraping.py +26 -265
  131. scitex/web/download_images.py +316 -0
  132. scitex/writer/Writer.py +1 -1
  133. scitex/writer/_clone_writer_project.py +1 -1
  134. scitex/writer/_validate_tree_structures.py +1 -1
  135. scitex/writer/dataclasses/config/_WriterConfig.py +1 -1
  136. scitex/writer/dataclasses/contents/_ManuscriptContents.py +1 -1
  137. scitex/writer/dataclasses/core/_Document.py +1 -1
  138. scitex/writer/dataclasses/core/_DocumentSection.py +1 -1
  139. scitex/writer/dataclasses/results/_CompilationResult.py +1 -1
  140. scitex/writer/dataclasses/results/_LaTeXIssue.py +1 -1
  141. scitex/writer/utils/.legacy_git_retry.py +7 -5
  142. scitex/writer/utils/_parse_latex_logs.py +1 -1
  143. {scitex-2.11.0.dist-info → scitex-2.13.0.dist-info}/METADATA +431 -269
  144. {scitex-2.11.0.dist-info → scitex-2.13.0.dist-info}/RECORD +147 -118
  145. scitex-2.13.0.dist-info/entry_points.txt +11 -0
  146. scitex-2.11.0.dist-info/entry_points.txt +0 -2
  147. {scitex-2.11.0.dist-info → scitex-2.13.0.dist-info}/WHEEL +0 -0
  148. {scitex-2.11.0.dist-info → scitex-2.13.0.dist-info}/licenses/LICENSE +0 -0
scitex/cli/capture.py ADDED
@@ -0,0 +1,307 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ SciTeX CLI - Capture Commands (Screenshot/Monitoring)
4
+
5
+ Provides screen capture, monitoring, and GIF creation.
6
+ """
7
+
8
+ import sys
9
+
10
+ import click
11
+
12
+
13
+ @click.group(context_settings={"help_option_names": ["-h", "--help"]})
14
+ def capture():
15
+ """
16
+ Screen capture and monitoring utilities
17
+
18
+ \b
19
+ Commands:
20
+ snap Take a single screenshot
21
+ start Start continuous monitoring
22
+ stop Stop monitoring
23
+ gif Create GIF from session
24
+ info Display info (monitors, windows)
25
+ window Capture specific window by handle
26
+
27
+ \b
28
+ Examples:
29
+ scitex capture snap # Take screenshot
30
+ scitex capture snap --message "debug" # With message in filename
31
+ scitex capture start --interval 2 # Monitor every 2 seconds
32
+ scitex capture stop # Stop monitoring
33
+ scitex capture gif # Create GIF from latest session
34
+ scitex capture info # List monitors and windows
35
+ """
36
+ pass
37
+
38
+
39
+ @capture.command()
40
+ @click.option("--message", "-m", default="", help="Message to include in filename")
41
+ @click.option("--output", "-o", type=click.Path(), help="Output directory")
42
+ @click.option(
43
+ "--quality", "-q", type=int, default=85, help="JPEG quality 1-100 (default: 85)"
44
+ )
45
+ @click.option(
46
+ "--monitor", type=int, default=0, help="Monitor number (0-based, default: 0)"
47
+ )
48
+ @click.option("--all-monitors", is_flag=True, help="Capture all monitors combined")
49
+ def snap(message, output, quality, monitor, all_monitors):
50
+ """
51
+ Take a single screenshot
52
+
53
+ \b
54
+ Examples:
55
+ scitex capture snap
56
+ scitex capture snap --message "before-change"
57
+ scitex capture snap --all-monitors
58
+ scitex capture snap --monitor 1 --quality 95
59
+ """
60
+ try:
61
+ from scitex.capture import snap as take_snap
62
+
63
+ click.echo("Taking screenshot...")
64
+
65
+ # Build kwargs
66
+ kwargs = {"message": message}
67
+ if output:
68
+ kwargs["output_dir"] = output
69
+ if quality != 85:
70
+ kwargs["quality"] = quality
71
+ if all_monitors:
72
+ kwargs["capture_all"] = True
73
+ else:
74
+ kwargs["monitor_id"] = monitor
75
+
76
+ result = take_snap(**kwargs)
77
+
78
+ if result:
79
+ click.secho(f"Screenshot saved: {result}", fg="green")
80
+ else:
81
+ click.secho("Screenshot taken", fg="green")
82
+
83
+ except Exception as e:
84
+ click.secho(f"Error: {e}", fg="red", err=True)
85
+ sys.exit(1)
86
+
87
+
88
+ @capture.command()
89
+ @click.option(
90
+ "--interval",
91
+ "-i",
92
+ type=float,
93
+ default=1.0,
94
+ help="Seconds between captures (default: 1.0)",
95
+ )
96
+ @click.option("--output", "-o", type=click.Path(), help="Output directory")
97
+ @click.option(
98
+ "--quality", "-q", type=int, default=60, help="JPEG quality 1-100 (default: 60)"
99
+ )
100
+ @click.option(
101
+ "--monitor", type=int, default=0, help="Monitor number (0-based, default: 0)"
102
+ )
103
+ @click.option("--all-monitors", is_flag=True, help="Capture all monitors combined")
104
+ def start(interval, output, quality, monitor, all_monitors):
105
+ """
106
+ Start continuous screenshot monitoring
107
+
108
+ \b
109
+ Examples:
110
+ scitex capture start # Default 1 second interval
111
+ scitex capture start --interval 0.5 # Every 0.5 seconds
112
+ scitex capture start --all-monitors
113
+ """
114
+ try:
115
+ from scitex.capture import start as start_monitor
116
+
117
+ click.echo(f"Starting monitoring (interval: {interval}s)...")
118
+ click.echo("Press Ctrl+C or run 'scitex capture stop' to stop")
119
+
120
+ kwargs = {"interval": interval}
121
+ if output:
122
+ kwargs["output_dir"] = output
123
+ if quality != 60:
124
+ kwargs["quality"] = quality
125
+ if all_monitors:
126
+ kwargs["capture_all"] = True
127
+ else:
128
+ kwargs["monitor_id"] = monitor
129
+
130
+ start_monitor(**kwargs)
131
+ click.secho("Monitoring started", fg="green")
132
+
133
+ except Exception as e:
134
+ click.secho(f"Error: {e}", fg="red", err=True)
135
+ sys.exit(1)
136
+
137
+
138
+ @capture.command()
139
+ def stop():
140
+ """
141
+ Stop continuous monitoring
142
+
143
+ \b
144
+ Example:
145
+ scitex capture stop
146
+ """
147
+ try:
148
+ from scitex.capture import stop as stop_monitor
149
+
150
+ stop_monitor()
151
+ click.secho("Monitoring stopped", fg="green")
152
+
153
+ except Exception as e:
154
+ click.secho(f"Error: {e}", fg="red", err=True)
155
+ sys.exit(1)
156
+
157
+
158
+ @capture.command()
159
+ @click.option(
160
+ "--session",
161
+ "-s",
162
+ help="Session ID (e.g., '20250823_104523'). Use 'latest' for most recent.",
163
+ )
164
+ @click.option("--output", "-o", type=click.Path(), help="Output GIF path")
165
+ @click.option(
166
+ "--duration",
167
+ "-d",
168
+ type=float,
169
+ default=0.5,
170
+ help="Duration per frame in seconds (default: 0.5)",
171
+ )
172
+ @click.option("--max-frames", type=int, help="Maximum number of frames to include")
173
+ @click.option(
174
+ "--pattern", "-p", help="Glob pattern for images (alternative to session)"
175
+ )
176
+ def gif(session, output, duration, max_frames, pattern):
177
+ """
178
+ Create animated GIF from screenshots
179
+
180
+ \b
181
+ Examples:
182
+ scitex capture gif # From latest session
183
+ scitex capture gif --session 20250823_104523
184
+ scitex capture gif --duration 0.3 --max-frames 50
185
+ scitex capture gif --pattern "./screenshots/*.jpg"
186
+ """
187
+ try:
188
+ if pattern:
189
+ from scitex.capture import create_gif_from_pattern
190
+
191
+ click.echo(f"Creating GIF from pattern: {pattern}")
192
+ result = create_gif_from_pattern(
193
+ pattern=pattern,
194
+ output_path=output,
195
+ duration=duration,
196
+ max_frames=max_frames,
197
+ )
198
+ elif session:
199
+ from scitex.capture import create_gif_from_session
200
+
201
+ click.echo(f"Creating GIF from session: {session}")
202
+ result = create_gif_from_session(
203
+ session_id=session,
204
+ output_path=output,
205
+ duration=duration,
206
+ max_frames=max_frames,
207
+ )
208
+ else:
209
+ from scitex.capture import create_gif_from_latest_session
210
+
211
+ click.echo("Creating GIF from latest session...")
212
+ result = create_gif_from_latest_session(
213
+ output_path=output,
214
+ duration=duration,
215
+ max_frames=max_frames,
216
+ )
217
+
218
+ if result:
219
+ click.secho(f"GIF created: {result}", fg="green")
220
+ else:
221
+ click.secho("No screenshots found to create GIF", fg="yellow")
222
+
223
+ except Exception as e:
224
+ click.secho(f"Error: {e}", fg="red", err=True)
225
+ sys.exit(1)
226
+
227
+
228
+ @capture.command()
229
+ @click.option("--json", "as_json", is_flag=True, help="Output as JSON")
230
+ def info(as_json):
231
+ """
232
+ Display system info (monitors, windows, virtual desktops)
233
+
234
+ \b
235
+ Examples:
236
+ scitex capture info
237
+ scitex capture info --json
238
+ """
239
+ try:
240
+ from scitex.capture import get_info
241
+
242
+ info_data = get_info()
243
+
244
+ if as_json:
245
+ import json
246
+
247
+ click.echo(json.dumps(info_data, indent=2, default=str))
248
+ else:
249
+ click.secho("Display Information", fg="cyan", bold=True)
250
+ click.echo("=" * 50)
251
+
252
+ # Monitors
253
+ monitors = info_data.get("Monitors", {})
254
+ click.secho(f"\nMonitors ({monitors.get('Count', 0)}):", fg="yellow")
255
+ for i, mon in enumerate(monitors.get("Details", [])):
256
+ primary = " (Primary)" if mon.get("Primary") else ""
257
+ click.echo(f" [{i}] {mon.get('Width')}x{mon.get('Height')}{primary}")
258
+
259
+ # Windows
260
+ windows = info_data.get("Windows", {})
261
+ click.secho(f"\nWindows ({windows.get('Count', 0)}):", fg="yellow")
262
+ for win in windows.get("Details", [])[:10]: # Show first 10
263
+ title = win.get("Title", "")[:40]
264
+ handle = win.get("Handle", "")
265
+ click.echo(f" [{handle}] {title}")
266
+ if windows.get("Count", 0) > 10:
267
+ click.echo(f" ... and {windows.get('Count') - 10} more")
268
+
269
+ except Exception as e:
270
+ click.secho(f"Error: {e}", fg="red", err=True)
271
+ sys.exit(1)
272
+
273
+
274
+ @capture.command()
275
+ @click.argument("handle", type=int)
276
+ @click.option("--output", "-o", type=click.Path(), help="Output file path")
277
+ @click.option("--quality", "-q", type=int, default=85, help="JPEG quality 1-100")
278
+ def window(handle, output, quality):
279
+ """
280
+ Capture a specific window by its handle
281
+
282
+ \b
283
+ Get window handles with: scitex capture info
284
+
285
+ \b
286
+ Examples:
287
+ scitex capture window 12345
288
+ scitex capture window 12345 --output ./window.jpg
289
+ """
290
+ try:
291
+ from scitex.capture import capture_window
292
+
293
+ click.echo(f"Capturing window {handle}...")
294
+ result = capture_window(handle, output)
295
+
296
+ if result:
297
+ click.secho(f"Window captured: {result}", fg="green")
298
+ else:
299
+ click.secho("Window captured", fg="green")
300
+
301
+ except Exception as e:
302
+ click.secho(f"Error: {e}", fg="red", err=True)
303
+ sys.exit(1)
304
+
305
+
306
+ if __name__ == "__main__":
307
+ capture()
scitex/cli/main.py CHANGED
@@ -8,7 +8,22 @@ import sys
8
8
 
9
9
  import click
10
10
 
11
- from . import cloud, config, convert, scholar, security, web, writer
11
+ from . import (
12
+ audio,
13
+ capture,
14
+ cloud,
15
+ config,
16
+ convert,
17
+ repro,
18
+ resource,
19
+ scholar,
20
+ security,
21
+ stats,
22
+ template,
23
+ tex,
24
+ web,
25
+ writer,
26
+ )
12
27
 
13
28
 
14
29
  @click.group(context_settings={"help_option_names": ["-h", "--help"]})
@@ -28,6 +43,10 @@ def cli():
28
43
  scitex security check --save
29
44
  scitex web get-urls https://example.com
30
45
  scitex web download-images https://example.com --output ./downloads
46
+ scitex audio speak "Hello world"
47
+ scitex capture snap --output screenshot.jpg
48
+ scitex resource usage
49
+ scitex stats recommend --data data.csv
31
50
 
32
51
  \b
33
52
  Enable tab-completion:
@@ -38,11 +57,18 @@ def cli():
38
57
 
39
58
 
40
59
  # Add command groups
60
+ cli.add_command(audio.audio)
61
+ cli.add_command(capture.capture)
41
62
  cli.add_command(cloud.cloud)
42
63
  cli.add_command(config.config)
43
64
  cli.add_command(convert.convert)
65
+ cli.add_command(repro.repro)
66
+ cli.add_command(resource.resource)
44
67
  cli.add_command(scholar.scholar)
45
68
  cli.add_command(security.security)
69
+ cli.add_command(stats.stats)
70
+ cli.add_command(template.template)
71
+ cli.add_command(tex.tex)
46
72
  cli.add_command(web.web)
47
73
  cli.add_command(writer.writer)
48
74
 
@@ -111,15 +137,12 @@ def completion(shell, show):
111
137
 
112
138
  # Generate completion script
113
139
  if shell == "bash":
114
- completion_script = f"_SCITEX_COMPLETE=bash_source {scitex_full}"
115
140
  rc_file = os.path.expanduser("~/.bashrc")
116
141
  eval_line = f'eval "$(_SCITEX_COMPLETE=bash_source {scitex_full})"'
117
142
  elif shell == "zsh":
118
- completion_script = f"_SCITEX_COMPLETE=zsh_source {scitex_full}"
119
143
  rc_file = os.path.expanduser("~/.zshrc")
120
144
  eval_line = f'eval "$(_SCITEX_COMPLETE=zsh_source {scitex_full})"'
121
145
  elif shell == "fish":
122
- completion_script = f"_SCITEX_COMPLETE=fish_source {scitex_full}"
123
146
  rc_file = os.path.expanduser("~/.config/fish/config.fish")
124
147
  eval_line = f"eval (env _SCITEX_COMPLETE=fish_source {scitex_full})"
125
148
 
scitex/cli/repro.py ADDED
@@ -0,0 +1,233 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ SciTeX CLI - Repro Commands (Reproducibility)
4
+
5
+ Provides reproducibility utilities: ID generation, timestamps, hashing.
6
+ """
7
+
8
+ import sys
9
+
10
+ import click
11
+
12
+
13
+ @click.group(context_settings={"help_option_names": ["-h", "--help"]})
14
+ def repro():
15
+ """
16
+ Reproducibility utilities
17
+
18
+ \b
19
+ Commands:
20
+ gen-id Generate unique identifier
21
+ gen-timestamp Generate timestamp
22
+ hash Hash array/file for reproducibility
23
+ seed Set random seed across all libraries
24
+
25
+ \b
26
+ Examples:
27
+ scitex repro gen-id # Generate unique ID
28
+ scitex repro gen-timestamp # Generate timestamp
29
+ scitex repro hash data.npy # Hash array file
30
+ scitex repro seed 42 # Set random seed
31
+ """
32
+ pass
33
+
34
+
35
+ @repro.command("gen-id")
36
+ @click.option("--length", "-l", type=int, default=8, help="ID length (default: 8)")
37
+ @click.option("--prefix", "-p", default="", help="Prefix to add to ID")
38
+ @click.option("--count", "-n", type=int, default=1, help="Number of IDs to generate")
39
+ def gen_id(length, prefix, count):
40
+ """
41
+ Generate unique identifier(s)
42
+
43
+ \b
44
+ Examples:
45
+ scitex repro gen-id
46
+ scitex repro gen-id --length 12
47
+ scitex repro gen-id --prefix exp_
48
+ scitex repro gen-id --count 5
49
+ """
50
+ try:
51
+ from scitex.repro import gen_ID
52
+
53
+ for _ in range(count):
54
+ id_str = gen_ID()
55
+ if length != 8:
56
+ id_str = id_str[:length]
57
+ if prefix:
58
+ id_str = f"{prefix}{id_str}"
59
+ click.echo(id_str)
60
+
61
+ except Exception as e:
62
+ click.secho(f"Error: {e}", fg="red", err=True)
63
+ sys.exit(1)
64
+
65
+
66
+ @repro.command("gen-timestamp")
67
+ @click.option(
68
+ "--format",
69
+ "-f",
70
+ "fmt",
71
+ type=click.Choice(["iso", "file", "compact", "human"]),
72
+ default="iso",
73
+ help="Timestamp format (default: iso)",
74
+ )
75
+ @click.option("--utc", is_flag=True, help="Use UTC timezone")
76
+ def gen_timestamp(fmt, utc):
77
+ """
78
+ Generate timestamp
79
+
80
+ \b
81
+ Formats:
82
+ iso - ISO 8601 format (2025-01-08T12:30:45)
83
+ file - File-safe format (20250108_123045)
84
+ compact - Compact format (20250108123045)
85
+ human - Human readable (Jan 08, 2025 12:30:45)
86
+
87
+ \b
88
+ Examples:
89
+ scitex repro gen-timestamp
90
+ scitex repro gen-timestamp --format file
91
+ scitex repro gen-timestamp --format human --utc
92
+ """
93
+ try:
94
+ from datetime import datetime, timezone
95
+
96
+ from scitex.repro import gen_timestamp as make_timestamp
97
+
98
+ if utc:
99
+ now = datetime.now(timezone.utc)
100
+ else:
101
+ now = datetime.now()
102
+
103
+ if fmt == "iso":
104
+ ts = now.isoformat()
105
+ elif fmt == "file":
106
+ ts = now.strftime("%Y%m%d_%H%M%S")
107
+ elif fmt == "compact":
108
+ ts = now.strftime("%Y%m%d%H%M%S")
109
+ elif fmt == "human":
110
+ ts = now.strftime("%b %d, %Y %H:%M:%S")
111
+ else:
112
+ ts = make_timestamp()
113
+
114
+ click.echo(ts)
115
+
116
+ except Exception as e:
117
+ click.secho(f"Error: {e}", fg="red", err=True)
118
+ sys.exit(1)
119
+
120
+
121
+ @repro.command()
122
+ @click.argument("file_path", type=click.Path(exists=True))
123
+ @click.option(
124
+ "--algorithm", "-a", default="sha256", help="Hash algorithm (default: sha256)"
125
+ )
126
+ @click.option("--short", "-s", is_flag=True, help="Output short hash (first 8 chars)")
127
+ def hash(file_path, algorithm, short):
128
+ """
129
+ Hash array or file for reproducibility verification
130
+
131
+ \b
132
+ Supported file types:
133
+ .npy, .npz - NumPy arrays
134
+ .pt, .pth - PyTorch tensors
135
+ .pkl - Pickle files
136
+ * - Any file (raw bytes hash)
137
+
138
+ \b
139
+ Examples:
140
+ scitex repro hash data.npy
141
+ scitex repro hash model.pt --short
142
+ scitex repro hash weights.npz --algorithm md5
143
+ """
144
+ try:
145
+ import hashlib
146
+ from pathlib import Path
147
+
148
+ path = Path(file_path)
149
+
150
+ # Try to load as array first
151
+ hash_val = None
152
+ try:
153
+ if path.suffix in (".npy", ".npz"):
154
+ import numpy as np
155
+
156
+ from scitex.repro import hash_array
157
+
158
+ arr = np.load(path, allow_pickle=True)
159
+ if isinstance(arr, np.lib.npyio.NpzFile):
160
+ # For npz, hash all arrays
161
+ hashes = []
162
+ for key in arr.files:
163
+ hashes.append(hash_array(arr[key]))
164
+ hash_val = hashlib.sha256("".join(hashes).encode()).hexdigest()
165
+ else:
166
+ hash_val = hash_array(arr)
167
+ elif path.suffix in (".pt", ".pth"):
168
+ import torch
169
+
170
+ data = torch.load(path, map_location="cpu")
171
+ if isinstance(data, torch.Tensor):
172
+ hash_val = hashlib.sha256(data.numpy().tobytes()).hexdigest()
173
+ else:
174
+ # State dict or other
175
+ hash_val = hashlib.sha256(str(data).encode()).hexdigest()
176
+ except ImportError:
177
+ pass
178
+
179
+ # Fall back to file hash
180
+ if hash_val is None:
181
+ h = hashlib.new(algorithm)
182
+ with open(path, "rb") as f:
183
+ for chunk in iter(lambda: f.read(8192), b""):
184
+ h.update(chunk)
185
+ hash_val = h.hexdigest()
186
+
187
+ if short:
188
+ hash_val = hash_val[:8]
189
+
190
+ click.echo(f"{hash_val} {path.name}")
191
+
192
+ except Exception as e:
193
+ click.secho(f"Error: {e}", fg="red", err=True)
194
+ sys.exit(1)
195
+
196
+
197
+ @repro.command()
198
+ @click.argument("seed", type=int)
199
+ @click.option("--verbose", "-v", is_flag=True, help="Show what was seeded")
200
+ def seed(seed, verbose):
201
+ """
202
+ Set random seed across all available libraries
203
+
204
+ \b
205
+ Affects: os, random, numpy, torch, tensorflow, jax
206
+
207
+ \b
208
+ Examples:
209
+ scitex repro seed 42
210
+ scitex repro seed 12345 --verbose
211
+ """
212
+ try:
213
+ from scitex.repro import RandomStateManager
214
+
215
+ rsm = RandomStateManager(seed=seed, verbose=verbose)
216
+
217
+ click.secho(f"Random seed set to: {seed}", fg="green")
218
+ if verbose:
219
+ click.echo("Seeded libraries:")
220
+ click.echo(" - os.environ['PYTHONHASHSEED']")
221
+ click.echo(" - random")
222
+ click.echo(" - numpy (if available)")
223
+ click.echo(" - torch (if available)")
224
+ click.echo(" - tensorflow (if available)")
225
+ click.echo(" - jax (if available)")
226
+
227
+ except Exception as e:
228
+ click.secho(f"Error: {e}", fg="red", err=True)
229
+ sys.exit(1)
230
+
231
+
232
+ if __name__ == "__main__":
233
+ repro()