diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 9a37fc68..ac8ce757 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -70,8 +70,8 @@ jobs:
- name: Prepare bundled Python seed
shell: pwsh
run: |
- $installer = Join-Path $PWD "python-3.11.0-amd64.exe"
- $target = Join-Path $PWD "python311"
+ $installer = Join-Path $env:RUNNER_TEMP "python-3.11.0-amd64.exe"
+ $target = Join-Path $env:RUNNER_TEMP "latexsnipper-python-seed"
Invoke-WebRequest `
-Uri "https://www.python.org/ftp/python/3.11.0/python-3.11.0-amd64.exe" `
-OutFile $installer
@@ -93,6 +93,7 @@ jobs:
"PrependPath=0",
"Shortcuts=0"
) `
+ -WindowStyle Hidden `
-Wait `
-PassThru
if ($process.ExitCode -ne 0) {
@@ -102,11 +103,13 @@ jobs:
throw "Bundled Python seed was not created: $target"
}
Remove-Item -LiteralPath $installer -Force
- & (Join-Path $target "python.exe") -m pip install --upgrade pip wheel
+ & (Join-Path $target "python.exe") -m pip install --upgrade pip wheel setuptools packaging
+ if ($LASTEXITCODE -ne 0) { throw "Bundled Python tooling installation failed." }
+ "LATEXSNIPPER_BUNDLED_PYTHON=$target" >> $env:GITHUB_ENV
- name: Build unsigned installer
shell: pwsh
- run: powershell -ExecutionPolicy Bypass -File scripts\build_github_release_installer.ps1 -PythonPath python
+ run: ./scripts/build_github_release_installer.ps1 -BundledPythonPath $env:LATEXSNIPPER_BUNDLED_PYTHON
- name: Upload unsigned installer artifact
id: upload-unsigned
diff --git a/.gitignore b/.gitignore
index c49fc26c..d15207a5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -28,20 +28,9 @@ office_plugin/release/InstallerAssets/
*.tar
*.gz
-# Embedded runtime (do not version)
-tools/deps/python311/
-tools/deps/python311-*/
-
-# Local runtime components (do not version)
-/python311/
-python-*.exe
-
# Logs
*.log
-# Local app state
-tools/deps/.deps_state.json
-
# Local OCR/PDF regression outputs
test_pdf/outputs/**
!test_pdf/outputs/
@@ -54,3 +43,6 @@ user_manual/*
!user_manual/*.typ
!user_manual/*.md
!user_manual/*.png
+
+# Local editor settings
+.vscode/
diff --git a/.pylintrc b/.pylintrc
index d6d578d6..5d519b27 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -1,6 +1,6 @@
[MASTER]
jobs=0
-ignore-patterns=^\.?#,.*__pycache__.*,.*\.venv.*,.*python311.*,.*build.*
+ignore-patterns=^\.?#,.*__pycache__.*,.*\.venv.*,.*build.*
[MESSAGES CONTROL]
disable=all
diff --git a/.vscode/settings.json b/.vscode/settings.json
deleted file mode 100644
index 619fe78d..00000000
--- a/.vscode/settings.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "git.ignoreLimitWarning": true,
- "python-envs.defaultEnvManager": "ms-python.python:system",
- "python.defaultInterpreterPath": "${workspaceFolder}\\tools\\deps\\python311\\python.exe"
-}
diff --git a/Inno/latexsnipper.iss b/Inno/latexsnipper.iss
index 07c6887c..44dd120b 100644
--- a/Inno/latexsnipper.iss
+++ b/Inno/latexsnipper.iss
@@ -5,11 +5,7 @@
#define MyAppPublisher "MathCraft"
#define MyAppURL "https://github.com/SakuraMathcraft/LaTeXSnipper"
#define MyAppExeName "LaTeXSnipper.exe"
-#if GetEnv("LATEXSNIPPER_REPO_ROOT") != ""
-#define MyRepoRoot GetEnv("LATEXSNIPPER_REPO_ROOT")
-#else
-#define MyRepoRoot "E:\LaTexSnipper"
-#endif
+#define MyRepoRoot AddBackslash(SourcePath) + ".."
#define MyBuildDir MyRepoRoot + "\dist\LaTeXSnipper"
#define MyOutputDir MyRepoRoot + "\dist\installer"
#define MyLicenseFile MyRepoRoot + "\LICENSE"
diff --git a/LaTeXSnipper.spec b/LaTeXSnipper.spec
index 877e55a9..05c7ccab 100644
--- a/LaTeXSnipper.spec
+++ b/LaTeXSnipper.spec
@@ -26,7 +26,6 @@ sys.path.insert(0, str(SRC))
from runtime.product_version import PRODUCT_VERSION, write_windows_version_info
APP_NAME = os.environ.get("LATEXSNIPPER_BUILD_NAME", "LaTeXSnipper")
-BUNDLED_DEPS_DIR_ENV = os.environ.get("LATEXSNIPPER_BUNDLED_DEPS_DIR", "").strip()
VERSION_INFO_PATH = ROOT / "build" / "version_info.txt"
write_windows_version_info(VERSION_INFO_PATH, PRODUCT_VERSION)
@@ -337,21 +336,16 @@ def _prune_qt_webengine_payload(dist_root: Path):
_remove_optional_qt_plugins(plugins_dir, removable_plugins)
-def _resolve_bundled_deps_root() -> Path:
- if BUNDLED_DEPS_DIR_ENV:
- return Path(BUNDLED_DEPS_DIR_ENV).expanduser()
- return ROOT
+# The release workflow prepares a clean seed outside the checkout.
+bundled_python = os.environ.get("LATEXSNIPPER_BUNDLED_PYTHON", "").strip()
+if not bundled_python:
+ raise RuntimeError("LATEXSNIPPER_BUNDLED_PYTHON must identify the CI Python seed")
+bundled_python = Path(bundled_python).resolve()
+if not (bundled_python / "python.exe").is_file():
+ raise RuntimeError(f"Bundled Python seed is missing python.exe: {bundled_python}")
+extra_datas += _collect_tree_as_datas(bundled_python, "deps/python311")
-# Bundle dependency runtime.
-BUNDLED_DEPS_ROOT = _resolve_bundled_deps_root()
-BUNDLED_PY311 = BUNDLED_DEPS_ROOT / "python311"
-if BUNDLED_PY311.exists():
- extra_datas += _collect_tree_as_datas(BUNDLED_PY311, "deps/python311")
- print(f"[SPEC] include bundled python311: {BUNDLED_PY311}")
-else:
- print(f"[SPEC] bundled python311 not found, skip: {BUNDLED_PY311}")
-
a = Analysis(
[str(SRC / "main.py")],
pathex=[str(SRC), str(ROOT)],
diff --git a/README.zh-CN.md b/README.zh-CN.md
index 1d1d2479..66d83750 100644
--- a/README.zh-CN.md
+++ b/README.zh-CN.md
@@ -14,7 +14,7 @@
[English](readme.md) · 简体中文
-
+
diff --git a/README_MATHCRAFT_OCR.md b/README_MATHCRAFT_OCR.md
index 9afdea87..b9eb46fc 100644
--- a/README_MATHCRAFT_OCR.md
+++ b/README_MATHCRAFT_OCR.md
@@ -98,9 +98,9 @@ mathcraft warmup --profile mixed --provider auto
Recognize an image:
```powershell
-mathcraft ocr "C:\path\to\page.png" --profile mixed --provider auto --output result.md
-mathcraft ocr "C:\path\to\page.png" --profile mixed --provider auto --output-dir "D:\MathCraft\outputs"
-mathcraft ocr "C:\path\to\formula.png" --profile formula --provider auto --json
+mathcraft ocr "./examples/page.png" --profile mixed --provider auto --output result.md
+mathcraft ocr "./examples/page.png" --profile mixed --provider auto --output-dir "./outputs"
+mathcraft ocr "./examples/formula.png" --profile formula --provider auto --json
```
Run JSONL worker mode:
@@ -122,14 +122,14 @@ Linux: ${XDG_DATA_HOME:-~/.local/share}/LaTeXSnipper/MathCraft/models
or from a custom root:
```powershell
-$env:MATHCRAFT_HOME="D:\MathCraft\models"
+$env:MATHCRAFT_HOME="./models"
mathcraft doctor --provider auto
```
Persist the custom root for future PowerShell sessions:
```powershell
-setx MATHCRAFT_HOME "D:\MathCraft\models"
+setx MATHCRAFT_HOME "./models"
```
Restore the default user cache root:
@@ -198,7 +198,7 @@ The actual provider is available on recognition results through the `provider` f
Run tests from the repository root:
```powershell
-cd E:\LaTexSnipper
+# Run from the repository root
python .\test\test_mathcraft_ocr.py
python .\test\test_mathcraft_document_engine.py
```
@@ -206,7 +206,7 @@ python .\test\test_mathcraft_document_engine.py
Build package artifacts:
```powershell
-cd E:\LaTexSnipper
+# Run from the repository root
python -m build --no-isolation --outdir .\release_assets\mathcraft-ocr-package\dist .
```
diff --git a/benchmarks/mathcraft_ocr/README.md b/benchmarks/mathcraft_ocr/README.md
index 0f605d61..3f433a5d 100644
--- a/benchmarks/mathcraft_ocr/README.md
+++ b/benchmarks/mathcraft_ocr/README.md
@@ -25,7 +25,7 @@ Third-party systems may appear only in related work or public-result context, wi
Keep external datasets and large run artifacts outside the repository:
```text
-E:\MathCraftBenchData
+./benchmark-data
```
Manifest files live under:
@@ -45,16 +45,16 @@ images, and dataset-provided normalized LaTeX labels as ground truth.
Run MathCraft GPU:
```powershell
-powershell -ExecutionPolicy Bypass -File benchmarks\mathcraft_ocr\run_mathwriting_test.ps1
+powershell -ExecutionPolicy Bypass -File benchmarks\mathcraft_ocr\run_mathwriting_test.ps1 -DataRoot ./benchmark-data
```
The runner creates:
```text
-E:\MathCraftBenchData\manifests\mathwriting_test.jsonl
-E:\MathCraftBenchData\raw\mathwriting\test_images
-E:\MathCraftBenchData\runs\mathwriting_test_gpu\mathwriting_test_gpu_full.jsonl
-E:\MathCraftBenchData\runs\mathwriting_test_gpu\cdm_input\mathwriting_test_full_cdm.json
+./benchmark-data\manifests\mathwriting_test.jsonl
+./benchmark-data\raw\mathwriting\test_images
+./benchmark-data\runs\mathwriting_test_gpu\mathwriting_test_gpu_full.jsonl
+./benchmark-data\runs\mathwriting_test_gpu\cdm_input\mathwriting_test_full_cdm.json
```
It also writes exact, BLEU-4, normalized edit distance, and latency summaries
@@ -62,15 +62,15 @@ after the full JSONL is combined. Render success is computed separately:
```powershell
python benchmarks\mathcraft_ocr\reports\render_formula_success.py `
- --results E:\MathCraftBenchData\runs\mathwriting_test_gpu\mathwriting_test_gpu_full.jsonl `
- --manifest E:\MathCraftBenchData\manifests\mathwriting_test.jsonl `
- --output-dir E:\MathCraftBenchData\runs\mathwriting_test_gpu\render_success
+ --results ./benchmark-data\runs\mathwriting_test_gpu\mathwriting_test_gpu_full.jsonl `
+ --manifest ./benchmark-data\manifests\mathwriting_test.jsonl `
+ --output-dir ./benchmark-data\runs\mathwriting_test_gpu\render_success
```
Run official CDM on MathWriting predictions:
```powershell
-powershell -ExecutionPolicy Bypass -File benchmarks\mathcraft_ocr\run_mathwriting_official_cdm.ps1
+powershell -ExecutionPolicy Bypass -File benchmarks\mathcraft_ocr\run_mathwriting_official_cdm.ps1 -DataRoot ./benchmark-data
```
Baseline comparisons must be restricted to the same split, normalized label, and
@@ -79,7 +79,7 @@ offline rasterization protocol.
## UniMER-Test
```powershell
-powershell -ExecutionPolicy Bypass -File benchmarks\mathcraft_ocr\run_unimer_test.ps1
+powershell -ExecutionPolicy Bypass -File benchmarks\mathcraft_ocr\run_unimer_test.ps1 -DataRoot ./benchmark-data
```
The script downloads UniMER-Test from Hugging Face when needed, creates the manifest,
@@ -90,18 +90,18 @@ Analyze text metrics:
```powershell
python benchmarks\mathcraft_ocr\reports\analyze_unimer_results.py `
- --results E:\MathCraftBenchData\runs\unimer_test_gpu\unimer_test_gpu_full.jsonl `
- --manifest E:\MathCraftBenchData\manifests\unimer_test_full.jsonl `
- --output-dir E:\MathCraftBenchData\runs\unimer_test_gpu
+ --results ./benchmark-data\runs\unimer_test_gpu\unimer_test_gpu_full.jsonl `
+ --manifest ./benchmark-data\manifests\unimer_test_full.jsonl `
+ --output-dir ./benchmark-data\runs\unimer_test_gpu
```
Analyze render-consistency fallback samples:
```powershell
python benchmarks\mathcraft_ocr\reports\render_unimer_samples.py `
- --results E:\MathCraftBenchData\runs\unimer_test_gpu\unimer_test_gpu_full.jsonl `
- --manifest E:\MathCraftBenchData\manifests\unimer_test_full.jsonl `
- --output-dir E:\MathCraftBenchData\runs\unimer_test_gpu\render_exact_underestimated `
+ --results ./benchmark-data\runs\unimer_test_gpu\unimer_test_gpu_full.jsonl `
+ --manifest ./benchmark-data\manifests\unimer_test_full.jsonl `
+ --output-dir ./benchmark-data\runs\unimer_test_gpu\render_exact_underestimated `
--per-subset 20 `
--min-similarity 0.95 `
--keep-images
@@ -111,9 +111,9 @@ Prepare official CDM batch JSON input:
```powershell
python benchmarks\mathcraft_ocr\reports\prepare_cdm_input.py `
- --results E:\MathCraftBenchData\runs\unimer_test_gpu\unimer_test_gpu_full.jsonl `
- --manifest E:\MathCraftBenchData\manifests\unimer_test_full.jsonl `
- --output E:\MathCraftBenchData\runs\unimer_test_gpu\cdm_input\unimer_test_full_cdm.json `
+ --results ./benchmark-data\runs\unimer_test_gpu\unimer_test_gpu_full.jsonl `
+ --manifest ./benchmark-data\manifests\unimer_test_full.jsonl `
+ --output ./benchmark-data\runs\unimer_test_gpu\cdm_input\unimer_test_full_cdm.json `
--subset all
```
@@ -123,21 +123,21 @@ The generated JSON follows the official CDM batch fields `img_id`, `gt`, and
Run the official UniMERNet CDM runtime with resumable shards:
```powershell
-powershell -ExecutionPolicy Bypass -File benchmarks\mathcraft_ocr\run_official_cdm.ps1
+powershell -ExecutionPolicy Bypass -File benchmarks\mathcraft_ocr\run_official_cdm.ps1 -DataRoot ./benchmark-data
```
The CDM runner uses the official `evaluation.py` from the local UniMERNet clone
-under `E:\MathCraftBenchData\sources\UniMERNet_official\cdm`. It intentionally
+under `./benchmark-data\sources\UniMERNet_official\cdm`. It intentionally
uses the system Python configured for that official runtime; MathCraft OCR
-inference remains on `tools\deps\python311\python.exe`. The default official
+inference remains on the developer-selected Python interpreter. The default official
CDM pool count is 8 on the local Windows evaluation host.
For a quick validation run:
```powershell
-powershell -ExecutionPolicy Bypass -File benchmarks\mathcraft_ocr\run_official_cdm.ps1 `
- -InputPath E:\MathCraftBenchData\runs\unimer_test_gpu\cdm_input\unimer_test_all_20_cdm.json `
- -OutputDir E:\MathCraftBenchData\runs\cdm_official_unimer_20_runner `
+powershell -ExecutionPolicy Bypass -File benchmarks\mathcraft_ocr\run_official_cdm.ps1 -DataRoot ./benchmark-data `
+ -InputPath ./benchmark-data\runs\unimer_test_gpu\cdm_input\unimer_test_all_20_cdm.json `
+ -OutputDir ./benchmark-data\runs\cdm_official_unimer_20_runner `
-ShardSize 10 `
-Limit 20 `
-Pools 8
@@ -155,7 +155,7 @@ render skips are not confused with interrupted runs.
The full official CDM UniMER-Test run is complete:
```text
-Run: E:\MathCraftBenchData\runs\cdm_official_unimer_full
+Run: ./benchmark-data\runs\cdm_official_unimer_full
Mean CDM: 0.929
ExpRate: 0.648
Expected: 23757
@@ -170,7 +170,7 @@ benchmarks/mathcraft_ocr/results/unimer_test_gpu
```
Large JSONL files, downloaded data, and rendered PNG pairs remain under
-`E:\MathCraftBenchData`.
+`./benchmark-data`.
## OpenStax Page Images
@@ -181,7 +181,7 @@ for exact-match formula accuracy because no page-level ground truth is included.
Run the default systematic mixed-page benchmark:
```powershell
-powershell -ExecutionPolicy Bypass -File benchmarks\mathcraft_ocr\run_openstax_pages.ps1
+powershell -ExecutionPolicy Bypass -File benchmarks\mathcraft_ocr\run_openstax_pages.ps1 -DataRoot ./benchmark-data
```
The runner creates a manifest, renders selected OpenStax PDF pages, runs mixed
@@ -205,10 +205,14 @@ Generate OpenStax block-overlay figure assets from the formal run:
```powershell
python benchmarks\mathcraft_ocr\visualization\make_openstax_block_gallery.py `
- --results E:\MathCraftBenchData\runs\openstax_mixed_gpu_144dpi\openstax_mixed_gpu_144dpi_full.jsonl `
- --output-dir E:\MathCraftBenchData\runs\openstax_mixed_gpu_144dpi\figures\block_gallery `
+ --results ./benchmark-data\runs\openstax_mixed_gpu_144dpi\openstax_mixed_gpu_144dpi_full.jsonl `
+ --output-dir ./benchmark-data\runs\openstax_mixed_gpu_144dpi\figures\block_gallery `
--limit 6
```
-Generated OpenStax page images and overlays remain under `E:\MathCraftBenchData`
+Generated OpenStax page images and overlays remain under `./benchmark-data`
because they are derived from licensed OpenStax PDF content.
+
+## Portable benchmark environment
+
+Run examples from the repository root. `./benchmark-data` is an example external-data directory; use `-DataRoot` to choose its location. The PowerShell runners resolve the checkout from the script location and use the active `python`; inference runners accept `-PythonPath`, and CDM runners accept `-Python`. Install CDM tools on PATH or pass `-PathPrepend` with your own tool directories. No drive letter or developer environment is required.
diff --git a/benchmarks/mathcraft_ocr/REPRODUCE_MATHWRITING.md b/benchmarks/mathcraft_ocr/REPRODUCE_MATHWRITING.md
index f15a9d0b..d85aad92 100644
--- a/benchmarks/mathcraft_ocr/REPRODUCE_MATHWRITING.md
+++ b/benchmarks/mathcraft_ocr/REPRODUCE_MATHWRITING.md
@@ -8,7 +8,7 @@ Large parquet files, exported images, full JSONL results, render outputs, and
official CDM outputs stay under:
```text
-E:\MathCraftBenchData
+./benchmark-data
```
## Inputs
@@ -16,7 +16,7 @@ E:\MathCraftBenchData
Expected local parquet:
```text
-E:\MathCraftBenchData\raw\mathwriting\test-00000-of-00001-694f317d8b634199.parquet
+./benchmark-data\raw\mathwriting\test-00000-of-00001-694f317d8b634199.parquet
```
The manifest generator validates:
@@ -29,14 +29,14 @@ data_type = human
Generated fixed-test assets:
```text
-E:\MathCraftBenchData\manifests\mathwriting_test.jsonl
-E:\MathCraftBenchData\raw\mathwriting\test_images
+./benchmark-data\manifests\mathwriting_test.jsonl
+./benchmark-data\raw\mathwriting\test_images
```
## Run MathCraft GPU
```powershell
-powershell -ExecutionPolicy Bypass -File E:\LaTexSnipper\benchmarks\mathcraft_ocr\run_mathwriting_test.ps1
+powershell -ExecutionPolicy Bypass -File .\benchmarks\mathcraft_ocr\run_mathwriting_test.ps1 -DataRoot ./benchmark-data
```
Defaults:
@@ -52,20 +52,20 @@ Progress: Write-Progress plus per-shard ETA
Formal output after a full run:
```text
-E:\MathCraftBenchData\runs\mathwriting_test_gpu\mathwriting_test_gpu_full.jsonl
-E:\MathCraftBenchData\runs\mathwriting_test_gpu\mathwriting_test_gpu_subset_metrics.csv
-E:\MathCraftBenchData\runs\mathwriting_test_gpu\mathwriting_test_gpu_report.md
-E:\MathCraftBenchData\runs\mathwriting_test_gpu\cdm_input\mathwriting_test_full_cdm.json
+./benchmark-data\runs\mathwriting_test_gpu\mathwriting_test_gpu_full.jsonl
+./benchmark-data\runs\mathwriting_test_gpu\mathwriting_test_gpu_subset_metrics.csv
+./benchmark-data\runs\mathwriting_test_gpu\mathwriting_test_gpu_report.md
+./benchmark-data\runs\mathwriting_test_gpu\cdm_input\mathwriting_test_full_cdm.json
```
## Render Success
```powershell
-E:\LaTexSnipper\tools\deps\python311\python.exe `
- E:\LaTexSnipper\benchmarks\mathcraft_ocr\reports\render_formula_success.py `
- --results E:\MathCraftBenchData\runs\mathwriting_test_gpu\mathwriting_test_gpu_full.jsonl `
- --manifest E:\MathCraftBenchData\manifests\mathwriting_test.jsonl `
- --output-dir E:\MathCraftBenchData\runs\mathwriting_test_gpu\render_success
+python `
+ .\benchmarks\mathcraft_ocr\reports\render_formula_success.py `
+ --results ./benchmark-data\runs\mathwriting_test_gpu\mathwriting_test_gpu_full.jsonl `
+ --manifest ./benchmark-data\manifests\mathwriting_test.jsonl `
+ --output-dir ./benchmark-data\runs\mathwriting_test_gpu\render_success
```
## Official CDM
@@ -73,13 +73,13 @@ E:\LaTexSnipper\tools\deps\python311\python.exe `
The MathCraft run creates official CDM input automatically. Then run:
```powershell
-powershell -ExecutionPolicy Bypass -File E:\LaTexSnipper\benchmarks\mathcraft_ocr\run_mathwriting_official_cdm.ps1
+powershell -ExecutionPolicy Bypass -File .\benchmarks\mathcraft_ocr\run_mathwriting_official_cdm.ps1 -DataRoot ./benchmark-data
```
For a bounded CDM validation:
```powershell
-powershell -ExecutionPolicy Bypass -File E:\LaTexSnipper\benchmarks\mathcraft_ocr\run_mathwriting_official_cdm.ps1 `
+powershell -ExecutionPolicy Bypass -File .\benchmarks\mathcraft_ocr\run_mathwriting_official_cdm.ps1 -DataRoot ./benchmark-data `
-Limit 100 `
-ShardSize 20 `
-Pools 8
diff --git a/benchmarks/mathcraft_ocr/REPRODUCE_OPENSTAX.md b/benchmarks/mathcraft_ocr/REPRODUCE_OPENSTAX.md
index 62d893ca..9ad4c456 100644
--- a/benchmarks/mathcraft_ocr/REPRODUCE_OPENSTAX.md
+++ b/benchmarks/mathcraft_ocr/REPRODUCE_OPENSTAX.md
@@ -7,7 +7,7 @@ evidence. They do not provide formula-level ground truth labels.
## Default Systematic Run
```powershell
-powershell -ExecutionPolicy Bypass -File E:\LaTexSnipper\benchmarks\mathcraft_ocr\run_openstax_pages.ps1
+powershell -ExecutionPolicy Bypass -File .\benchmarks\mathcraft_ocr\run_openstax_pages.ps1 -DataRoot ./benchmark-data
```
Default settings:
@@ -25,17 +25,17 @@ command.
## Outputs
```text
-E:\MathCraftBenchData\manifests\openstax_mixed_gpu_144dpi.jsonl
-E:\MathCraftBenchData\runs\openstax_mixed_gpu_144dpi\openstax_mixed_gpu_144dpi_full.jsonl
-E:\MathCraftBenchData\runs\openstax_mixed_gpu_144dpi\openstax_report.md
-E:\MathCraftBenchData\runs\openstax_mixed_gpu_144dpi\openstax_summary.csv
-E:\MathCraftBenchData\runs\openstax_mixed_gpu_144dpi\openstax_page_metrics.csv
+./benchmark-data\manifests\openstax_mixed_gpu_144dpi.jsonl
+./benchmark-data\runs\openstax_mixed_gpu_144dpi\openstax_mixed_gpu_144dpi_full.jsonl
+./benchmark-data\runs\openstax_mixed_gpu_144dpi\openstax_report.md
+./benchmark-data\runs\openstax_mixed_gpu_144dpi\openstax_summary.csv
+./benchmark-data\runs\openstax_mixed_gpu_144dpi\openstax_page_metrics.csv
```
Small source-control evidence tables are mirrored to:
```text
-E:\LaTexSnipper\benchmarks\mathcraft_ocr\results\openstax_mixed_gpu_144dpi
+.\benchmarks\mathcraft_ocr\results\openstax_mixed_gpu_144dpi
```
Current formal run summary:
@@ -52,20 +52,20 @@ Current formal run summary:
Generate paper-oriented block overlay panels from the formal run:
```powershell
-E:\LaTexSnipper\tools\deps\python311\python.exe `
- E:\LaTexSnipper\benchmarks\mathcraft_ocr\visualization\make_openstax_block_gallery.py `
- --results E:\MathCraftBenchData\runs\openstax_mixed_gpu_144dpi\openstax_mixed_gpu_144dpi_full.jsonl `
- --output-dir E:\MathCraftBenchData\runs\openstax_mixed_gpu_144dpi\figures\block_gallery `
+python `
+ .\benchmarks\mathcraft_ocr\visualization\make_openstax_block_gallery.py `
+ --results ./benchmark-data\runs\openstax_mixed_gpu_144dpi\openstax_mixed_gpu_144dpi_full.jsonl `
+ --output-dir ./benchmark-data\runs\openstax_mixed_gpu_144dpi\figures\block_gallery `
--limit 6
```
-The generated PNG panels and HTML gallery remain in `E:\MathCraftBenchData`
+The generated PNG panels and HTML gallery remain in `./benchmark-data`
because the page images are rendered from OpenStax PDFs.
## Smaller Trial
```powershell
-powershell -ExecutionPolicy Bypass -File E:\LaTexSnipper\benchmarks\mathcraft_ocr\run_openstax_pages.ps1 `
+powershell -ExecutionPolicy Bypass -File .\benchmarks\mathcraft_ocr\run_openstax_pages.ps1 -DataRoot ./benchmark-data `
-Pages "calculus_v1:120-124,college_algebra:80-84" `
-RunId "openstax_trial_gpu_144dpi"
```
@@ -75,10 +75,10 @@ powershell -ExecutionPolicy Bypass -File E:\LaTexSnipper\benchmarks\mathcraft_oc
Use explicit page ranges and keep `-ShardSize 1` for best resume behavior:
```powershell
-powershell -ExecutionPolicy Bypass -File E:\LaTexSnipper\benchmarks\mathcraft_ocr\run_openstax_pages.ps1 `
+powershell -ExecutionPolicy Bypass -File .\benchmarks\mathcraft_ocr\run_openstax_pages.ps1 -DataRoot ./benchmark-data `
-Pages "calculus_v1:20-219,college_algebra:20-219" `
-RunId "openstax_400pages_gpu_144dpi"
```
Large JSONL outputs, rendered page images, and downloaded PDFs remain under
-`E:\MathCraftBenchData` and should not be committed.
+`./benchmark-data` and should not be committed.
diff --git a/benchmarks/mathcraft_ocr/REPRODUCE_UNIMER.md b/benchmarks/mathcraft_ocr/REPRODUCE_UNIMER.md
index 5fffb35b..6456d05b 100644
--- a/benchmarks/mathcraft_ocr/REPRODUCE_UNIMER.md
+++ b/benchmarks/mathcraft_ocr/REPRODUCE_UNIMER.md
@@ -7,14 +7,14 @@ paper tables. Large datasets and run outputs are kept outside the repository.
- Windows 11
- TeX Live 2024 for render checks
-- Python runtime: `tools\deps\python311\python.exe`
+- Python runtime: the developer-selected Python interpreter
- ONNX provider requested: `gpu`
- Observed active provider: `CUDAExecutionProvider`
Install benchmark-only metric dependencies into the development runtime:
```powershell
-.\tools\deps\python311\python.exe -m pip install rapidfuzz evaluate
+python -m pip install rapidfuzz evaluate
```
## Data Root
@@ -22,7 +22,7 @@ Install benchmark-only metric dependencies into the development runtime:
Use a local data root:
```text
-E:\MathCraftBenchData
+./benchmark-data
```
The scripts download `UniMER-Test.zip` from the public Hugging Face dataset
@@ -31,7 +31,7 @@ The scripts download `UniMER-Test.zip` from the public Hugging Face dataset
## Run MathCraft OCR
```powershell
-powershell -ExecutionPolicy Bypass -File E:\LaTexSnipper\benchmarks\mathcraft_ocr\run_unimer_test.ps1
+powershell -ExecutionPolicy Bypass -File .\benchmarks\mathcraft_ocr\run_unimer_test.ps1 -DataRoot ./benchmark-data
```
The runner is sharded and resumable. Completed shard files are skipped; partial
@@ -40,16 +40,16 @@ shards are rerun.
Main output:
```text
-E:\MathCraftBenchData\runs\unimer_test_gpu\unimer_test_gpu_full.jsonl
+./benchmark-data\runs\unimer_test_gpu\unimer_test_gpu_full.jsonl
```
## Text Metrics
```powershell
-.\tools\deps\python311\python.exe benchmarks\mathcraft_ocr\reports\analyze_unimer_results.py `
- --results E:\MathCraftBenchData\runs\unimer_test_gpu\unimer_test_gpu_full.jsonl `
- --manifest E:\MathCraftBenchData\manifests\unimer_test_full.jsonl `
- --output-dir E:\MathCraftBenchData\runs\unimer_test_gpu
+python benchmarks\mathcraft_ocr\reports\analyze_unimer_results.py `
+ --results ./benchmark-data\runs\unimer_test_gpu\unimer_test_gpu_full.jsonl `
+ --manifest ./benchmark-data\manifests\unimer_test_full.jsonl `
+ --output-dir ./benchmark-data\runs\unimer_test_gpu
```
This generates BLEU-4, normalized edit distance, exact-match, similarity, and
@@ -60,10 +60,10 @@ latency tables.
This is a local render-consistency fallback for paper analysis, not CDM.
```powershell
-.\tools\deps\python311\python.exe benchmarks\mathcraft_ocr\reports\render_unimer_samples.py `
- --results E:\MathCraftBenchData\runs\unimer_test_gpu\unimer_test_gpu_full.jsonl `
- --manifest E:\MathCraftBenchData\manifests\unimer_test_full.jsonl `
- --output-dir E:\MathCraftBenchData\runs\unimer_test_gpu\render_exact_underestimated `
+python benchmarks\mathcraft_ocr\reports\render_unimer_samples.py `
+ --results ./benchmark-data\runs\unimer_test_gpu\unimer_test_gpu_full.jsonl `
+ --manifest ./benchmark-data\manifests\unimer_test_full.jsonl `
+ --output-dir ./benchmark-data\runs\unimer_test_gpu\render_exact_underestimated `
--per-subset 20 `
--min-similarity 0.95 `
--keep-images
@@ -72,10 +72,10 @@ This is a local render-consistency fallback for paper analysis, not CDM.
Create an HTML gallery:
```powershell
-.\tools\deps\python311\python.exe benchmarks\mathcraft_ocr\visualization\make_render_gallery.py `
- --rows E:\MathCraftBenchData\runs\unimer_test_gpu\render_exact_underestimated\unimer_render_consistency_rows.csv `
- --image-dir E:\MathCraftBenchData\runs\unimer_test_gpu\render_exact_underestimated\rendered_pairs `
- --output E:\MathCraftBenchData\runs\unimer_test_gpu\render_exact_underestimated\render_gallery.html `
+python benchmarks\mathcraft_ocr\visualization\make_render_gallery.py `
+ --rows ./benchmark-data\runs\unimer_test_gpu\render_exact_underestimated\unimer_render_consistency_rows.csv `
+ --image-dir ./benchmark-data\runs\unimer_test_gpu\render_exact_underestimated\rendered_pairs `
+ --output ./benchmark-data\runs\unimer_test_gpu\render_exact_underestimated\render_gallery.html `
--limit 40
```
@@ -85,20 +85,20 @@ CDM expects a batch JSON file with `img_id`, `gt`, and `pred` fields. Convert th
full MathCraft UniMER-Test run to that schema:
```powershell
-.\tools\deps\python311\python.exe benchmarks\mathcraft_ocr\reports\prepare_cdm_input.py `
- --results E:\MathCraftBenchData\runs\unimer_test_gpu\unimer_test_gpu_full.jsonl `
- --manifest E:\MathCraftBenchData\manifests\unimer_test_full.jsonl `
- --output E:\MathCraftBenchData\runs\unimer_test_gpu\cdm_input\unimer_test_full_cdm.json `
+python benchmarks\mathcraft_ocr\reports\prepare_cdm_input.py `
+ --results ./benchmark-data\runs\unimer_test_gpu\unimer_test_gpu_full.jsonl `
+ --manifest ./benchmark-data\manifests\unimer_test_full.jsonl `
+ --output ./benchmark-data\runs\unimer_test_gpu\cdm_input\unimer_test_full_cdm.json `
--subset all
```
Subset conversion is also supported:
```powershell
-.\tools\deps\python311\python.exe benchmarks\mathcraft_ocr\reports\prepare_cdm_input.py `
- --results E:\MathCraftBenchData\runs\unimer_test_gpu\unimer_test_gpu_full.jsonl `
- --manifest E:\MathCraftBenchData\manifests\unimer_test_full.jsonl `
- --output E:\MathCraftBenchData\runs\unimer_test_gpu\cdm_input\unimer_test_cpe_cdm.json `
+python benchmarks\mathcraft_ocr\reports\prepare_cdm_input.py `
+ --results ./benchmark-data\runs\unimer_test_gpu\unimer_test_gpu_full.jsonl `
+ --manifest ./benchmark-data\manifests\unimer_test_full.jsonl `
+ --output ./benchmark-data\runs\unimer_test_gpu\cdm_input\unimer_test_cpe_cdm.json `
--subset cpe
```
@@ -111,31 +111,30 @@ dependencies; do not label local fallback metrics as CDM.
Run the official UniMERNet CDM evaluator through the resumable benchmark entry:
```powershell
-powershell -ExecutionPolicy Bypass -File E:\LaTexSnipper\benchmarks\mathcraft_ocr\run_official_cdm.ps1
+powershell -ExecutionPolicy Bypass -File .\benchmarks\mathcraft_ocr\run_official_cdm.ps1 -DataRoot ./benchmark-data
```
Defaults:
```text
-Input: E:\MathCraftBenchData\runs\unimer_test_gpu\cdm_input\unimer_test_full_cdm.json
-Output: E:\MathCraftBenchData\runs\cdm_official_unimer_full
-CDM code: E:\MathCraftBenchData\sources\UniMERNet_official\cdm
-Python: D:\Python312\python.exe
+Input: ./benchmark-data\runs\unimer_test_gpu\cdm_input\unimer_test_full_cdm.json
+Output: ./benchmark-data\runs\cdm_official_unimer_full
+CDM code: ./benchmark-data\sources\UniMERNet_official\cdm
+Python: python
Pools: 8
ShardSize: 100
```
-`tools\deps\python311` is the MathCraft development/runtime environment. The
-official CDM clone is evaluated with the system Python above because the
-isolated `tools\deps` runtime does not expose the official CDM source tree on
-`sys.path`.
+MathCraft inference uses the selected development environment. The official CDM
+runner can use a separate environment with its own dependencies and source tree.
+Pass -PythonPath to select an interpreter for the benchmark runners.
The runner is resumable. A shard is skipped only when it has a local completion
marker from a successful official CDM process. It writes:
```text
-E:\MathCraftBenchData\runs\cdm_official_unimer_full\metrics_res.json
-E:\MathCraftBenchData\runs\cdm_official_unimer_full\cdm_shard_summary.csv
+./benchmark-data\runs\cdm_official_unimer_full\metrics_res.json
+./benchmark-data\runs\cdm_official_unimer_full\cdm_shard_summary.csv
```
The runner also writes `.mathcraft_cdm_complete.json` for each shard after the
@@ -146,9 +145,9 @@ the shard CSV records `expected_rows`, `evaluated_rows`, and `skipped_rows`.
For a bounded check:
```powershell
-powershell -ExecutionPolicy Bypass -File E:\LaTexSnipper\benchmarks\mathcraft_ocr\run_official_cdm.ps1 `
- -InputPath E:\MathCraftBenchData\runs\unimer_test_gpu\cdm_input\unimer_test_all_20_cdm.json `
- -OutputDir E:\MathCraftBenchData\runs\cdm_official_unimer_20_runner `
+powershell -ExecutionPolicy Bypass -File .\benchmarks\mathcraft_ocr\run_official_cdm.ps1 -DataRoot ./benchmark-data `
+ -InputPath ./benchmark-data\runs\unimer_test_gpu\cdm_input\unimer_test_all_20_cdm.json `
+ -OutputDir ./benchmark-data\runs\cdm_official_unimer_20_runner `
-ShardSize 10 `
-Limit 20
```
@@ -182,7 +181,7 @@ prints `extract bbox` progress during the rendering/bbox stage.
Full official CDM run:
```text
-Run: E:\MathCraftBenchData\runs\cdm_official_unimer_full
+Run: ./benchmark-data\runs\cdm_official_unimer_full
Mean CDM: 0.929
ExpRate: 0.648
Expected: 23757
@@ -194,12 +193,12 @@ Shards: 238 / 238 complete
Generate compact source-controlled CDM tables:
```powershell
-D:\Python312\python.exe E:\LaTexSnipper\benchmarks\mathcraft_ocr\reports\analyze_cdm_results.py `
- --metrics E:\MathCraftBenchData\runs\cdm_official_unimer_full\metrics_res.json `
- --input E:\MathCraftBenchData\runs\unimer_test_gpu\cdm_input\unimer_test_full_cdm.json `
- --manifest E:\MathCraftBenchData\manifests\unimer_test_full.jsonl `
- --shards E:\MathCraftBenchData\runs\cdm_official_unimer_full\cdm_shard_summary.csv `
- --output-dir E:\LaTexSnipper\benchmarks\mathcraft_ocr\results\unimer_test_gpu
+python .\benchmarks\mathcraft_ocr\reports\analyze_cdm_results.py `
+ --metrics ./benchmark-data\runs\cdm_official_unimer_full\metrics_res.json `
+ --input ./benchmark-data\runs\unimer_test_gpu\cdm_input\unimer_test_full_cdm.json `
+ --manifest ./benchmark-data\manifests\unimer_test_full.jsonl `
+ --shards ./benchmark-data\runs\cdm_official_unimer_full\cdm_shard_summary.csv `
+ --output-dir .\benchmarks\mathcraft_ocr\results\unimer_test_gpu
```
## Source-Controlled Result Evidence
@@ -210,7 +209,7 @@ Small result tables are copied into:
benchmarks/mathcraft_ocr/results/unimer_test_gpu
```
-Do not commit `E:\MathCraftBenchData`, full JSONL outputs, downloaded datasets,
+Do not commit `./benchmark-data`, full JSONL outputs, downloaded datasets,
or rendered PNG pairs into the repository.
## Related Page-Level Data
diff --git a/benchmarks/mathcraft_ocr/datasets/create_openstax_manifest.py b/benchmarks/mathcraft_ocr/datasets/create_openstax_manifest.py
index 9bae51ca..b41418fa 100644
--- a/benchmarks/mathcraft_ocr/datasets/create_openstax_manifest.py
+++ b/benchmarks/mathcraft_ocr/datasets/create_openstax_manifest.py
@@ -46,8 +46,8 @@ class OpenStaxSource:
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description="Create MathCraft page-level manifest from OpenStax PDFs.")
- parser.add_argument("--data-root", default=r"E:\MathCraftBenchData")
- parser.add_argument("--output", default=r"E:\MathCraftBenchData\manifests\openstax_mixed_gpu_144dpi.jsonl")
+ parser.add_argument("--data-root", required=True)
+ parser.add_argument("--output", help="Defaults to manifests/openstax_mixed_gpu_144dpi.jsonl under --data-root")
parser.add_argument(
"--pages",
default=",".join(f"{key}:{value}" for key, value in DEFAULT_PAGES.items()),
@@ -59,7 +59,7 @@ def main(argv: list[str] | None = None) -> int:
data_root = Path(args.data_root)
raw_dir = data_root / "raw" / "openstax"
image_dir = data_root / "processed" / "openstax_pages"
- output = Path(args.output)
+ output = Path(args.output) if args.output else data_root / "manifests" / "openstax_mixed_gpu_144dpi.jsonl"
raw_dir.mkdir(parents=True, exist_ok=True)
image_dir.mkdir(parents=True, exist_ok=True)
output.parent.mkdir(parents=True, exist_ok=True)
diff --git a/benchmarks/mathcraft_ocr/reports/run_official_cdm.py b/benchmarks/mathcraft_ocr/reports/run_official_cdm.py
index 76f6604d..fe975e5f 100644
--- a/benchmarks/mathcraft_ocr/reports/run_official_cdm.py
+++ b/benchmarks/mathcraft_ocr/reports/run_official_cdm.py
@@ -46,10 +46,10 @@ def main(argv: list[str] | None = None) -> int:
parser.add_argument("--force", action="store_true")
args = parser.parse_args(argv)
- input_path = Path(args.input)
- output_dir = Path(args.output_dir)
- cdm_dir = Path(args.cdm_dir)
- python_path = Path(args.python)
+ input_path = Path(args.input).resolve()
+ output_dir = Path(args.output_dir).resolve()
+ cdm_dir = Path(args.cdm_dir).resolve()
+ python_path = Path(args.python).resolve()
validate_args(input_path, output_dir, cdm_dir, python_path, args.shard_size, args.pools)
diff --git a/benchmarks/mathcraft_ocr/results/unimer_test_gpu/cdm_official_shard_summary.csv b/benchmarks/mathcraft_ocr/results/unimer_test_gpu/cdm_official_shard_summary.csv
index 9a22b10b..73dd8b9e 100644
--- a/benchmarks/mathcraft_ocr/results/unimer_test_gpu/cdm_official_shard_summary.csv
+++ b/benchmarks/mathcraft_ocr/results/unimer_test_gpu/cdm_official_shard_summary.csv
@@ -1,239 +1,239 @@
shard,start,end_exclusive,expected_rows,evaluated_rows,skipped_rows,state,mean_score,exp_rate,metrics_path
-1,0,100,100,100,0,complete,0.99,0.79,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_00000_00099\metrics_res.json
-2,100,200,100,100,0,complete,0.994,0.83,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_00100_00199\metrics_res.json
-3,200,300,100,100,0,complete,0.989,0.76,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_00200_00299\metrics_res.json
-4,300,400,100,100,0,complete,0.997,0.9,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_00300_00399\metrics_res.json
-5,400,500,100,100,0,complete,0.992,0.79,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_00400_00499\metrics_res.json
-6,500,600,100,100,0,complete,0.986,0.74,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_00500_00599\metrics_res.json
-7,600,700,100,100,0,complete,0.992,0.84,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_00600_00699\metrics_res.json
-8,700,800,100,100,0,complete,0.985,0.85,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_00700_00799\metrics_res.json
-9,800,900,100,100,0,complete,0.99,0.8,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_00800_00899\metrics_res.json
-10,900,1000,100,100,0,complete,0.994,0.86,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_00900_00999\metrics_res.json
-11,1000,1100,100,100,0,complete,0.991,0.81,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_01000_01099\metrics_res.json
-12,1100,1200,100,100,0,complete,0.993,0.83,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_01100_01199\metrics_res.json
-13,1200,1300,100,100,0,complete,0.983,0.77,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_01200_01299\metrics_res.json
-14,1300,1400,100,100,0,complete,0.992,0.81,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_01300_01399\metrics_res.json
-15,1400,1500,100,100,0,complete,0.989,0.78,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_01400_01499\metrics_res.json
-16,1500,1600,100,100,0,complete,0.992,0.86,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_01500_01599\metrics_res.json
-17,1600,1700,100,100,0,complete,0.988,0.78,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_01600_01699\metrics_res.json
-18,1700,1800,100,100,0,complete,0.984,0.77,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_01700_01799\metrics_res.json
-19,1800,1900,100,100,0,complete,0.992,0.85,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_01800_01899\metrics_res.json
-20,1900,2000,100,100,0,complete,0.983,0.73,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_01900_01999\metrics_res.json
-21,2000,2100,100,100,0,complete,0.991,0.79,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_02000_02099\metrics_res.json
-22,2100,2200,100,100,0,complete,0.984,0.83,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_02100_02199\metrics_res.json
-23,2200,2300,100,100,0,complete,0.992,0.81,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_02200_02299\metrics_res.json
-24,2300,2400,100,100,0,complete,0.995,0.8,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_02300_02399\metrics_res.json
-25,2400,2500,100,100,0,complete,0.987,0.72,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_02400_02499\metrics_res.json
-26,2500,2600,100,100,0,complete,0.982,0.8,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_02500_02599\metrics_res.json
-27,2600,2700,100,100,0,complete,0.992,0.87,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_02600_02699\metrics_res.json
-28,2700,2800,100,100,0,complete,0.991,0.82,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_02700_02799\metrics_res.json
-29,2800,2900,100,100,0,complete,0.992,0.88,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_02800_02899\metrics_res.json
-30,2900,3000,100,100,0,complete,0.983,0.8,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_02900_02999\metrics_res.json
-31,3000,3100,100,100,0,complete,0.989,0.8,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_03000_03099\metrics_res.json
-32,3100,3200,100,100,0,complete,0.979,0.86,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_03100_03199\metrics_res.json
-33,3200,3300,100,100,0,complete,0.989,0.81,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_03200_03299\metrics_res.json
-34,3300,3400,100,100,0,complete,0.993,0.79,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_03300_03399\metrics_res.json
-35,3400,3500,100,100,0,complete,0.989,0.78,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_03400_03499\metrics_res.json
-36,3500,3600,100,100,0,complete,0.977,0.82,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_03500_03599\metrics_res.json
-37,3600,3700,100,100,0,complete,0.98,0.86,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_03600_03699\metrics_res.json
-38,3700,3800,100,100,0,complete,0.993,0.87,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_03700_03799\metrics_res.json
-39,3800,3900,100,100,0,complete,0.994,0.89,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_03800_03899\metrics_res.json
-40,3900,4000,100,100,0,complete,0.983,0.78,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_03900_03999\metrics_res.json
-41,4000,4100,100,100,0,complete,0.992,0.87,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_04000_04099\metrics_res.json
-42,4100,4200,100,100,0,complete,0.99,0.76,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_04100_04199\metrics_res.json
-43,4200,4300,100,100,0,complete,0.989,0.82,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_04200_04299\metrics_res.json
-44,4300,4400,100,100,0,complete,0.994,0.86,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_04300_04399\metrics_res.json
-45,4400,4500,100,100,0,complete,0.992,0.94,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_04400_04499\metrics_res.json
-46,4500,4600,100,100,0,complete,0.997,0.92,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_04500_04599\metrics_res.json
-47,4600,4700,100,100,0,complete,0.995,0.93,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_04600_04699\metrics_res.json
-48,4700,4800,100,100,0,complete,0.996,0.91,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_04700_04799\metrics_res.json
-49,4800,4900,100,100,0,complete,0.991,0.93,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_04800_04899\metrics_res.json
-50,4900,5000,100,100,0,complete,0.993,0.92,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_04900_04999\metrics_res.json
-51,5000,5100,100,100,0,complete,0.989,0.91,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_05000_05099\metrics_res.json
-52,5100,5200,100,99,1,complete,0.992,0.899,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_05100_05199\metrics_res.json
-53,5200,5300,100,100,0,complete,0.992,0.9,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_05200_05299\metrics_res.json
-54,5300,5400,100,100,0,complete,0.995,0.97,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_05300_05399\metrics_res.json
-55,5400,5500,100,100,0,complete,0.985,0.92,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_05400_05499\metrics_res.json
-56,5500,5600,100,100,0,complete,0.997,0.93,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_05500_05599\metrics_res.json
-57,5600,5700,100,100,0,complete,0.988,0.89,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_05600_05699\metrics_res.json
-58,5700,5800,100,100,0,complete,0.986,0.87,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_05700_05799\metrics_res.json
-59,5800,5900,100,100,0,complete,0.996,0.91,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_05800_05899\metrics_res.json
-60,5900,6000,100,100,0,complete,0.992,0.94,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_05900_05999\metrics_res.json
-61,6000,6100,100,100,0,complete,0.994,0.93,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_06000_06099\metrics_res.json
-62,6100,6200,100,100,0,complete,0.979,0.83,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_06100_06199\metrics_res.json
-63,6200,6300,100,100,0,complete,0.996,0.95,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_06200_06299\metrics_res.json
-64,6300,6400,100,100,0,complete,0.989,0.94,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_06300_06399\metrics_res.json
-65,6400,6500,100,100,0,complete,0.991,0.91,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_06400_06499\metrics_res.json
-66,6500,6600,100,100,0,complete,0.996,0.94,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_06500_06599\metrics_res.json
-67,6600,6700,100,100,0,complete,0.994,0.94,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_06600_06699\metrics_res.json
-68,6700,6800,100,100,0,complete,0.959,0.71,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_06700_06799\metrics_res.json
-69,6800,6900,100,99,1,complete,0.813,0.374,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_06800_06899\metrics_res.json
-70,6900,7000,100,99,1,complete,0.864,0.293,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_06900_06999\metrics_res.json
-71,7000,7100,100,99,1,complete,0.845,0.253,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_07000_07099\metrics_res.json
-72,7100,7200,100,100,0,complete,0.913,0.29,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_07100_07199\metrics_res.json
-73,7200,7300,100,100,0,complete,0.86,0.3,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_07200_07299\metrics_res.json
-74,7300,7400,100,100,0,complete,0.863,0.3,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_07300_07399\metrics_res.json
-75,7400,7500,100,100,0,complete,0.863,0.29,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_07400_07499\metrics_res.json
-76,7500,7600,100,100,0,complete,0.844,0.35,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_07500_07599\metrics_res.json
-77,7600,7700,100,100,0,complete,0.85,0.3,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_07600_07699\metrics_res.json
-78,7700,7800,100,100,0,complete,0.922,0.47,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_07700_07799\metrics_res.json
-79,7800,7900,100,100,0,complete,0.876,0.28,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_07800_07899\metrics_res.json
-80,7900,8000,100,100,0,complete,0.892,0.36,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_07900_07999\metrics_res.json
-81,8000,8100,100,99,1,complete,0.898,0.364,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_08000_08099\metrics_res.json
-82,8100,8200,100,99,1,complete,0.893,0.354,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_08100_08199\metrics_res.json
-83,8200,8300,100,100,0,complete,0.848,0.29,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_08200_08299\metrics_res.json
-84,8300,8400,100,100,0,complete,0.872,0.27,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_08300_08399\metrics_res.json
-85,8400,8500,100,100,0,complete,0.868,0.36,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_08400_08499\metrics_res.json
-86,8500,8600,100,99,1,complete,0.847,0.313,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_08500_08599\metrics_res.json
-87,8600,8700,100,100,0,complete,0.916,0.41,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_08600_08699\metrics_res.json
-88,8700,8800,100,100,0,complete,0.88,0.28,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_08700_08799\metrics_res.json
-89,8800,8900,100,100,0,complete,0.875,0.32,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_08800_08899\metrics_res.json
-90,8900,9000,100,100,0,complete,0.857,0.31,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_08900_08999\metrics_res.json
-91,9000,9100,100,100,0,complete,0.867,0.29,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_09000_09099\metrics_res.json
-92,9100,9200,100,100,0,complete,0.886,0.36,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_09100_09199\metrics_res.json
-93,9200,9300,100,100,0,complete,0.875,0.25,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_09200_09299\metrics_res.json
-94,9300,9400,100,100,0,complete,0.902,0.34,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_09300_09399\metrics_res.json
-95,9400,9500,100,100,0,complete,0.902,0.39,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_09400_09499\metrics_res.json
-96,9500,9600,100,100,0,complete,0.867,0.36,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_09500_09599\metrics_res.json
-97,9600,9700,100,100,0,complete,0.885,0.32,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_09600_09699\metrics_res.json
-98,9700,9800,100,100,0,complete,0.884,0.32,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_09700_09799\metrics_res.json
-99,9800,9900,100,100,0,complete,0.896,0.36,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_09800_09899\metrics_res.json
-100,9900,10000,100,100,0,complete,0.882,0.27,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_09900_09999\metrics_res.json
-101,10000,10100,100,100,0,complete,0.897,0.32,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_10000_10099\metrics_res.json
-102,10100,10200,100,100,0,complete,0.882,0.4,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_10100_10199\metrics_res.json
-103,10200,10300,100,99,1,complete,0.901,0.313,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_10200_10299\metrics_res.json
-104,10300,10400,100,100,0,complete,0.836,0.31,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_10300_10399\metrics_res.json
-105,10400,10500,100,100,0,complete,0.894,0.36,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_10400_10499\metrics_res.json
-106,10500,10600,100,100,0,complete,0.914,0.28,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_10500_10599\metrics_res.json
-107,10600,10700,100,100,0,complete,0.885,0.31,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_10600_10699\metrics_res.json
-108,10700,10800,100,100,0,complete,0.891,0.37,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_10700_10799\metrics_res.json
-109,10800,10900,100,100,0,complete,0.891,0.31,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_10800_10899\metrics_res.json
-110,10900,11000,100,100,0,complete,0.868,0.31,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_10900_10999\metrics_res.json
-111,11000,11100,100,100,0,complete,0.934,0.4,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_11000_11099\metrics_res.json
-112,11100,11200,100,100,0,complete,0.909,0.27,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_11100_11199\metrics_res.json
-113,11200,11300,100,100,0,complete,0.869,0.3,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_11200_11299\metrics_res.json
-114,11300,11400,100,100,0,complete,0.897,0.41,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_11300_11399\metrics_res.json
-115,11400,11500,100,100,0,complete,0.9,0.32,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_11400_11499\metrics_res.json
-116,11500,11600,100,99,1,complete,0.843,0.333,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_11500_11599\metrics_res.json
-117,11600,11700,100,100,0,complete,0.828,0.26,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_11600_11699\metrics_res.json
-118,11700,11800,100,99,1,complete,0.9,0.343,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_11700_11799\metrics_res.json
-119,11800,11900,100,100,0,complete,0.846,0.34,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_11800_11899\metrics_res.json
-120,11900,12000,100,100,0,complete,0.844,0.29,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_11900_11999\metrics_res.json
-121,12000,12100,100,100,0,complete,0.888,0.32,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_12000_12099\metrics_res.json
-122,12100,12200,100,99,1,complete,0.872,0.343,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_12100_12199\metrics_res.json
-123,12200,12300,100,100,0,complete,0.901,0.28,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_12200_12299\metrics_res.json
-124,12300,12400,100,100,0,complete,0.895,0.32,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_12300_12399\metrics_res.json
-125,12400,12500,100,100,0,complete,0.912,0.43,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_12400_12499\metrics_res.json
-126,12500,12600,100,100,0,complete,0.9,0.31,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_12500_12599\metrics_res.json
-127,12600,12700,100,100,0,complete,0.872,0.35,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_12600_12699\metrics_res.json
-128,12700,12800,100,100,0,complete,0.94,0.82,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_12700_12799\metrics_res.json
-129,12800,12900,100,100,0,complete,0.921,0.71,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_12800_12899\metrics_res.json
-130,12900,13000,100,99,1,complete,0.943,0.788,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_12900_12999\metrics_res.json
-131,13000,13100,100,99,1,complete,0.922,0.737,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_13000_13099\metrics_res.json
-132,13100,13200,100,99,1,complete,0.905,0.747,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_13100_13199\metrics_res.json
-133,13200,13300,100,100,0,complete,0.912,0.68,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_13200_13299\metrics_res.json
-134,13300,13400,100,97,3,complete,0.964,0.866,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_13300_13399\metrics_res.json
-135,13400,13500,100,100,0,complete,0.939,0.75,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_13400_13499\metrics_res.json
-136,13500,13600,100,99,1,complete,0.912,0.707,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_13500_13599\metrics_res.json
-137,13600,13700,100,99,1,complete,0.929,0.758,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_13600_13699\metrics_res.json
-138,13700,13800,100,98,2,complete,0.954,0.827,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_13700_13799\metrics_res.json
-139,13800,13900,100,98,2,complete,0.941,0.724,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_13800_13899\metrics_res.json
-140,13900,14000,100,100,0,complete,0.968,0.81,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_13900_13999\metrics_res.json
-141,14000,14100,100,99,1,complete,0.919,0.768,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_14000_14099\metrics_res.json
-142,14100,14200,100,98,2,complete,0.97,0.796,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_14100_14199\metrics_res.json
-143,14200,14300,100,100,0,complete,0.928,0.73,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_14200_14299\metrics_res.json
-144,14300,14400,100,100,0,complete,0.951,0.77,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_14300_14399\metrics_res.json
-145,14400,14500,100,100,0,complete,0.922,0.76,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_14400_14499\metrics_res.json
-146,14500,14600,100,97,3,complete,0.942,0.742,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_14500_14599\metrics_res.json
-147,14600,14700,100,99,1,complete,0.947,0.768,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_14600_14699\metrics_res.json
-148,14700,14800,100,100,0,complete,0.944,0.8,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_14700_14799\metrics_res.json
-149,14800,14900,100,100,0,complete,0.952,0.77,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_14800_14899\metrics_res.json
-150,14900,15000,100,100,0,complete,0.957,0.8,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_14900_14999\metrics_res.json
-151,15000,15100,100,98,2,complete,0.935,0.806,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_15000_15099\metrics_res.json
-152,15100,15200,100,98,2,complete,0.958,0.806,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_15100_15199\metrics_res.json
-153,15200,15300,100,100,0,complete,0.915,0.68,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_15200_15299\metrics_res.json
-154,15300,15400,100,100,0,complete,0.943,0.78,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_15300_15399\metrics_res.json
-155,15400,15500,100,100,0,complete,0.916,0.7,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_15400_15499\metrics_res.json
-156,15500,15600,100,96,4,complete,0.951,0.781,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_15500_15599\metrics_res.json
-157,15600,15700,100,100,0,complete,0.935,0.74,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_15600_15699\metrics_res.json
-158,15700,15800,100,99,1,complete,0.929,0.758,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_15700_15799\metrics_res.json
-159,15800,15900,100,100,0,complete,0.939,0.82,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_15800_15899\metrics_res.json
-160,15900,16000,100,100,0,complete,0.93,0.78,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_15900_15999\metrics_res.json
-161,16000,16100,100,100,0,complete,0.936,0.75,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_16000_16099\metrics_res.json
-162,16100,16200,100,100,0,complete,0.955,0.81,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_16100_16199\metrics_res.json
-163,16200,16300,100,100,0,complete,0.923,0.73,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_16200_16299\metrics_res.json
-164,16300,16400,100,99,1,complete,0.941,0.778,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_16300_16399\metrics_res.json
-165,16400,16500,100,100,0,complete,0.95,0.8,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_16400_16499\metrics_res.json
-166,16500,16600,100,99,1,complete,0.942,0.737,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_16500_16599\metrics_res.json
-167,16600,16700,100,99,1,complete,0.956,0.758,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_16600_16699\metrics_res.json
-168,16700,16800,100,99,1,complete,0.93,0.778,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_16700_16799\metrics_res.json
-169,16800,16900,100,99,1,complete,0.934,0.778,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_16800_16899\metrics_res.json
-170,16900,17000,100,100,0,complete,0.938,0.84,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_16900_16999\metrics_res.json
-171,17000,17100,100,100,0,complete,0.914,0.74,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_17000_17099\metrics_res.json
-172,17100,17200,100,100,0,complete,0.935,0.77,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_17100_17199\metrics_res.json
-173,17200,17300,100,100,0,complete,0.956,0.85,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_17200_17299\metrics_res.json
-174,17300,17400,100,100,0,complete,0.936,0.76,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_17300_17399\metrics_res.json
-175,17400,17500,100,100,0,complete,0.911,0.69,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_17400_17499\metrics_res.json
-176,17500,17600,100,100,0,complete,0.875,0.61,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_17500_17599\metrics_res.json
-177,17600,17700,100,99,1,complete,0.911,0.657,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_17600_17699\metrics_res.json
-178,17700,17800,100,100,0,complete,0.895,0.62,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_17700_17799\metrics_res.json
-179,17800,17900,100,100,0,complete,0.905,0.61,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_17800_17899\metrics_res.json
-180,17900,18000,100,100,0,complete,0.878,0.63,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_17900_17999\metrics_res.json
-181,18000,18100,100,100,0,complete,0.924,0.66,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_18000_18099\metrics_res.json
-182,18100,18200,100,100,0,complete,0.86,0.61,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_18100_18199\metrics_res.json
-183,18200,18300,100,100,0,complete,0.92,0.64,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_18200_18299\metrics_res.json
-184,18300,18400,100,99,1,complete,0.878,0.586,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_18300_18399\metrics_res.json
-185,18400,18500,100,100,0,complete,0.917,0.67,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_18400_18499\metrics_res.json
-186,18500,18600,100,100,0,complete,0.89,0.61,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_18500_18599\metrics_res.json
-187,18600,18700,100,100,0,complete,0.896,0.58,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_18600_18699\metrics_res.json
-188,18700,18800,100,99,1,complete,0.928,0.667,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_18700_18799\metrics_res.json
-189,18800,18900,100,100,0,complete,0.916,0.64,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_18800_18899\metrics_res.json
-190,18900,19000,100,100,0,complete,0.899,0.62,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_18900_18999\metrics_res.json
-191,19000,19100,100,100,0,complete,0.917,0.7,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_19000_19099\metrics_res.json
-192,19100,19200,100,100,0,complete,0.896,0.63,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_19100_19199\metrics_res.json
-193,19200,19300,100,99,1,complete,0.912,0.667,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_19200_19299\metrics_res.json
-194,19300,19400,100,100,0,complete,0.86,0.48,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_19300_19399\metrics_res.json
-195,19400,19500,100,100,0,complete,0.891,0.6,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_19400_19499\metrics_res.json
-196,19500,19600,100,100,0,complete,0.879,0.58,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_19500_19599\metrics_res.json
-197,19600,19700,100,100,0,complete,0.91,0.66,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_19600_19699\metrics_res.json
-198,19700,19800,100,100,0,complete,0.914,0.69,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_19700_19799\metrics_res.json
-199,19800,19900,100,100,0,complete,0.915,0.65,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_19800_19899\metrics_res.json
-200,19900,20000,100,100,0,complete,0.925,0.71,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_19900_19999\metrics_res.json
-201,20000,20100,100,100,0,complete,0.916,0.69,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_20000_20099\metrics_res.json
-202,20100,20200,100,100,0,complete,0.902,0.64,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_20100_20199\metrics_res.json
-203,20200,20300,100,99,1,complete,0.928,0.687,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_20200_20299\metrics_res.json
-204,20300,20400,100,100,0,complete,0.904,0.66,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_20300_20399\metrics_res.json
-205,20400,20500,100,100,0,complete,0.886,0.62,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_20400_20499\metrics_res.json
-206,20500,20600,100,100,0,complete,0.933,0.72,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_20500_20599\metrics_res.json
-207,20600,20700,100,100,0,complete,0.893,0.59,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_20600_20699\metrics_res.json
-208,20700,20800,100,100,0,complete,0.894,0.61,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_20700_20799\metrics_res.json
-209,20800,20900,100,100,0,complete,0.919,0.67,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_20800_20899\metrics_res.json
-210,20900,21000,100,100,0,complete,0.906,0.65,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_20900_20999\metrics_res.json
-211,21000,21100,100,99,1,complete,0.89,0.626,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_21000_21099\metrics_res.json
-212,21100,21200,100,100,0,complete,0.881,0.61,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_21100_21199\metrics_res.json
-213,21200,21300,100,99,1,complete,0.904,0.657,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_21200_21299\metrics_res.json
-214,21300,21400,100,100,0,complete,0.876,0.58,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_21300_21399\metrics_res.json
-215,21400,21500,100,99,1,complete,0.944,0.778,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_21400_21499\metrics_res.json
-216,21500,21600,100,100,0,complete,0.928,0.68,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_21500_21599\metrics_res.json
-217,21600,21700,100,100,0,complete,0.918,0.67,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_21600_21699\metrics_res.json
-218,21700,21800,100,100,0,complete,0.899,0.65,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_21700_21799\metrics_res.json
-219,21800,21900,100,99,1,complete,0.932,0.687,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_21800_21899\metrics_res.json
-220,21900,22000,100,99,1,complete,0.915,0.697,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_21900_21999\metrics_res.json
-221,22000,22100,100,100,0,complete,0.879,0.57,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_22000_22099\metrics_res.json
-222,22100,22200,100,99,1,complete,0.877,0.606,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_22100_22199\metrics_res.json
-223,22200,22300,100,100,0,complete,0.879,0.55,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_22200_22299\metrics_res.json
-224,22300,22400,100,100,0,complete,0.887,0.66,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_22300_22399\metrics_res.json
-225,22400,22500,100,100,0,complete,0.895,0.65,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_22400_22499\metrics_res.json
-226,22500,22600,100,100,0,complete,0.911,0.64,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_22500_22599\metrics_res.json
-227,22600,22700,100,100,0,complete,0.911,0.65,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_22600_22699\metrics_res.json
-228,22700,22800,100,100,0,complete,0.926,0.7,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_22700_22799\metrics_res.json
-229,22800,22900,100,100,0,complete,0.905,0.65,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_22800_22899\metrics_res.json
-230,22900,23000,100,100,0,complete,0.921,0.67,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_22900_22999\metrics_res.json
-231,23000,23100,100,100,0,complete,0.887,0.61,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_23000_23099\metrics_res.json
-232,23100,23200,100,100,0,complete,0.901,0.66,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_23100_23199\metrics_res.json
-233,23200,23300,100,99,1,complete,0.905,0.646,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_23200_23299\metrics_res.json
-234,23300,23400,100,100,0,complete,0.906,0.64,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_23300_23399\metrics_res.json
-235,23400,23500,100,100,0,complete,0.891,0.58,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_23400_23499\metrics_res.json
-236,23500,23600,100,100,0,complete,0.88,0.65,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_23500_23599\metrics_res.json
-237,23600,23700,100,100,0,complete,0.904,0.58,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_23600_23699\metrics_res.json
-238,23700,23757,57,57,0,complete,0.965,0.772,E:\MathCraftBenchData\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_23700_23756\metrics_res.json
+1,0,100,100,100,0,complete,0.99,0.79,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_00000_00099\metrics_res.json
+2,100,200,100,100,0,complete,0.994,0.83,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_00100_00199\metrics_res.json
+3,200,300,100,100,0,complete,0.989,0.76,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_00200_00299\metrics_res.json
+4,300,400,100,100,0,complete,0.997,0.9,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_00300_00399\metrics_res.json
+5,400,500,100,100,0,complete,0.992,0.79,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_00400_00499\metrics_res.json
+6,500,600,100,100,0,complete,0.986,0.74,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_00500_00599\metrics_res.json
+7,600,700,100,100,0,complete,0.992,0.84,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_00600_00699\metrics_res.json
+8,700,800,100,100,0,complete,0.985,0.85,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_00700_00799\metrics_res.json
+9,800,900,100,100,0,complete,0.99,0.8,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_00800_00899\metrics_res.json
+10,900,1000,100,100,0,complete,0.994,0.86,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_00900_00999\metrics_res.json
+11,1000,1100,100,100,0,complete,0.991,0.81,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_01000_01099\metrics_res.json
+12,1100,1200,100,100,0,complete,0.993,0.83,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_01100_01199\metrics_res.json
+13,1200,1300,100,100,0,complete,0.983,0.77,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_01200_01299\metrics_res.json
+14,1300,1400,100,100,0,complete,0.992,0.81,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_01300_01399\metrics_res.json
+15,1400,1500,100,100,0,complete,0.989,0.78,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_01400_01499\metrics_res.json
+16,1500,1600,100,100,0,complete,0.992,0.86,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_01500_01599\metrics_res.json
+17,1600,1700,100,100,0,complete,0.988,0.78,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_01600_01699\metrics_res.json
+18,1700,1800,100,100,0,complete,0.984,0.77,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_01700_01799\metrics_res.json
+19,1800,1900,100,100,0,complete,0.992,0.85,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_01800_01899\metrics_res.json
+20,1900,2000,100,100,0,complete,0.983,0.73,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_01900_01999\metrics_res.json
+21,2000,2100,100,100,0,complete,0.991,0.79,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_02000_02099\metrics_res.json
+22,2100,2200,100,100,0,complete,0.984,0.83,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_02100_02199\metrics_res.json
+23,2200,2300,100,100,0,complete,0.992,0.81,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_02200_02299\metrics_res.json
+24,2300,2400,100,100,0,complete,0.995,0.8,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_02300_02399\metrics_res.json
+25,2400,2500,100,100,0,complete,0.987,0.72,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_02400_02499\metrics_res.json
+26,2500,2600,100,100,0,complete,0.982,0.8,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_02500_02599\metrics_res.json
+27,2600,2700,100,100,0,complete,0.992,0.87,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_02600_02699\metrics_res.json
+28,2700,2800,100,100,0,complete,0.991,0.82,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_02700_02799\metrics_res.json
+29,2800,2900,100,100,0,complete,0.992,0.88,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_02800_02899\metrics_res.json
+30,2900,3000,100,100,0,complete,0.983,0.8,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_02900_02999\metrics_res.json
+31,3000,3100,100,100,0,complete,0.989,0.8,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_03000_03099\metrics_res.json
+32,3100,3200,100,100,0,complete,0.979,0.86,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_03100_03199\metrics_res.json
+33,3200,3300,100,100,0,complete,0.989,0.81,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_03200_03299\metrics_res.json
+34,3300,3400,100,100,0,complete,0.993,0.79,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_03300_03399\metrics_res.json
+35,3400,3500,100,100,0,complete,0.989,0.78,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_03400_03499\metrics_res.json
+36,3500,3600,100,100,0,complete,0.977,0.82,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_03500_03599\metrics_res.json
+37,3600,3700,100,100,0,complete,0.98,0.86,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_03600_03699\metrics_res.json
+38,3700,3800,100,100,0,complete,0.993,0.87,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_03700_03799\metrics_res.json
+39,3800,3900,100,100,0,complete,0.994,0.89,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_03800_03899\metrics_res.json
+40,3900,4000,100,100,0,complete,0.983,0.78,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_03900_03999\metrics_res.json
+41,4000,4100,100,100,0,complete,0.992,0.87,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_04000_04099\metrics_res.json
+42,4100,4200,100,100,0,complete,0.99,0.76,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_04100_04199\metrics_res.json
+43,4200,4300,100,100,0,complete,0.989,0.82,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_04200_04299\metrics_res.json
+44,4300,4400,100,100,0,complete,0.994,0.86,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_04300_04399\metrics_res.json
+45,4400,4500,100,100,0,complete,0.992,0.94,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_04400_04499\metrics_res.json
+46,4500,4600,100,100,0,complete,0.997,0.92,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_04500_04599\metrics_res.json
+47,4600,4700,100,100,0,complete,0.995,0.93,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_04600_04699\metrics_res.json
+48,4700,4800,100,100,0,complete,0.996,0.91,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_04700_04799\metrics_res.json
+49,4800,4900,100,100,0,complete,0.991,0.93,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_04800_04899\metrics_res.json
+50,4900,5000,100,100,0,complete,0.993,0.92,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_04900_04999\metrics_res.json
+51,5000,5100,100,100,0,complete,0.989,0.91,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_05000_05099\metrics_res.json
+52,5100,5200,100,99,1,complete,0.992,0.899,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_05100_05199\metrics_res.json
+53,5200,5300,100,100,0,complete,0.992,0.9,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_05200_05299\metrics_res.json
+54,5300,5400,100,100,0,complete,0.995,0.97,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_05300_05399\metrics_res.json
+55,5400,5500,100,100,0,complete,0.985,0.92,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_05400_05499\metrics_res.json
+56,5500,5600,100,100,0,complete,0.997,0.93,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_05500_05599\metrics_res.json
+57,5600,5700,100,100,0,complete,0.988,0.89,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_05600_05699\metrics_res.json
+58,5700,5800,100,100,0,complete,0.986,0.87,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_05700_05799\metrics_res.json
+59,5800,5900,100,100,0,complete,0.996,0.91,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_05800_05899\metrics_res.json
+60,5900,6000,100,100,0,complete,0.992,0.94,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_05900_05999\metrics_res.json
+61,6000,6100,100,100,0,complete,0.994,0.93,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_06000_06099\metrics_res.json
+62,6100,6200,100,100,0,complete,0.979,0.83,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_06100_06199\metrics_res.json
+63,6200,6300,100,100,0,complete,0.996,0.95,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_06200_06299\metrics_res.json
+64,6300,6400,100,100,0,complete,0.989,0.94,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_06300_06399\metrics_res.json
+65,6400,6500,100,100,0,complete,0.991,0.91,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_06400_06499\metrics_res.json
+66,6500,6600,100,100,0,complete,0.996,0.94,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_06500_06599\metrics_res.json
+67,6600,6700,100,100,0,complete,0.994,0.94,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_06600_06699\metrics_res.json
+68,6700,6800,100,100,0,complete,0.959,0.71,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_06700_06799\metrics_res.json
+69,6800,6900,100,99,1,complete,0.813,0.374,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_06800_06899\metrics_res.json
+70,6900,7000,100,99,1,complete,0.864,0.293,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_06900_06999\metrics_res.json
+71,7000,7100,100,99,1,complete,0.845,0.253,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_07000_07099\metrics_res.json
+72,7100,7200,100,100,0,complete,0.913,0.29,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_07100_07199\metrics_res.json
+73,7200,7300,100,100,0,complete,0.86,0.3,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_07200_07299\metrics_res.json
+74,7300,7400,100,100,0,complete,0.863,0.3,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_07300_07399\metrics_res.json
+75,7400,7500,100,100,0,complete,0.863,0.29,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_07400_07499\metrics_res.json
+76,7500,7600,100,100,0,complete,0.844,0.35,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_07500_07599\metrics_res.json
+77,7600,7700,100,100,0,complete,0.85,0.3,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_07600_07699\metrics_res.json
+78,7700,7800,100,100,0,complete,0.922,0.47,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_07700_07799\metrics_res.json
+79,7800,7900,100,100,0,complete,0.876,0.28,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_07800_07899\metrics_res.json
+80,7900,8000,100,100,0,complete,0.892,0.36,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_07900_07999\metrics_res.json
+81,8000,8100,100,99,1,complete,0.898,0.364,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_08000_08099\metrics_res.json
+82,8100,8200,100,99,1,complete,0.893,0.354,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_08100_08199\metrics_res.json
+83,8200,8300,100,100,0,complete,0.848,0.29,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_08200_08299\metrics_res.json
+84,8300,8400,100,100,0,complete,0.872,0.27,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_08300_08399\metrics_res.json
+85,8400,8500,100,100,0,complete,0.868,0.36,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_08400_08499\metrics_res.json
+86,8500,8600,100,99,1,complete,0.847,0.313,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_08500_08599\metrics_res.json
+87,8600,8700,100,100,0,complete,0.916,0.41,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_08600_08699\metrics_res.json
+88,8700,8800,100,100,0,complete,0.88,0.28,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_08700_08799\metrics_res.json
+89,8800,8900,100,100,0,complete,0.875,0.32,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_08800_08899\metrics_res.json
+90,8900,9000,100,100,0,complete,0.857,0.31,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_08900_08999\metrics_res.json
+91,9000,9100,100,100,0,complete,0.867,0.29,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_09000_09099\metrics_res.json
+92,9100,9200,100,100,0,complete,0.886,0.36,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_09100_09199\metrics_res.json
+93,9200,9300,100,100,0,complete,0.875,0.25,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_09200_09299\metrics_res.json
+94,9300,9400,100,100,0,complete,0.902,0.34,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_09300_09399\metrics_res.json
+95,9400,9500,100,100,0,complete,0.902,0.39,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_09400_09499\metrics_res.json
+96,9500,9600,100,100,0,complete,0.867,0.36,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_09500_09599\metrics_res.json
+97,9600,9700,100,100,0,complete,0.885,0.32,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_09600_09699\metrics_res.json
+98,9700,9800,100,100,0,complete,0.884,0.32,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_09700_09799\metrics_res.json
+99,9800,9900,100,100,0,complete,0.896,0.36,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_09800_09899\metrics_res.json
+100,9900,10000,100,100,0,complete,0.882,0.27,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_09900_09999\metrics_res.json
+101,10000,10100,100,100,0,complete,0.897,0.32,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_10000_10099\metrics_res.json
+102,10100,10200,100,100,0,complete,0.882,0.4,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_10100_10199\metrics_res.json
+103,10200,10300,100,99,1,complete,0.901,0.313,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_10200_10299\metrics_res.json
+104,10300,10400,100,100,0,complete,0.836,0.31,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_10300_10399\metrics_res.json
+105,10400,10500,100,100,0,complete,0.894,0.36,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_10400_10499\metrics_res.json
+106,10500,10600,100,100,0,complete,0.914,0.28,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_10500_10599\metrics_res.json
+107,10600,10700,100,100,0,complete,0.885,0.31,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_10600_10699\metrics_res.json
+108,10700,10800,100,100,0,complete,0.891,0.37,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_10700_10799\metrics_res.json
+109,10800,10900,100,100,0,complete,0.891,0.31,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_10800_10899\metrics_res.json
+110,10900,11000,100,100,0,complete,0.868,0.31,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_10900_10999\metrics_res.json
+111,11000,11100,100,100,0,complete,0.934,0.4,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_11000_11099\metrics_res.json
+112,11100,11200,100,100,0,complete,0.909,0.27,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_11100_11199\metrics_res.json
+113,11200,11300,100,100,0,complete,0.869,0.3,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_11200_11299\metrics_res.json
+114,11300,11400,100,100,0,complete,0.897,0.41,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_11300_11399\metrics_res.json
+115,11400,11500,100,100,0,complete,0.9,0.32,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_11400_11499\metrics_res.json
+116,11500,11600,100,99,1,complete,0.843,0.333,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_11500_11599\metrics_res.json
+117,11600,11700,100,100,0,complete,0.828,0.26,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_11600_11699\metrics_res.json
+118,11700,11800,100,99,1,complete,0.9,0.343,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_11700_11799\metrics_res.json
+119,11800,11900,100,100,0,complete,0.846,0.34,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_11800_11899\metrics_res.json
+120,11900,12000,100,100,0,complete,0.844,0.29,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_11900_11999\metrics_res.json
+121,12000,12100,100,100,0,complete,0.888,0.32,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_12000_12099\metrics_res.json
+122,12100,12200,100,99,1,complete,0.872,0.343,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_12100_12199\metrics_res.json
+123,12200,12300,100,100,0,complete,0.901,0.28,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_12200_12299\metrics_res.json
+124,12300,12400,100,100,0,complete,0.895,0.32,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_12300_12399\metrics_res.json
+125,12400,12500,100,100,0,complete,0.912,0.43,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_12400_12499\metrics_res.json
+126,12500,12600,100,100,0,complete,0.9,0.31,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_12500_12599\metrics_res.json
+127,12600,12700,100,100,0,complete,0.872,0.35,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_12600_12699\metrics_res.json
+128,12700,12800,100,100,0,complete,0.94,0.82,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_12700_12799\metrics_res.json
+129,12800,12900,100,100,0,complete,0.921,0.71,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_12800_12899\metrics_res.json
+130,12900,13000,100,99,1,complete,0.943,0.788,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_12900_12999\metrics_res.json
+131,13000,13100,100,99,1,complete,0.922,0.737,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_13000_13099\metrics_res.json
+132,13100,13200,100,99,1,complete,0.905,0.747,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_13100_13199\metrics_res.json
+133,13200,13300,100,100,0,complete,0.912,0.68,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_13200_13299\metrics_res.json
+134,13300,13400,100,97,3,complete,0.964,0.866,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_13300_13399\metrics_res.json
+135,13400,13500,100,100,0,complete,0.939,0.75,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_13400_13499\metrics_res.json
+136,13500,13600,100,99,1,complete,0.912,0.707,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_13500_13599\metrics_res.json
+137,13600,13700,100,99,1,complete,0.929,0.758,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_13600_13699\metrics_res.json
+138,13700,13800,100,98,2,complete,0.954,0.827,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_13700_13799\metrics_res.json
+139,13800,13900,100,98,2,complete,0.941,0.724,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_13800_13899\metrics_res.json
+140,13900,14000,100,100,0,complete,0.968,0.81,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_13900_13999\metrics_res.json
+141,14000,14100,100,99,1,complete,0.919,0.768,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_14000_14099\metrics_res.json
+142,14100,14200,100,98,2,complete,0.97,0.796,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_14100_14199\metrics_res.json
+143,14200,14300,100,100,0,complete,0.928,0.73,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_14200_14299\metrics_res.json
+144,14300,14400,100,100,0,complete,0.951,0.77,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_14300_14399\metrics_res.json
+145,14400,14500,100,100,0,complete,0.922,0.76,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_14400_14499\metrics_res.json
+146,14500,14600,100,97,3,complete,0.942,0.742,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_14500_14599\metrics_res.json
+147,14600,14700,100,99,1,complete,0.947,0.768,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_14600_14699\metrics_res.json
+148,14700,14800,100,100,0,complete,0.944,0.8,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_14700_14799\metrics_res.json
+149,14800,14900,100,100,0,complete,0.952,0.77,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_14800_14899\metrics_res.json
+150,14900,15000,100,100,0,complete,0.957,0.8,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_14900_14999\metrics_res.json
+151,15000,15100,100,98,2,complete,0.935,0.806,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_15000_15099\metrics_res.json
+152,15100,15200,100,98,2,complete,0.958,0.806,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_15100_15199\metrics_res.json
+153,15200,15300,100,100,0,complete,0.915,0.68,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_15200_15299\metrics_res.json
+154,15300,15400,100,100,0,complete,0.943,0.78,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_15300_15399\metrics_res.json
+155,15400,15500,100,100,0,complete,0.916,0.7,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_15400_15499\metrics_res.json
+156,15500,15600,100,96,4,complete,0.951,0.781,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_15500_15599\metrics_res.json
+157,15600,15700,100,100,0,complete,0.935,0.74,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_15600_15699\metrics_res.json
+158,15700,15800,100,99,1,complete,0.929,0.758,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_15700_15799\metrics_res.json
+159,15800,15900,100,100,0,complete,0.939,0.82,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_15800_15899\metrics_res.json
+160,15900,16000,100,100,0,complete,0.93,0.78,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_15900_15999\metrics_res.json
+161,16000,16100,100,100,0,complete,0.936,0.75,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_16000_16099\metrics_res.json
+162,16100,16200,100,100,0,complete,0.955,0.81,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_16100_16199\metrics_res.json
+163,16200,16300,100,100,0,complete,0.923,0.73,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_16200_16299\metrics_res.json
+164,16300,16400,100,99,1,complete,0.941,0.778,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_16300_16399\metrics_res.json
+165,16400,16500,100,100,0,complete,0.95,0.8,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_16400_16499\metrics_res.json
+166,16500,16600,100,99,1,complete,0.942,0.737,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_16500_16599\metrics_res.json
+167,16600,16700,100,99,1,complete,0.956,0.758,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_16600_16699\metrics_res.json
+168,16700,16800,100,99,1,complete,0.93,0.778,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_16700_16799\metrics_res.json
+169,16800,16900,100,99,1,complete,0.934,0.778,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_16800_16899\metrics_res.json
+170,16900,17000,100,100,0,complete,0.938,0.84,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_16900_16999\metrics_res.json
+171,17000,17100,100,100,0,complete,0.914,0.74,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_17000_17099\metrics_res.json
+172,17100,17200,100,100,0,complete,0.935,0.77,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_17100_17199\metrics_res.json
+173,17200,17300,100,100,0,complete,0.956,0.85,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_17200_17299\metrics_res.json
+174,17300,17400,100,100,0,complete,0.936,0.76,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_17300_17399\metrics_res.json
+175,17400,17500,100,100,0,complete,0.911,0.69,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_17400_17499\metrics_res.json
+176,17500,17600,100,100,0,complete,0.875,0.61,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_17500_17599\metrics_res.json
+177,17600,17700,100,99,1,complete,0.911,0.657,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_17600_17699\metrics_res.json
+178,17700,17800,100,100,0,complete,0.895,0.62,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_17700_17799\metrics_res.json
+179,17800,17900,100,100,0,complete,0.905,0.61,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_17800_17899\metrics_res.json
+180,17900,18000,100,100,0,complete,0.878,0.63,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_17900_17999\metrics_res.json
+181,18000,18100,100,100,0,complete,0.924,0.66,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_18000_18099\metrics_res.json
+182,18100,18200,100,100,0,complete,0.86,0.61,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_18100_18199\metrics_res.json
+183,18200,18300,100,100,0,complete,0.92,0.64,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_18200_18299\metrics_res.json
+184,18300,18400,100,99,1,complete,0.878,0.586,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_18300_18399\metrics_res.json
+185,18400,18500,100,100,0,complete,0.917,0.67,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_18400_18499\metrics_res.json
+186,18500,18600,100,100,0,complete,0.89,0.61,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_18500_18599\metrics_res.json
+187,18600,18700,100,100,0,complete,0.896,0.58,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_18600_18699\metrics_res.json
+188,18700,18800,100,99,1,complete,0.928,0.667,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_18700_18799\metrics_res.json
+189,18800,18900,100,100,0,complete,0.916,0.64,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_18800_18899\metrics_res.json
+190,18900,19000,100,100,0,complete,0.899,0.62,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_18900_18999\metrics_res.json
+191,19000,19100,100,100,0,complete,0.917,0.7,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_19000_19099\metrics_res.json
+192,19100,19200,100,100,0,complete,0.896,0.63,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_19100_19199\metrics_res.json
+193,19200,19300,100,99,1,complete,0.912,0.667,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_19200_19299\metrics_res.json
+194,19300,19400,100,100,0,complete,0.86,0.48,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_19300_19399\metrics_res.json
+195,19400,19500,100,100,0,complete,0.891,0.6,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_19400_19499\metrics_res.json
+196,19500,19600,100,100,0,complete,0.879,0.58,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_19500_19599\metrics_res.json
+197,19600,19700,100,100,0,complete,0.91,0.66,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_19600_19699\metrics_res.json
+198,19700,19800,100,100,0,complete,0.914,0.69,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_19700_19799\metrics_res.json
+199,19800,19900,100,100,0,complete,0.915,0.65,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_19800_19899\metrics_res.json
+200,19900,20000,100,100,0,complete,0.925,0.71,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_19900_19999\metrics_res.json
+201,20000,20100,100,100,0,complete,0.916,0.69,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_20000_20099\metrics_res.json
+202,20100,20200,100,100,0,complete,0.902,0.64,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_20100_20199\metrics_res.json
+203,20200,20300,100,99,1,complete,0.928,0.687,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_20200_20299\metrics_res.json
+204,20300,20400,100,100,0,complete,0.904,0.66,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_20300_20399\metrics_res.json
+205,20400,20500,100,100,0,complete,0.886,0.62,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_20400_20499\metrics_res.json
+206,20500,20600,100,100,0,complete,0.933,0.72,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_20500_20599\metrics_res.json
+207,20600,20700,100,100,0,complete,0.893,0.59,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_20600_20699\metrics_res.json
+208,20700,20800,100,100,0,complete,0.894,0.61,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_20700_20799\metrics_res.json
+209,20800,20900,100,100,0,complete,0.919,0.67,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_20800_20899\metrics_res.json
+210,20900,21000,100,100,0,complete,0.906,0.65,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_20900_20999\metrics_res.json
+211,21000,21100,100,99,1,complete,0.89,0.626,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_21000_21099\metrics_res.json
+212,21100,21200,100,100,0,complete,0.881,0.61,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_21100_21199\metrics_res.json
+213,21200,21300,100,99,1,complete,0.904,0.657,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_21200_21299\metrics_res.json
+214,21300,21400,100,100,0,complete,0.876,0.58,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_21300_21399\metrics_res.json
+215,21400,21500,100,99,1,complete,0.944,0.778,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_21400_21499\metrics_res.json
+216,21500,21600,100,100,0,complete,0.928,0.68,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_21500_21599\metrics_res.json
+217,21600,21700,100,100,0,complete,0.918,0.67,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_21600_21699\metrics_res.json
+218,21700,21800,100,100,0,complete,0.899,0.65,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_21700_21799\metrics_res.json
+219,21800,21900,100,99,1,complete,0.932,0.687,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_21800_21899\metrics_res.json
+220,21900,22000,100,99,1,complete,0.915,0.697,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_21900_21999\metrics_res.json
+221,22000,22100,100,100,0,complete,0.879,0.57,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_22000_22099\metrics_res.json
+222,22100,22200,100,99,1,complete,0.877,0.606,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_22100_22199\metrics_res.json
+223,22200,22300,100,100,0,complete,0.879,0.55,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_22200_22299\metrics_res.json
+224,22300,22400,100,100,0,complete,0.887,0.66,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_22300_22399\metrics_res.json
+225,22400,22500,100,100,0,complete,0.895,0.65,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_22400_22499\metrics_res.json
+226,22500,22600,100,100,0,complete,0.911,0.64,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_22500_22599\metrics_res.json
+227,22600,22700,100,100,0,complete,0.911,0.65,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_22600_22699\metrics_res.json
+228,22700,22800,100,100,0,complete,0.926,0.7,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_22700_22799\metrics_res.json
+229,22800,22900,100,100,0,complete,0.905,0.65,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_22800_22899\metrics_res.json
+230,22900,23000,100,100,0,complete,0.921,0.67,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_22900_22999\metrics_res.json
+231,23000,23100,100,100,0,complete,0.887,0.61,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_23000_23099\metrics_res.json
+232,23100,23200,100,100,0,complete,0.901,0.66,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_23100_23199\metrics_res.json
+233,23200,23300,100,99,1,complete,0.905,0.646,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_23200_23299\metrics_res.json
+234,23300,23400,100,100,0,complete,0.906,0.64,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_23300_23399\metrics_res.json
+235,23400,23500,100,100,0,complete,0.891,0.58,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_23400_23499\metrics_res.json
+236,23500,23600,100,100,0,complete,0.88,0.65,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_23500_23599\metrics_res.json
+237,23600,23700,100,100,0,complete,0.904,0.58,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_23600_23699\metrics_res.json
+238,23700,23757,57,57,0,complete,0.965,0.772,./benchmark-data\runs\cdm_official_unimer_full\unimer_test_full_cdm_shard_23700_23756\metrics_res.json
diff --git a/benchmarks/mathcraft_ocr/run_mathwriting_official_cdm.ps1 b/benchmarks/mathcraft_ocr/run_mathwriting_official_cdm.ps1
index 461d37e6..edb9b770 100644
--- a/benchmarks/mathcraft_ocr/run_mathwriting_official_cdm.ps1
+++ b/benchmarks/mathcraft_ocr/run_mathwriting_official_cdm.ps1
@@ -1,10 +1,12 @@
param(
- [string]$RepoRoot = "E:\LaTexSnipper",
- [string]$DataRoot = "E:\MathCraftBenchData",
- [string]$CdmDir = "E:\MathCraftBenchData\sources\UniMERNet_official\cdm",
- [string]$Python = "D:\Python312\python.exe",
- [string]$InputPath = "E:\MathCraftBenchData\runs\mathwriting_test_gpu\cdm_input\mathwriting_test_full_cdm.json",
- [string]$OutputDir = "E:\MathCraftBenchData\runs\cdm_official_mathwriting_test",
+ [string]$RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).Path,
+ [Parameter(Mandatory = $true)]
+ [string]$DataRoot,
+ [string]$CdmDir = (Join-Path $DataRoot "sources\UniMERNet_official\cdm"),
+ [string]$Python = "python",
+ [string]$PathPrepend = "",
+ [string]$InputPath = (Join-Path $DataRoot "runs\mathwriting_test_gpu\cdm_input\mathwriting_test_full_cdm.json"),
+ [string]$OutputDir = (Join-Path $DataRoot "runs\cdm_official_mathwriting_test"),
[int]$ShardSize = 100,
[int]$Pools = 8,
[int]$StartOffset = 0,
@@ -17,7 +19,7 @@ $ErrorActionPreference = "Stop"
Set-StrictMode -Version Latest
$runner = Join-Path $RepoRoot "benchmarks\mathcraft_ocr\reports\run_official_cdm.py"
-$pathPrepend = "E:\MathCraftBenchData\tools\unix_shims;E:\MathCraftBenchData\tools\Ghostscript\gs10.07.1\bin;C:\Program Files\ImageMagick-7.1.2-Q16"
+$Python = (Get-Command $Python -CommandType Application -ErrorAction Stop).Source
foreach ($path in @($runner, $CdmDir, $Python, $InputPath)) {
if (-not (Test-Path -LiteralPath $path)) {
@@ -31,7 +33,6 @@ $argsList = @(
"--output-dir", $OutputDir,
"--cdm-dir", $CdmDir,
"--python", $Python,
- "--path-prepend", $pathPrepend,
"--shard-size", $ShardSize,
"--pools", $Pools,
"--start-offset", $StartOffset,
@@ -39,8 +40,14 @@ $argsList = @(
"--max-shards", $MaxShards
)
+if ($PathPrepend) {
+ $argsList += @("--path-prepend", $PathPrepend)
+}
+
if ($Force) {
$argsList += "--force"
}
& $Python @argsList
+
+exit $LASTEXITCODE
diff --git a/benchmarks/mathcraft_ocr/run_mathwriting_test.ps1 b/benchmarks/mathcraft_ocr/run_mathwriting_test.ps1
index c28681bd..56846d01 100644
--- a/benchmarks/mathcraft_ocr/run_mathwriting_test.ps1
+++ b/benchmarks/mathcraft_ocr/run_mathwriting_test.ps1
@@ -1,6 +1,8 @@
param(
- [string]$RepoRoot = "E:\LaTexSnipper",
- [string]$DataRoot = "E:\MathCraftBenchData",
+ [string]$RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).Path,
+ [string]$PythonPath = "python",
+ [Parameter(Mandatory = $true)]
+ [string]$DataRoot,
[ValidateSet("gpu")]
[string]$Provider = "gpu",
[int]$ShardSize = 100,
@@ -24,7 +26,7 @@ if ($Limit -lt 0) {
throw "Limit must be non-negative."
}
-$python = Join-Path $RepoRoot "tools\deps\python311\python.exe"
+$python = (Get-Command $PythonPath -CommandType Application -ErrorAction Stop).Source
$manifestScript = Join-Path $RepoRoot "benchmarks\mathcraft_ocr\datasets\create_mathwriting_manifest.py"
$runnerScript = Join-Path $RepoRoot "benchmarks\mathcraft_ocr\runners\run_mathcraft.py"
$combineScript = Join-Path $RepoRoot "benchmarks\mathcraft_ocr\reports\combine_jsonl.py"
diff --git a/benchmarks/mathcraft_ocr/run_official_cdm.ps1 b/benchmarks/mathcraft_ocr/run_official_cdm.ps1
index e04575ad..a9f7f706 100644
--- a/benchmarks/mathcraft_ocr/run_official_cdm.ps1
+++ b/benchmarks/mathcraft_ocr/run_official_cdm.ps1
@@ -1,10 +1,12 @@
param(
- [string]$RepoRoot = "E:\LaTexSnipper",
- [string]$DataRoot = "E:\MathCraftBenchData",
- [string]$CdmDir = "E:\MathCraftBenchData\sources\UniMERNet_official\cdm",
- [string]$Python = "D:\Python312\python.exe",
- [string]$InputPath = "E:\MathCraftBenchData\runs\unimer_test_gpu\cdm_input\unimer_test_full_cdm.json",
- [string]$OutputDir = "E:\MathCraftBenchData\runs\cdm_official_unimer_full",
+ [string]$RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).Path,
+ [Parameter(Mandatory = $true)]
+ [string]$DataRoot,
+ [string]$CdmDir = (Join-Path $DataRoot "sources\UniMERNet_official\cdm"),
+ [string]$Python = "python",
+ [string]$PathPrepend = "",
+ [string]$InputPath = (Join-Path $DataRoot "runs\unimer_test_gpu\cdm_input\unimer_test_full_cdm.json"),
+ [string]$OutputDir = (Join-Path $DataRoot "runs\cdm_official_unimer_full"),
[int]$ShardSize = 100,
[int]$Pools = 8,
[int]$StartOffset = 0,
@@ -21,7 +23,7 @@ if ($Pools -gt 8) {
}
$runner = Join-Path $RepoRoot "benchmarks\mathcraft_ocr\reports\run_official_cdm.py"
-$pathPrepend = "E:\MathCraftBenchData\tools\unix_shims;E:\MathCraftBenchData\tools\Ghostscript\gs10.07.1\bin;C:\Program Files\ImageMagick-7.1.2-Q16"
+$Python = (Get-Command $Python -CommandType Application -ErrorAction Stop).Source
foreach ($path in @($runner, $CdmDir, $Python, $InputPath)) {
if (-not (Test-Path -LiteralPath $path)) {
@@ -35,7 +37,6 @@ $argsList = @(
"--output-dir", $OutputDir,
"--cdm-dir", $CdmDir,
"--python", $Python,
- "--path-prepend", $pathPrepend,
"--shard-size", $ShardSize,
"--pools", $Pools,
"--start-offset", $StartOffset,
@@ -43,8 +44,14 @@ $argsList = @(
"--max-shards", $MaxShards
)
+if ($PathPrepend) {
+ $argsList += @("--path-prepend", $PathPrepend)
+}
+
if ($Force) {
$argsList += "--force"
}
& $Python @argsList
+
+exit $LASTEXITCODE
diff --git a/benchmarks/mathcraft_ocr/run_openstax_pages.ps1 b/benchmarks/mathcraft_ocr/run_openstax_pages.ps1
index 93191d60..d6bbfea6 100644
--- a/benchmarks/mathcraft_ocr/run_openstax_pages.ps1
+++ b/benchmarks/mathcraft_ocr/run_openstax_pages.ps1
@@ -1,6 +1,8 @@
param(
- [string]$RepoRoot = "E:\LaTexSnipper",
- [string]$DataRoot = "E:\MathCraftBenchData",
+ [string]$RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).Path,
+ [string]$PythonPath = "python",
+ [Parameter(Mandatory = $true)]
+ [string]$DataRoot,
[ValidateSet("gpu", "cpu", "auto")]
[string]$Provider = "gpu",
[string]$Pages = "calculus_v1:40-89+120-169,college_algebra:60-109+180-229",
@@ -29,7 +31,7 @@ if ($Dpi -le 0) {
throw "Dpi must be positive."
}
-$python = Join-Path $RepoRoot "tools\deps\python311\python.exe"
+$python = (Get-Command $PythonPath -CommandType Application -ErrorAction Stop).Source
$manifestScript = Join-Path $RepoRoot "benchmarks\mathcraft_ocr\datasets\create_openstax_manifest.py"
$runnerScript = Join-Path $RepoRoot "benchmarks\mathcraft_ocr\runners\run_mathcraft.py"
$combineScript = Join-Path $RepoRoot "benchmarks\mathcraft_ocr\reports\combine_jsonl.py"
diff --git a/benchmarks/mathcraft_ocr/run_unimer_test.ps1 b/benchmarks/mathcraft_ocr/run_unimer_test.ps1
index 2e95fb9f..4868e966 100644
--- a/benchmarks/mathcraft_ocr/run_unimer_test.ps1
+++ b/benchmarks/mathcraft_ocr/run_unimer_test.ps1
@@ -1,6 +1,8 @@
param(
- [string]$RepoRoot = "E:\LaTexSnipper",
- [string]$DataRoot = "E:\MathCraftBenchData",
+ [string]$RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).Path,
+ [string]$PythonPath = "python",
+ [Parameter(Mandatory = $true)]
+ [string]$DataRoot,
[ValidateSet("gpu", "cpu", "auto")]
[string]$Provider = "gpu",
[int]$ShardSize = 250,
@@ -22,7 +24,7 @@ if ($Limit -lt 0) {
throw "Limit must be non-negative."
}
-$python = Join-Path $RepoRoot "tools\deps\python311\python.exe"
+$python = (Get-Command $PythonPath -CommandType Application -ErrorAction Stop).Source
$manifestScript = Join-Path $RepoRoot "benchmarks\mathcraft_ocr\datasets\create_unimer_manifest.py"
$runnerScript = Join-Path $RepoRoot "benchmarks\mathcraft_ocr\runners\run_mathcraft.py"
$combineScript = Join-Path $RepoRoot "benchmarks\mathcraft_ocr\reports\combine_jsonl.py"
diff --git a/benchmarks/mathcraft_ocr/visualization/make_openstax_block_gallery.py b/benchmarks/mathcraft_ocr/visualization/make_openstax_block_gallery.py
index e2f84d5d..bb49d8da 100644
--- a/benchmarks/mathcraft_ocr/visualization/make_openstax_block_gallery.py
+++ b/benchmarks/mathcraft_ocr/visualization/make_openstax_block_gallery.py
@@ -312,12 +312,14 @@ def is_formula_block(block: dict[str, Any]) -> bool:
def load_font(size: int) -> ImageFont.FreeTypeFont | ImageFont.ImageFont:
candidates = [
- Path("C:/Windows/Fonts/arial.ttf"),
- Path("C:/Windows/Fonts/calibri.ttf"),
+ "arial.ttf",
+ "calibri.ttf",
]
- for path in candidates:
- if path.exists():
- return ImageFont.truetype(str(path), size=size)
+ for font in candidates:
+ try:
+ return ImageFont.truetype(font, size=size)
+ except OSError:
+ continue
return ImageFont.load_default()
diff --git a/docs/client_onboarding_plan.md b/docs/client_onboarding_plan.md
new file mode 100644
index 00000000..bd4a9ced
--- /dev/null
+++ b/docs/client_onboarding_plan.md
@@ -0,0 +1,72 @@
+# 客户端首次使用引导方案
+
+日期:2026-09-23
+状态:方案已确认,待实现。本文描述目标行为,不代表功能已经上线。
+适用范围:客户端主窗口;不涉及 Office 插件编辑器。
+
+## 1. 目标与入口
+
+首次进入主窗口后,通过轻量高亮引导帮助用户认识截图识别、结果、导出、快捷键和模型准备状态。复用现有 PyQt6 / QFluentWidgets 界面、配置与模型生命周期,不另建初始化流程。
+
+主窗口“截图识别”右侧新增紧凑的“ⓘ 快速入门”入口。截图识别保持主操作视觉层级并占据剩余宽度,引导按钮按内容确定宽度,不将原按钮机械地平分。空间不足时显示图标,保留提示文字和可访问名称。点击该入口始终可以重新查看引导。
+
+欢迎和结束使用居中卡片,中间步骤通过柔和遮罩、高亮区域和说明卡片指向真实控件。步骤指示、上一页、下一页、跳过及关闭行为保持一致;Esc 可以退出。不通过启动全屏网页或独立安装向导替代主窗口交互。
+
+## 2. 六步内容
+
+| 步骤 | 标题与说明 | 操作与定位 |
+| --- | --- | --- |
+| 1 | 欢迎使用 LaTeXSnipper / 您的快速上手向导。 | 居中欢迎卡片;开始引导、跳过 |
+| 2 | 从这里开始 / 识别、预览,再导出您的公式。 | 在同一步中突出截图识别、实时渲染预览区和导出入口,不要求用户必须识别成功才能继续 |
+| 3 | 让操作更顺手 / 设置您习惯的截图快捷键。 | 显示当前实际快捷键;修改快捷键、下一步;打开现有配置界面,保存或取消后返回本步 |
+| 4 | 关于模型准备 / 本地模型会在后台准备,首次使用可能需要下载。您可以在日志中查看详情。 | 固定说明与“查看日志”入口;复用现有忙碌状态显示不确定态 ProgressRing,不阻塞继续 |
+| 5 | 一起让它变得更好 / 如果 LaTeXSnipper 对您有帮助,欢迎支持项目。 | Star、问题反馈、社区 / 支持项目;全部可选,可直接下一步 |
+| 6 | 开始使用吧 / 欢迎,让公式处理更简单。 | 固定“开始使用”按钮,关闭引导并回到主窗口 |
+
+标题与说明接入现有国际化机制。文案保持简洁;不使用倒计时、强制赞助、强制打开网页或强制配置快捷键。
+
+## 3. 模型准备说明
+
+第四步统一使用说明性文案,介绍本地模型后台准备和日志入口。只复用主窗口已有的忙碌状态控制不确定态 ProgressRing,不新增下载进度、成功、失败、外部模型等引导专用状态分支,也不提供引导专用重试逻辑。具体运行状态、错误和配置继续由现有主窗口、日志及设置界面负责。
+
+引导不重复启动下载或预热,不等待模型就绪,也不因关闭而取消后台任务。最后一步固定为“开始使用吧”和“开始使用”按钮,不订阅模型状态,不切换标题或操作。所有模型状态下均使用同一套六步流程。
+
+## 4. 首次显示、退出与重新查看
+
+- 主窗口完成首次布局且可交互后再显示;避免遮挡启动错误或依赖配置对话框。同一时刻只存在一个引导实例。
+- 自动引导完成或确认退出时,统一保存 `onboarding_auto_show: false`,不再自动弹出。跳过、关闭或按 Esc 时显示退出确认卡片;“继续引导”返回当前步骤,“退出引导”确认退出。确认卡片中再次按 Esc 或点击关闭则返回当前步骤,不修改偏好。
+- 需要重看时,点击主窗口“快速入门”。手动重看不修改自动显示偏好。
+- 直接关闭应用不修改偏好。再次自动显示时从欢迎页开始,不持久化步骤编号。
+- 配置只保存 `onboarding_auto_show` 一个布尔值,默认开启,普通版本升级不重置。
+
+## 5. 焦点与辅助操作
+
+打开运行日志、快捷键设置或其他配置界面时,暂停遮罩及步骤输入,让现有窗口正常交互;返回主窗口后恢复相应步骤。引导中不启动实际截图识别;用户结束引导后通过主窗口操作,不建立截图完成后恢复引导的回调。
+
+步骤切换保持键盘焦点可见,支持 Tab 操作,窗口缩放或移动后重新计算高亮位置。目标控件暂不可见时采用可读的说明卡片或定位到当前有效入口,不出现悬空高亮。重复点击引导入口只聚焦已有引导,不叠加窗口。
+
+## 6. 支持项目入口
+
+- Star 打开项目主页,由用户自行操作。
+- 问题反馈打开项目 Issues。
+- “社区 / 支持项目”合并为一个入口,按当前界面语言打开中文 README 的“支持本项目”区域或英文 README 的“Support the Project”区域;不自动加载远程二维码,不把三个大二维码同时放入引导。
+- 链接复用现有项目地址定义;只有明确点击才打开外部浏览器。无法打开时显示简洁提示并允许继续。
+- 后续若需要内嵌二维码,再单独确定资源来源与更新策略,不作为本阶段前置条件。
+
+## 7. 实现边界
+
+使用一个引导控制器管理步骤、显示来源、暂停恢复和退出;步骤定义描述文案、目标控件和动作;共享高亮 / 卡片组件只负责显示。仅复用现有忙碌状态和设置入口,不复制模型状态机,不新增进度协议、轮询或结束页回调。
+
+配置中只保留一个自动显示偏好。无需旧引导迁移、新版本强制重播、多个完成标志或重复状态存储。不要将本机依赖路径写入引导、构建配置或共享开发文档。
+
+## 8. 分阶段实施与验收
+
+1. **引导基础与主窗口入口:** 实现紧凑入口、六步框架、定位遮罩、键盘交互、首次显示、完成 / 退出偏好及手动重播。验证窗口缩放、重复打开、完成后重启和中途退出及直接关闭应用。
+2. **业务状态与操作接入:** 接通实际快捷键、日志、现有忙碌指示和支持链接。验证打开设置后返回,以及模型运行状态不改变引导流程、引导关闭不取消后台任务。
+3. **视觉与交互验收:** 检查明暗主题、中文 / 英文、不同 DPI 和窗口尺寸、焦点恢复;确认没有遮罩残留或重复预热,结束页文案与按钮始终一致。更新当前用户手册后再构建发布。
+
+各阶段独立检查差异并验证当前行为;不添加只证明旧文件或旧接口不存在的墓碑测试。当前已实现六步引导、入口、偏好、快捷键与日志返回、支持链接和忙碌指示。行为测试覆盖完成、退出、重播、弹窗返回、焦点、缩放与固定结束页;真实主窗口及不同 DPI 的桌面验收仍待完成。
+
+## 9. 后续工作接续
+
+在客户端 worktree 中先检查分支和工作树,继续第 8 节第 3 项的真实桌面验收。入口参考 `src/ui/main_window_setup.py` 中的 `capture_button`;启动时机参考 `src/ui/main_window.py`;状态参考 `src/ui/model_runtime_controller.py`;配置继续使用现有配置管理器。
diff --git a/docs/faq.md b/docs/faq.md
index 9c3577c9..88f54d54 100644
--- a/docs/faq.md
+++ b/docs/faq.md
@@ -106,7 +106,7 @@ Pandoc does not follow the active dependency root. It is deployed once under the
app-managed tools directory and remains available after the user switches Python
dependency roots.
-Linux/macOS release packages do not bundle build-machine environments from `tools/deps/`.
+Linux/macOS release packages do not bundle build-machine virtual environments.
## When does Dependency Management initialize pip?
@@ -173,7 +173,7 @@ Wayland restricts application-level screen capture. LaTeXSnipper uses Qt capture
Pandoc is only needed for the optional desktop export formats: Word `.docx`, ODT `.odt`, PowerPoint `.pptx`, EPUB `.epub`, PDF `.pdf`, standalone HTML `.html`, Typst `.typ`, and plain text `.txt`. PDF export also requires a LaTeX PDF engine such as XeLaTeX, LuaLaTeX, or pdfLaTeX. Core recognition, editing, preview, handwriting, and built-in LaTeX/Markdown/MathML/HTML/SVG exports work without Pandoc.
-Dependency Management manages the optional `PANDOC` layer. Manually downloaded or generated Pandoc binaries should not live under `src/`; local developer/build tools belong under `tools/deps/` or the app-managed dependency directory.
+Dependency Management manages the optional `PANDOC` layer. Manually downloaded or generated Pandoc binaries should not live under `src/`; developer tools may be installed in a location of your choice; app-managed tools use the application data directory.
## How does PDF recognition work?
@@ -208,4 +208,4 @@ Issues without logs are usually not actionable.
## Which Python environment should contributors use?
-Use `tools/deps/python311` for local development, checks, packaging helpers, and IDE integration. The repository-root `python311/` is the Windows template runtime and must not be polluted with development packages or used for ruff, pyright, pytest, or builds.
+Use a supported Python environment of your choice (venv, Conda, uv, or another manager). Activate it for development and checks, and select the same interpreter in your IDE. No repository-specific environment path is required. Client installers are built by `.github/workflows/release.yml`; Windows runtime preparation happens in the runner temporary directory, independently of developer environments.
diff --git a/docs/latexsnipper-3.0.0.png b/docs/latexsnipper.png
similarity index 100%
rename from docs/latexsnipper-3.0.0.png
rename to docs/latexsnipper.png
diff --git a/docs/mathcraft_ocr_literature_data_inventory.md b/docs/mathcraft_ocr_literature_data_inventory.md
index 44f6c194..9cc6e9d5 100644
--- a/docs/mathcraft_ocr_literature_data_inventory.md
+++ b/docs/mathcraft_ocr_literature_data_inventory.md
@@ -6,19 +6,19 @@ This inventory records the paper evidence that is already reproducible, the
public datasets still worth adding, and the claims each source can support. The
benchmark repository should contain scripts, schemas, reports, and compact
tables. Downloaded PDFs, rendered pages, large JSONL files, and generated figure
-assets stay under `E:\MathCraftBenchData`.
+assets stay under `./benchmark-data`.
## Current Evidence Status
| Evidence line | Status | Local artifact | Paper use |
| --- | --- | --- | --- |
-| UniMER-Test formula OCR | Formal run complete | `E:\MathCraftBenchData\runs\unimer_test_gpu\unimer_test_gpu_full.jsonl` | Main formula-recognition benchmark |
-| UniMER-Test render consistency | Formal auxiliary complete | `E:\MathCraftBenchData\runs\unimer_test_gpu\render_exact_underestimated` | Show exact-match underestimation cases |
-| OpenStax mixed pages | Formal run complete | `E:\MathCraftBenchData\runs\openstax_mixed_gpu_144dpi` | Main mixed-document evidence |
-| OpenStax block visualizations | Regenerable figure assets complete | `E:\MathCraftBenchData\runs\openstax_mixed_gpu_144dpi\figures\block_gallery` | Qualitative structure-recovery figures |
-| MathWriting | Formal run complete | `E:\MathCraftBenchData\runs\mathwriting_test_gpu` | Independent handwritten benchmark and exact-match stress evidence |
+| UniMER-Test formula OCR | Formal run complete | `./benchmark-data\runs\unimer_test_gpu\unimer_test_gpu_full.jsonl` | Main formula-recognition benchmark |
+| UniMER-Test render consistency | Formal auxiliary complete | `./benchmark-data\runs\unimer_test_gpu\render_exact_underestimated` | Show exact-match underestimation cases |
+| OpenStax mixed pages | Formal run complete | `./benchmark-data\runs\openstax_mixed_gpu_144dpi` | Main mixed-document evidence |
+| OpenStax block visualizations | Regenerable figure assets complete | `./benchmark-data\runs\openstax_mixed_gpu_144dpi\figures\block_gallery` | Qualitative structure-recovery figures |
+| MathWriting | Formal run complete | `./benchmark-data\runs\mathwriting_test_gpu` | Independent handwritten benchmark and exact-match stress evidence |
| CROHME | Candidate only | Not downloaded as formal benchmark | Standard HME comparison if data access is cleared |
-| CDM | Official full run complete | `E:\MathCraftBenchData\runs\cdm_official_unimer_full` | Full UniMER-Test render-aware evidence: mean CDM 0.929, ExpRate@CDM 0.648 |
+| CDM | Official full run complete | `./benchmark-data\runs\cdm_official_unimer_full` | Full UniMER-Test render-aware evidence: mean CDM 0.929, ExpRate@CDM 0.648 |
## Formal Public Benchmarks
@@ -30,7 +30,7 @@ Source: [UniMERNet paper](https://arxiv.org/abs/2404.15254),
Local manifest:
```text
-E:\MathCraftBenchData\manifests\unimer_test_full.jsonl
+./benchmark-data\manifests\unimer_test_full.jsonl
```
Scale:
@@ -46,7 +46,7 @@ Scale:
Current MathCraft result:
```text
-E:\LaTexSnipper\benchmarks\mathcraft_ocr\results\unimer_test_gpu
+.\benchmarks\mathcraft_ocr\results\unimer_test_gpu
```
Use in paper:
@@ -77,13 +77,13 @@ Local protocol:
Local manifest:
```text
-E:\MathCraftBenchData\manifests\mathwriting_test.jsonl
+./benchmark-data\manifests\mathwriting_test.jsonl
```
Run entry:
```powershell
-powershell -ExecutionPolicy Bypass -File E:\LaTexSnipper\benchmarks\mathcraft_ocr\run_mathwriting_test.ps1
+powershell -ExecutionPolicy Bypass -File .\benchmarks\mathcraft_ocr\run_mathwriting_test.ps1
```
Current MathCraft result:
@@ -135,7 +135,7 @@ Source pages are rendered from public OpenStax PDFs:
Local formal run:
```text
-E:\MathCraftBenchData\runs\openstax_mixed_gpu_144dpi
+./benchmark-data\runs\openstax_mixed_gpu_144dpi
```
Default protocol:
@@ -155,16 +155,16 @@ Default protocol:
Source-control evidence:
```text
-E:\LaTexSnipper\benchmarks\mathcraft_ocr\results\openstax_mixed_gpu_144dpi
+.\benchmarks\mathcraft_ocr\results\openstax_mixed_gpu_144dpi
```
Figure-generation entry:
```powershell
-E:\LaTexSnipper\tools\deps\python311\python.exe `
- E:\LaTexSnipper\benchmarks\mathcraft_ocr\visualization\make_openstax_block_gallery.py `
- --results E:\MathCraftBenchData\runs\openstax_mixed_gpu_144dpi\openstax_mixed_gpu_144dpi_full.jsonl `
- --output-dir E:\MathCraftBenchData\runs\openstax_mixed_gpu_144dpi\figures\block_gallery `
+python `
+ .\benchmarks\mathcraft_ocr\visualization\make_openstax_block_gallery.py `
+ --results ./benchmark-data\runs\openstax_mixed_gpu_144dpi\openstax_mixed_gpu_144dpi_full.jsonl `
+ --output-dir ./benchmark-data\runs\openstax_mixed_gpu_144dpi\figures\block_gallery `
--limit 6
```
@@ -214,7 +214,7 @@ Status: complete.
Implementation:
- Uses the official UniMERNet CDM implementation from the local clone under
- `E:\MathCraftBenchData\sources\UniMERNet_official\cdm`.
+ `./benchmark-data\sources\UniMERNet_official\cdm`.
- Converts MathCraft outputs into the official `img_id` / `gt` / `pred` batch
format before running the official CDM scorer.
- Keeps the current render-consistency fallback under a different name; it is
@@ -263,9 +263,9 @@ models are rerun under the same local protocol.
| Source | Repository policy |
| --- | --- |
-| UniMER-Test | Keep generated compact reports in repo; keep full data/results under `E:\MathCraftBenchData` |
+| UniMER-Test | Keep generated compact reports in repo; keep full data/results under `./benchmark-data` |
| OpenStax | Keep scripts and compact metrics in repo; keep rendered page images and overlays local unless publication license review is complete |
-| MathWriting | Keep scripts and compact metrics in repo; keep parquet, images, full results, and CDM render artifacts under `E:\MathCraftBenchData` |
+| MathWriting | Keep scripts and compact metrics in repo; keep parquet, images, full results, and CDM render artifacts under `./benchmark-data` |
| CROHME/HME100K | Do not add to formal benchmark until license/access is recorded |
| Project `test_pdf` assets | Internal regression only unless the original PDF source is public and attributable |
diff --git a/office_plugin/tests/LaTeXSnipper.OfficePlugin.WordParsingE2E/README.md b/office_plugin/tests/LaTeXSnipper.OfficePlugin.WordParsingE2E/README.md
index d5fb53b0..f4353716 100644
--- a/office_plugin/tests/LaTeXSnipper.OfficePlugin.WordParsingE2E/README.md
+++ b/office_plugin/tests/LaTeXSnipper.OfficePlugin.WordParsingE2E/README.md
@@ -19,7 +19,7 @@
运行前必须关闭所有 Word 窗口,且测试完成前不要再次启动 Word,以确保测试实例与日常文档隔离:
```powershell
-E:\LaTexSnipper\office_plugin\tools\Test-WordFormulaParsingE2E.ps1
+.\office_plugin\tools\Test-WordFormulaParsingE2E.ps1
```
通过后,测试项目的 `artifacts` 目录会生成 OMML/OLE 两份 DOCX 和 Word 导出的同名 PDF。该目录已被 Git 忽略,只用于本地结构与视觉复核;可通过脚本的 `-OutputDirectory` 参数改写输出位置。
diff --git a/office_plugin/tools/Build-NativeOleHandler.ps1 b/office_plugin/tools/Build-NativeOleHandler.ps1
index 60ee92b5..b67e9477 100644
--- a/office_plugin/tools/Build-NativeOleHandler.ps1
+++ b/office_plugin/tools/Build-NativeOleHandler.ps1
@@ -37,7 +37,6 @@ function Find-NativeBuildEnvironment {
}
foreach ($candidate in @(
- "D:\Microsoft Visual Studio\2022\Community",
"${env:ProgramFiles}\Microsoft Visual Studio\2022\Community",
"${env:ProgramFiles(x86)}\Microsoft Visual Studio\2022\Community"
)) {
diff --git a/office_plugin/tools/Build-VstoAddIns.ps1 b/office_plugin/tools/Build-VstoAddIns.ps1
index 5df1031a..a4ba8f89 100644
--- a/office_plugin/tools/Build-VstoAddIns.ps1
+++ b/office_plugin/tools/Build-VstoAddIns.ps1
@@ -47,7 +47,6 @@ function Find-VstoBuildEnvironment {
}
foreach ($candidate in @(
- "D:\Microsoft Visual Studio\2022\Community",
"${env:ProgramFiles}\Microsoft Visual Studio\2022\Community",
"${env:ProgramFiles(x86)}\Microsoft Visual Studio\2022\Community"
)) {
diff --git a/pyproject.toml b/pyproject.toml
index abd25ae2..b43cc681 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -89,14 +89,10 @@ mathcraft_ocr = ["manifests/*.json"]
[tool.pytest.ini_options]
pythonpath = ["src"]
testpaths = ["test"]
-addopts = ["--ignore=python311", "--ignore=tools/deps", "--ignore=.venv", "--ignore=build", "--ignore=dist"]
+addopts = ["--ignore=.venv", "--ignore=build", "--ignore=dist"]
[tool.vulture]
paths = ["src"]
-exclude = [
- "build/",
- "python311/",
- "tools/deps/python311/"
-]
+exclude = ["build/", "dist/", ".venv/"]
min_confidence = 70
sort_by_size = true
diff --git a/pyrightconfig.json b/pyrightconfig.json
index d8550d6d..15c00e8d 100644
--- a/pyrightconfig.json
+++ b/pyrightconfig.json
@@ -7,14 +7,13 @@
],
"extraPaths": [
".",
- "src",
- "tools/deps/python311/Lib/site-packages"
+ "src"
],
"exclude": [
"**/__pycache__",
"build",
- "python311",
- "tools/deps/python311"
+ "dist",
+ ".venv"
],
"reportUnusedImport": "warning",
"reportUnusedVariable": "warning",
@@ -29,8 +28,7 @@
{
"root": "src",
"extraPaths": [
- ".",
- "tools/deps/python311/Lib/site-packages"
+ "."
]
}
]
diff --git a/readme.md b/readme.md
index be5f7849..945177ef 100644
--- a/readme.md
+++ b/readme.md
@@ -14,7 +14,7 @@
English · [简体中文](README.zh-CN.md)
-
+
diff --git a/scripts/build_deb.sh b/scripts/build_deb.sh
index be0b58c3..bc9eda70 100755
--- a/scripts/build_deb.sh
+++ b/scripts/build_deb.sh
@@ -31,8 +31,8 @@ command -v python3 >/dev/null 2>&1 || die "python3 is required"
[[ -f "$SPEC_FILE" ]] || die "missing spec file: $SPEC_FILE"
clear_debian_package_outputs "$DEB_PATH" "$DEB_OUTPUT_DIR/SHA256SUMS-linux.txt"
-log_step "1/5" "Preparing isolated Python runtime"
-BUILD_PYTHON="$(prepare_python_runtime "$PROJECT_ROOT")"
+log_step "1/5" "Using the runner Python environment"
+BUILD_PYTHON="$(resolve_build_python)"
install_python_requirements \
"$BUILD_PYTHON" \
"$PROJECT_ROOT/requirements-linux.txt" \
@@ -50,7 +50,7 @@ cd "$PROJECT_ROOT"
[[ -d "$DIST_DIR" ]] || die "PyInstaller output was not created: $DIST_DIR"
PYINSTALLER_BIN="$DIST_DIR/LaTeXSnipper"
-PYINSTALLER_ARCHIVE_VIEWER="$(dirname "$BUILD_PYTHON")/pyi-archive_viewer"
+PYINSTALLER_ARCHIVE_VIEWER="$("$BUILD_PYTHON" -c 'import sysconfig; print(sysconfig.get_path("scripts"))')/pyi-archive_viewer"
HOTKEY_ARCHIVE_VERIFIER="$PROJECT_ROOT/scripts/verify_linux_hotkey_archive.py"
[[ -f "$PYINSTALLER_BIN" ]] || die "PyInstaller executable was not created: $PYINSTALLER_BIN"
[[ -x "$PYINSTALLER_ARCHIVE_VIEWER" ]] || die "PyInstaller archive viewer was not found: $PYINSTALLER_ARCHIVE_VIEWER"
diff --git a/scripts/build_github_release_installer.ps1 b/scripts/build_github_release_installer.ps1
index 48bca9f3..4bb791d3 100644
--- a/scripts/build_github_release_installer.ps1
+++ b/scripts/build_github_release_installer.ps1
@@ -1,9 +1,8 @@
param(
- [switch]$Sign,
- [string]$CertificateThumbprint = "",
- [string]$TimestampUrl = "http://timestamp.digicert.com",
- [string]$InnoCompiler = "",
- [string]$PythonPath = ""
+ [Parameter(Mandatory = $true)]
+ [string]$BundledPythonPath,
+ [string]$PythonPath = "python",
+ [string]$InnoCompiler = ""
)
$ErrorActionPreference = "Stop"
@@ -34,101 +33,6 @@ function Find-Tool {
throw "Could not find $ToolName."
}
-function Find-WindowsSdkTool {
- param([string]$ToolName)
-
- $roots = @()
- if ($env:ProgramFiles) {
- $roots += (Join-Path $env:ProgramFiles "Windows Kits\10\bin")
- }
- $programFilesX86 = ${env:ProgramFiles(x86)}
- if ($programFilesX86) {
- $roots += (Join-Path $programFilesX86 "Windows Kits\10\bin")
- }
-
- foreach ($root in $roots) {
- if (-not (Test-Path $root)) {
- continue
- }
- $candidate = Get-ChildItem -Path $root -Directory -ErrorAction SilentlyContinue |
- Sort-Object Name -Descending |
- ForEach-Object { Join-Path $_.FullName "x64\$ToolName" } |
- Where-Object { Test-Path $_ } |
- Select-Object -First 1
- if ($candidate) {
- return $candidate
- }
- }
-
- $command = Get-Command $ToolName -ErrorAction SilentlyContinue
- if ($command) {
- return $command.Source
- }
-
- throw "Could not find $ToolName. Install the Windows SDK or put it on PATH."
-}
-
-function Resolve-BuildPython {
- param(
- [string]$Root,
- [string]$RequestedPython
- )
-
- $candidates = @()
- if (-not [string]::IsNullOrWhiteSpace($RequestedPython)) {
- $candidates += $RequestedPython
- }
-
- $candidates += (Join-Path $Root "tools\deps\python311\python.exe")
-
- foreach ($candidate in $candidates) {
- if ([string]::IsNullOrWhiteSpace($candidate)) {
- continue
- }
- if (Test-Path $candidate) {
- return (Resolve-Path $candidate).Path
- }
- $command = Get-Command $candidate -ErrorAction SilentlyContinue
- if ($command) {
- return $command.Source
- }
- }
-
- $pythonCommand = Get-Command "python" -ErrorAction SilentlyContinue
- if ($pythonCommand) {
- return $pythonCommand.Source
- }
-
- throw "Could not find build Python. Pass -PythonPath or install Python on PATH."
-}
-
-function Invoke-CodeSign {
- param(
- [string]$Signtool,
- [string]$Path,
- [string]$Thumbprint,
- [string]$TimestampUrl
- )
-
- if (-not (Test-Path $Path)) {
- throw "Cannot sign missing file: $Path"
- }
-
- $args = @("sign", "/fd", "SHA256", "/td", "SHA256", "/tr", $TimestampUrl)
- if ([string]::IsNullOrWhiteSpace($Thumbprint)) {
- $args += "/a"
- }
- else {
- $args += @("/sha1", $Thumbprint)
- }
- $args += $Path
-
- & $Signtool @args
- if ($LASTEXITCODE -ne 0) {
- throw "signtool failed with exit code $LASTEXITCODE for $Path"
- }
-}
-
function Write-Sha256File {
param([string]$Path)
@@ -184,13 +88,7 @@ if "HTTPSHandler" not in handlers:
}
function Normalize-BundledPythonSeed {
- param([string]$Root)
-
- $seedRoot = Join-Path $Root "python311"
- if (-not (Test-Path $seedRoot)) {
- Write-Host "Bundled Python seed not found, skip normalization: $seedRoot"
- return
- }
+ param([string]$SeedRoot)
$pythonExe = Join-Path $seedRoot "python.exe"
if (-not (Test-Path $pythonExe)) {
@@ -377,37 +275,14 @@ function Remove-PythonCache {
Remove-Item -Force
}
-function Stage-BundledPythonSeed {
- param([string]$Root)
-
- $source = Join-Path $Root "python311"
- if (-not (Test-Path -LiteralPath $source)) {
- throw "Bundled Python template not found: $source"
- }
-
- $stagingBase = Join-Path $Root "build\github-release"
- New-Item -ItemType Directory -Path $stagingBase -Force | Out-Null
- $stagingBase = (Resolve-Path -LiteralPath $stagingBase).Path
- $stagedRoot = Join-Path $stagingBase "bundled-deps"
- if (Test-Path -LiteralPath $stagedRoot) {
- $resolvedStagedRoot = (Resolve-Path -LiteralPath $stagedRoot).Path
- $expectedPrefix = $stagingBase.TrimEnd('\') + '\'
- if (-not $resolvedStagedRoot.StartsWith($expectedPrefix, [System.StringComparison]::OrdinalIgnoreCase)) {
- throw "Refusing to replace bundled dependency stage outside build directory: $resolvedStagedRoot"
- }
- Remove-Item -LiteralPath $stagedRoot -Recurse -Force
- }
- New-Item -ItemType Directory -Path $stagedRoot -Force | Out-Null
- Copy-Item -LiteralPath $source -Destination (Join-Path $stagedRoot "python311") -Recurse -Force
-
- Write-Host "Bundled Python template staged: $stagedRoot"
- return $stagedRoot
-}
-
$root = Resolve-RepoRoot
-$python = Resolve-BuildPython -Root $root -RequestedPython $PythonPath
-$bundledDepsRoot = Stage-BundledPythonSeed -Root $root
-Normalize-BundledPythonSeed -Root $bundledDepsRoot
+$python = (Get-Command $PythonPath -CommandType Application -ErrorAction Stop).Source
+$bundledPython = (Resolve-Path -LiteralPath $BundledPythonPath).Path
+$runnerTemp = (Resolve-Path -LiteralPath $env:RUNNER_TEMP).Path.TrimEnd('\') + '\'
+if (-not $bundledPython.StartsWith($runnerTemp, [System.StringComparison]::OrdinalIgnoreCase)) {
+ throw "Bundled Python must be prepared inside RUNNER_TEMP: $bundledPython"
+}
+Normalize-BundledPythonSeed -SeedRoot $bundledPython
$isccCandidates = @()
if ($InnoCompiler) {
@@ -419,7 +294,6 @@ if (${env:ProgramFiles(x86)}) {
if ($env:ProgramFiles) {
$isccCandidates += (Join-Path $env:ProgramFiles "Inno Setup 6\ISCC.exe")
}
-$isccCandidates += "D:\Program Files (x86)\Inno Setup 6\ISCC.exe"
$iscc = Find-Tool -ToolName "ISCC.exe" -Candidates $isccCandidates
$buildName = "LaTeXSnipper"
@@ -438,10 +312,10 @@ if (-not (Test-Path $iss)) {
}
$oldBuildName = $env:LATEXSNIPPER_BUILD_NAME
-$oldBundledDepsDir = $env:LATEXSNIPPER_BUNDLED_DEPS_DIR
+$oldBundledPython = $env:LATEXSNIPPER_BUNDLED_PYTHON
try {
$env:LATEXSNIPPER_BUILD_NAME = $buildName
- $env:LATEXSNIPPER_BUNDLED_DEPS_DIR = $bundledDepsRoot
+ $env:LATEXSNIPPER_BUNDLED_PYTHON = $bundledPython
foreach ($path in @($distAppDir, $pyinstallerWorkDir)) {
if (Test-Path -LiteralPath $path) {
@@ -476,7 +350,7 @@ try {
}
finally {
$env:LATEXSNIPPER_BUILD_NAME = $oldBuildName
- $env:LATEXSNIPPER_BUNDLED_DEPS_DIR = $oldBundledDepsDir
+ $env:LATEXSNIPPER_BUNDLED_PYTHON = $oldBundledPython
}
$appExe = Join-Path $distAppDir "$buildName.exe"
@@ -484,26 +358,13 @@ if (-not (Test-Path $appExe)) {
throw "PyInstaller output exe not found: $appExe"
}
-$signtool = ""
-if ($Sign) {
- $signtool = Find-WindowsSdkTool -ToolName "signtool.exe"
- Invoke-CodeSign -Signtool $signtool -Path $appExe -Thumbprint $CertificateThumbprint -TimestampUrl $TimestampUrl
-}
-
-$oldRepoRoot = $env:LATEXSNIPPER_REPO_ROOT
-try {
- $env:LATEXSNIPPER_REPO_ROOT = $root
- if (Test-Path $installerOutputDir) {
- Get-ChildItem -LiteralPath $installerOutputDir -Filter "LaTeXSnipperSetup-*.exe" -File |
- Remove-Item -Force
- }
- & $iscc $iss
- if ($LASTEXITCODE -ne 0) {
- throw "Inno Setup failed with exit code $LASTEXITCODE"
- }
+if (Test-Path $installerOutputDir) {
+ Get-ChildItem -LiteralPath $installerOutputDir -Filter "LaTeXSnipperSetup-*.exe" -File |
+ Remove-Item -Force
}
-finally {
- $env:LATEXSNIPPER_REPO_ROOT = $oldRepoRoot
+& $iscc $iss
+if ($LASTEXITCODE -ne 0) {
+ throw "Inno Setup failed with exit code $LASTEXITCODE"
}
$installer = Get-ChildItem -LiteralPath $installerOutputDir -Filter "LaTeXSnipperSetup-*.exe" -File |
@@ -513,10 +374,6 @@ if (-not $installer -or -not (Test-Path -LiteralPath $installer.FullName)) {
throw "Installer output not found in: $installerOutputDir"
}
-if ($Sign) {
- Invoke-CodeSign -Signtool $signtool -Path $installer.FullName -Thumbprint $CertificateThumbprint -TimestampUrl $TimestampUrl
-}
-
$hash = Write-Sha256File -Path $installer.FullName
Write-Host ""
@@ -524,9 +381,4 @@ Write-Host "GitHub release installer created:"
Write-Host " $($installer.FullName)"
Write-Host "SHA256:"
Write-Host " $hash"
-if ($Sign) {
- Write-Host "Signing: completed"
-}
-else {
- Write-Host "Signing: skipped. Submit the installer to SignPath, or rerun with -Sign when a trusted code-signing certificate is available."
-}
+Write-Host "Signing is handled by the release workflow through SignPath."
diff --git a/scripts/build_macos.sh b/scripts/build_macos.sh
index 298402fa..708216ec 100755
--- a/scripts/build_macos.sh
+++ b/scripts/build_macos.sh
@@ -57,8 +57,8 @@ if [[ ! -f "$ICON_SOURCE" && ! -f "$PROJECT_ROOT/src/assets/icon.icns" ]]; then
echo "warning: no application icon source was found; the app will use the default icon"
fi
-log_step "1/6" "Preparing isolated Python runtime"
-BUILD_PYTHON="$(prepare_python_runtime "$PROJECT_ROOT")"
+log_step "1/6" "Using the runner Python environment"
+BUILD_PYTHON="$(resolve_build_python)"
install_python_requirements \
"$BUILD_PYTHON" \
"$PROJECT_ROOT/requirements-macos.txt" \
diff --git a/scripts/package_common.sh b/scripts/package_common.sh
index 7debde7a..9c58defe 100644
--- a/scripts/package_common.sh
+++ b/scripts/package_common.sh
@@ -27,78 +27,9 @@ print(version)
PY
}
-prepare_python_runtime() {
- local project_root="$1"
- local platform arch runtime_dir runtime_python create_python
- platform="$(uname -s | tr '[:upper:]' '[:lower:]')"
- arch="$(uname -m | tr '[:upper:]' '[:lower:]')"
- runtime_dir="$project_root/tools/deps/python311-$platform-$arch"
- runtime_python="$runtime_dir/bin/python3"
- create_python="$(find_python311_command)"
- [[ -n "$create_python" ]] || exit 1
-
- mkdir -p "$(dirname "$runtime_dir")"
-
- local rebuild=false
- if [[ ! -x "$runtime_python" ]]; then
- rebuild=true
- elif [[ -L "$runtime_python" ]]; then
- rebuild=true
- elif ! "$runtime_python" - <<'PY' >/dev/null 2>&1; then
-import sys
-
-raise SystemExit(0 if sys.version_info[:2] == (3, 11) else 1)
-PY
- rebuild=true
- elif ! "$runtime_python" -c "print('ok')" >/dev/null 2>&1; then
- rebuild=true
- fi
-
- if [[ "$rebuild" == "true" ]]; then
- rm -rf "$runtime_dir"
- "$create_python" -m venv --copies "$runtime_dir" || die "failed to create isolated Python 3.11 runtime at $runtime_dir"
- fi
-
- "$runtime_python" - <<'PY' >/dev/null 2>&1 || die "isolated build runtime must be Python 3.11: $runtime_python"
-import sys
-
-raise SystemExit(0 if sys.version_info[:2] == (3, 11) else 1)
-PY
-
- "$runtime_python" -m ensurepip --upgrade >/dev/null 2>&1 || true
- "$runtime_python" -m pip install --upgrade pip wheel setuptools >&2
- echo "$runtime_python"
-}
-
-find_python311_command() {
- local override="${PYTHON311:-}"
- if [[ -n "$override" ]]; then
- [[ -x "$override" ]] || die "PYTHON311 is not executable: $override"
- "$override" - <<'PY' >/dev/null 2>&1 || die "PYTHON311 must point to Python 3.11: $override"
-import sys
-
-raise SystemExit(0 if sys.version_info[:2] == (3, 11) else 1)
-PY
- echo "$override"
- return
- fi
-
- local candidate resolved
- for candidate in python3.11 python3; do
- if command -v "$candidate" >/dev/null 2>&1; then
- resolved="$(command -v "$candidate")"
- if "$resolved" - <<'PY' >/dev/null 2>&1; then
-import sys
-
-raise SystemExit(0 if sys.version_info[:2] == (3, 11) else 1)
-PY
- echo "$resolved"
- return
- fi
- fi
- done
-
- die "Python 3.11 is required for packaging; install python3.11 or set PYTHON311=/path/to/python3.11"
+# actions/setup-python supplies the build interpreter on PATH.
+resolve_build_python() {
+ command -v python3 || die "Python is required on the release runner"
}
install_python_requirements() {
diff --git a/src/application/app_runner.py b/src/application/app_runner.py
index 42372a28..ef4b5bb7 100644
--- a/src/application/app_runner.py
+++ b/src/application/app_runner.py
@@ -7,7 +7,7 @@
from PyQt6.QtCore import QTimer
from PyQt6.QtWidgets import QApplication
-from application.dependency_controller import ensure_deps
+from application.dependency_controller import dependencies_ready, ensure_deps
from localization.manager import translate as tr
from ui.runtime_log_controller import apply_runtime_log_window_preference
from ui.startup_splash import (
@@ -39,7 +39,7 @@ def _create_window(main_window_cls, splash):
win.show()
win.start_post_show_tasks()
QTimer.singleShot(
- 0, lambda: apply_runtime_log_window_preference(force=False, tee=True)
+ 0, lambda: apply_runtime_log_window_preference(force=False)
)
finish_startup_splash(splash, win)
print("[INFO] 应用界面已就绪")
@@ -74,7 +74,7 @@ def _run_development(app, main_window_cls) -> int:
)
deps_check_message = startup_status_message(tr("检查依赖..."))
update_startup_splash(splash, deps_check_message)
- deps_ready_cached = os.environ.get("LATEXSNIPPER_DEPS_OK") == "1"
+ deps_ready_cached = dependencies_ready()
needs_interactive_deps_ui = bool(
open_dependency_management or (not deps_ready_cached)
)
diff --git a/src/application/bootstrap.py b/src/application/bootstrap.py
index 4a8e2ea6..7a3e4f9d 100644
--- a/src/application/bootstrap.py
+++ b/src/application/bootstrap.py
@@ -1,9 +1,8 @@
from __future__ import annotations
import atexit
-import json
+import logging
import os
-import subprocess
import sys
from dataclasses import dataclass
from pathlib import Path
@@ -16,12 +15,10 @@
from preview.math_preview import configure_math_preview_runtime
from runtime.app_paths import resource_path
from runtime.native_runtime import configure_native_runtime_environment
-from runtime.dependency_runtime import DEPENDENCY_PYTHON_DIRNAME, dependency_venv_python
from application.python_runtime_resolver import (
APP_DIR,
_append_private_site_packages,
_clean_bad_env,
- _config_path,
_find_install_base_python,
_in_ide,
_initial_deps_dir,
@@ -29,7 +26,6 @@
_relaunch_with,
_same_exe,
_sanitize_sys_path,
- _win_subprocess_kwargs,
ensure_full_python_or_prompt,
resolve_install_base_dir,
)
@@ -37,7 +33,7 @@
ensure_single_instance,
release_single_instance_lock,
)
-from application.dependency_controller import deps_force_entered, ensure_deps
+from application.dependency_controller import ensure_deps
from ui.startup_splash import (
ensure_startup_splash,
hide_startup_splash_for_modal,
@@ -100,72 +96,6 @@ def _ensure_src_path() -> None:
sys.path.insert(0, src_dir)
-def _maybe_redirect_to_private_python(install_base_dir: Path) -> None:
- if not _is_packaged_mode():
- return
-
- py_exe_path = _find_install_base_python(install_base_dir)
- py_exe = (
- py_exe_path
- if py_exe_path is not None
- else dependency_venv_python(install_base_dir / DEPENDENCY_PYTHON_DIRNAME)
- )
-
- if not py_exe.exists():
- print(f"[WARN] 依赖目录 Python 不存在,继续使用内置运行时: {py_exe}")
- return
-
- if os.environ.get("LATEXSNIPPER_FORCE_PRIVATE_PY") != "1":
- print("[DEBUG] 使用内置运行时启动,依赖目录由子进程使用")
- return
-
- if os.environ.get("LATEXSNIPPER_INNER_PY") == "1":
- print("[DEBUG] 已在依赖目录 Python 中运行")
- return
-
- print(f"[DEBUG] 切换到依赖目录 Python: {py_exe}")
- env = os.environ.copy()
- env["LATEXSNIPPER_INNER_PY"] = "1"
-
- raw_pref = (
- (os.environ.get("LATEXSNIPPER_SHOW_RUNTIME_LOG", "") or "").strip().lower()
- )
- if raw_pref in ("1", "true", "yes", "on", "0", "false", "no", "off"):
- show_runtime_log = raw_pref in ("1", "true", "yes", "on")
- else:
- show_runtime_log = False
- try:
- cfg_path = _config_path()
- if cfg_path.exists():
- cfg_data = json.loads(cfg_path.read_text(encoding="utf-8"))
- raw = (
- cfg_data.get("show_runtime_log", False)
- if isinstance(cfg_data, dict)
- else False
- )
- if isinstance(raw, bool):
- show_runtime_log = raw
- elif isinstance(raw, (int, float)):
- show_runtime_log = bool(raw)
- elif isinstance(raw, str):
- show_runtime_log = raw.strip().lower() in ("1", "true", "yes", "on")
- except Exception:
- pass
- env["LATEXSNIPPER_SHOW_RUNTIME_LOG"] = "1" if show_runtime_log else "0"
-
- run_py = py_exe
- pyw = py_exe.parent / "pythonw.exe"
- if pyw.exists():
- run_py = pyw
- argv = [
- str(run_py),
- os.path.join(os.path.dirname(os.path.dirname(__file__)), "main.py"),
- *sys.argv[1:],
- ]
- subprocess.Popen(argv, env=env, **_win_subprocess_kwargs())
- raise SystemExit(0)
-
-
def _prepare_python_runtime(install_base_dir: Path) -> tuple[Path, str]:
base_dir = Path(install_base_dir)
_clean_bad_env()
@@ -187,7 +117,7 @@ def _prepare_python_runtime(install_base_dir: Path) -> tuple[Path, str]:
if not _same_exe(sys.executable, target_py):
_relaunch_with(target_py)
elif _in_ide():
- print("[DEBUG] IDE 中运行,保持当前解释器并使用私有依赖路径")
+ logging.getLogger(__name__).debug("IDE 中运行,保持当前解释器并使用私有依赖路径")
return base_dir, target_py
@@ -213,7 +143,7 @@ def _prepare_python_runtime_for_dependency_management(
os.environ.pop("PYTHONPATH", None)
os.environ.pop("MATHCRAFT_HOME", None)
- print("[DEBUG] 依赖管理模式:等待用户选择后准备 Python 环境")
+ logging.getLogger(__name__).debug("依赖管理模式:等待用户选择后准备 Python 环境")
return base_dir, target_py
@@ -229,7 +159,7 @@ def _bootstrap_dependencies(base_dir: Path, target_py: str) -> None:
os.environ.get("LATEXSNIPPER_OPEN_DEPENDENCY_MANAGEMENT", "") == "1"
)
if open_dependency_management:
- print("[DEBUG] 依赖管理模式:统一验证依赖")
+ logging.getLogger(__name__).debug("依赖管理模式:统一验证依赖")
return
ensure_startup_splash(tr("检查依赖..."))
@@ -242,12 +172,11 @@ def _bootstrap_dependencies(base_dir: Path, target_py: str) -> None:
before_show_ui=hide_startup_splash_for_modal,
after_force_enter=mark_startup_force_entered,
)
- if ok:
- os.environ["LATEXSNIPPER_DEPS_OK"] = "1"
- if deps_force_entered():
- mark_startup_force_entered()
+ if not ok:
+ raise SystemExit(0)
except Exception as e:
print(f"[WARN] 依赖管理启动失败: {e}")
+ raise SystemExit(1) from e
def bootstrap_application() -> BootstrapContext:
@@ -263,12 +192,12 @@ def bootstrap_application() -> BootstrapContext:
_ensure_src_path()
configure_default_webengine_profile()
- print(f"[DEBUG] 应用根目录: {APP_DIR}")
- print(f"[DEBUG] 打包模式: {_is_packaged_mode()}")
+ logging.getLogger(__name__).debug(f"应用根目录: {APP_DIR}")
+ logging.getLogger(__name__).debug(f"打包模式: {_is_packaged_mode()}")
deps_dir = _initial_deps_dir()
deps_dir.mkdir(parents=True, exist_ok=True)
- print(f"[DEBUG] 依赖目录: {deps_dir}")
+ logging.getLogger(__name__).debug(f"依赖目录: {deps_dir}")
install_base_dir = resolve_install_base_dir()
open_dependency_management = (
@@ -279,7 +208,6 @@ def bootstrap_application() -> BootstrapContext:
install_base_dir
)
else:
- _maybe_redirect_to_private_python(install_base_dir)
base_dir, target_py = _prepare_python_runtime(install_base_dir)
_bootstrap_dependencies(base_dir, target_py)
@@ -288,7 +216,7 @@ def bootstrap_application() -> BootstrapContext:
for var in ("PYTHONHOME", "PYTHONPATH", "MATHCRAFT_HOME"):
if var in os.environ:
- print(f"[DEBUG] 清除环境变量 {var}")
+ logging.getLogger(__name__).debug(f"清除环境变量 {var}")
os.environ.pop(var)
_BOOTSTRAP_CONTEXT = BootstrapContext(
diff --git a/src/application/dependency_controller.py b/src/application/dependency_controller.py
index a22b9e05..0b4c256a 100644
--- a/src/application/dependency_controller.py
+++ b/src/application/dependency_controller.py
@@ -2,8 +2,6 @@
from __future__ import annotations
-import os
-
from bootstrap.deps_context import was_last_ensure_deps_force_enter
from ui.startup_splash import (
hide_startup_splash_for_modal,
@@ -11,13 +9,21 @@
)
+_dependencies_ready = False
+
+
+def dependencies_ready() -> bool:
+ return _dependencies_ready
+
+
def deps_force_entered() -> bool:
return was_last_ensure_deps_force_enter()
def ensure_deps(*args, **kwargs):
+ global _dependencies_ready
from_settings = bool(kwargs.get("from_settings", False))
- if os.environ.get("LATEXSNIPPER_DEPS_OK") == "1" and not from_settings:
+ if dependencies_ready() and not from_settings:
return True
from bootstrap.deps_entry import ensure_deps as run_dependency_bootstrap
@@ -28,7 +34,7 @@ def ensure_deps(*args, **kwargs):
kwargs.setdefault("after_force_enter", mark_startup_force_entered)
ok = run_dependency_bootstrap(*args, **kwargs)
if ok:
- os.environ["LATEXSNIPPER_DEPS_OK"] = "1"
+ _dependencies_ready = True
if deps_force_entered():
mark_startup_force_entered()
return ok
diff --git a/src/application/python_runtime_resolver.py b/src/application/python_runtime_resolver.py
index f48ebc48..2deb2cdf 100644
--- a/src/application/python_runtime_resolver.py
+++ b/src/application/python_runtime_resolver.py
@@ -3,6 +3,7 @@
from __future__ import annotations
import json
+import logging
import os
import re
import subprocess
@@ -310,7 +311,7 @@ def _save_install_base_dir(p: Path) -> None:
cfg = json.loads(c.read_text("utf-8") or "{}")
cfg["install_base_dir"] = str(p)
c.write_text(json.dumps(cfg, ensure_ascii=False, indent=2), encoding="utf-8")
- print(f"[DEBUG] 依赖目录配置已保存: {p}")
+ logging.getLogger(__name__).debug(f"依赖目录配置已保存: {p}")
except Exception as e:
print(f"[WARN] 保存配置失败: {e}")
@@ -329,7 +330,7 @@ def resolve_install_base_dir() -> Path:
if not p and _is_packaged_mode() and os.name != "nt":
p = _default_packaged_user_deps_dir()
p.mkdir(parents=True, exist_ok=True)
- print(f"[DEBUG] Linux/macOS 打包依赖目录: {p}")
+ logging.getLogger(__name__).debug(f"Linux/macOS 打包依赖目录: {p}")
_save_install_base_dir(p)
if not p and os.name == "nt":
@@ -339,12 +340,12 @@ def resolve_install_base_dir() -> Path:
bundled.mkdir(parents=True, exist_ok=True)
except Exception:
pass
- print(f"[DEBUG] 首次启动使用内置依赖目录: {bundled}")
+ logging.getLogger(__name__).debug(f"首次启动使用内置依赖目录: {bundled}")
_save_install_base_dir(bundled)
p = bundled
if not p:
- print("[DEBUG] 首次启动选择依赖安装目录")
+ logging.getLogger(__name__).debug("首次启动选择依赖安装目录")
try:
p = _select_install_base_dir()
except RuntimeError:
@@ -356,11 +357,11 @@ def resolve_install_base_dir() -> Path:
py_exe = _find_install_base_python(p)
if py_exe is not None and py_exe.exists():
- print(f"[DEBUG] 已复用目录内 Python: {py_exe}")
+ logging.getLogger(__name__).debug(f"已复用目录内 Python: {py_exe}")
_save_install_base_dir(p)
return p
- print(f"[DEBUG] 选定目录未检测到可复用 Python: {p / DEPENDENCY_PYTHON_DIRNAME}")
+ logging.getLogger(__name__).debug(f"选定目录未检测到可复用 Python: {p / DEPENDENCY_PYTHON_DIRNAME}")
_save_install_base_dir(p)
return p
@@ -543,7 +544,7 @@ def _append_private_site_packages(pyexe: str | None):
sys.path.append(pstr)
except Exception:
pass
- print(f"[DEBUG] 已挂载依赖运行时路径: {base}")
+ logging.getLogger(__name__).debug(f"已挂载依赖运行时路径: {base}")
def _allowed_roots_for(pyexe: str | None, base_dir: Path) -> list[str]:
@@ -617,6 +618,7 @@ def _relaunch_with(pyexe: str):
sys.exit(5)
env = os.environ.copy()
env["LATEXSNIPPER_BOOTSTRAPPED"] = "1"
+ env["LATEXSNIPPER_RESTART"] = "1"
env["PYTHONNOUSERSITE"] = "1" if os.name == "nt" else "0"
env.pop("PYTHONHOME", None)
env.pop("PYTHONPATH", None)
@@ -625,14 +627,14 @@ def _relaunch_with(pyexe: str):
env.setdefault("QT_QPA_PLATFORM", "windows")
elif sys.platform.startswith("linux"):
env.setdefault("QT_QPA_PLATFORM", "xcb")
- argv = [pyexe, os.path.abspath(__file__), *sys.argv[1:]]
- print(f"[DEBUG] 使用私有解释器重启: {pyexe}")
+ argv = [pyexe, str(Path(__file__).resolve().parents[1] / "main.py"), *sys.argv[1:]]
+ logging.getLogger(__name__).debug(f"使用私有解释器重启: {pyexe}")
try:
proc = subprocess.Popen(argv, env=env, **_win_subprocess_kwargs())
except Exception as e:
print(f"[ERR] 启动子进程失败: {e}")
sys.exit(6)
- print(f"[DEBUG] 私有解释器子进程已启动: pid={getattr(proc, 'pid', None)}")
+ logging.getLogger(__name__).debug(f"私有解释器子进程已启动: pid={getattr(proc, 'pid', None)}")
sys.exit(0)
@@ -715,7 +717,7 @@ def _find_full_python(base_dir: Path) -> str | None:
return str(candidate)
except Exception:
pass
- print(f"[DEBUG] 忽略不支持依赖运行时的 Python: {candidate}")
+ logging.getLogger(__name__).debug(f"忽略不支持依赖运行时的 Python: {candidate}")
if getattr(sys, "frozen", False):
return None
@@ -734,16 +736,16 @@ def ensure_full_python_or_prompt(base_dir: Path) -> str | None:
py_norm = os.path.normcase(os.path.abspath(py))
bundled_norm = os.path.normcase(os.path.abspath(str(base_dir)))
if py_norm.startswith(bundled_norm):
- print(f"[DEBUG] 使用依赖目录 Python: {py}")
+ logging.getLogger(__name__).debug(f"使用依赖目录 Python: {py}")
else:
- print(f"[DEBUG] 使用外部私有 Python: {py}")
+ logging.getLogger(__name__).debug(f"使用外部私有 Python: {py}")
return py
- print("[DEBUG] 依赖目录未检测到可用 Python,使用内置运行时启动依赖管理")
+ logging.getLogger(__name__).debug("依赖目录未检测到可用 Python,使用内置运行时启动依赖管理")
return sys.executable
py = _find_full_python(base_dir)
if py:
- print(f"[DEBUG] 使用依赖目录 Python: {py}")
+ logging.getLogger(__name__).debug(f"使用依赖目录 Python: {py}")
return py
system_python = find_system_python3()
diff --git a/src/application/restart.py b/src/application/restart.py
index 4f784465..0e189273 100644
--- a/src/application/restart.py
+++ b/src/application/restart.py
@@ -14,7 +14,6 @@ def build_restart_with_dependency_management_launch(
env = dict(base_env or os.environ.copy())
env["LATEXSNIPPER_OPEN_DEPENDENCY_MANAGEMENT"] = "1"
env["LATEXSNIPPER_RESTART"] = "1"
- env.pop("LATEXSNIPPER_DEPS_OK", None)
script_path = os.path.abspath(argv0)
if script_path.lower().endswith(".py"):
diff --git a/src/assets/i18n/latexsnipper_en_US.qm b/src/assets/i18n/latexsnipper_en_US.qm
index b0498cbe..a6abcadd 100644
Binary files a/src/assets/i18n/latexsnipper_en_US.qm and b/src/assets/i18n/latexsnipper_en_US.qm differ
diff --git a/src/assets/i18n/latexsnipper_en_US.ts b/src/assets/i18n/latexsnipper_en_US.ts
index 1212e960..4017cf99 100644
--- a/src/assets/i18n/latexsnipper_en_US.ts
+++ b/src/assets/i18n/latexsnipper_en_US.ts
@@ -29,7 +29,7 @@
"
@@ -201,13 +213,13 @@ def render_changelog(changelog: str):
txt.start_new_markdown(changelog, css)
def on_result(info, err, diag):
- if DEBUG_LOG and diag:
- print(f"[DEBUG] 更新检查诊断: {diag}")
+ if diag:
+ logging.getLogger(__name__).debug("更新检查诊断: %s", diag)
if state["aborted"] or state["done"] or (not dlg.isVisible()):
return
state["done"] = True
watchdog.stop()
- bar.setRange(0, 1)
+ set_progress_range(0, 1)
dlg.unsetCursor()
if err:
message = _brief_error_message(err)
@@ -409,7 +421,7 @@ def _maybe_launch_installer(path: str):
def _on_download_progress(cur: int, total: int, path: object):
if state["aborted"] or (not dlg.isVisible()):
return
- bar.setRange(0, max(total, 1))
+ set_progress_range(0, total if total > 0 else 0)
bar.setValue(max(0, min(cur, max(total, 1))))
name = Path(str(path or "")).name or tr("更新包")
if total > 0:
@@ -432,7 +444,7 @@ def _on_download_done(path: object, err: object):
btn_copy.setEnabled(bool(state.get("info")))
btn_retry.setEnabled(True)
dlg.unsetCursor()
- bar.setRange(0, 1)
+ set_progress_range(0, 1)
lbl_status.setText(tr("下载已暂停,可稍后继续下载。"))
InfoBar.info(
title=tr("下载已暂停"),
@@ -449,7 +461,7 @@ def _on_download_done(path: object, err: object):
btn_copy.setEnabled(bool(state.get("info")))
btn_retry.setEnabled(True)
dlg.unsetCursor()
- bar.setRange(0, 1)
+ set_progress_range(0, 1)
lbl_status.setText(tr("下载失败:{message}").format(message=message))
InfoBar.error(
title=tr("下载失败"),
@@ -459,7 +471,7 @@ def _on_download_done(path: object, err: object):
position=InfoBarPosition.TOP,
)
return
- bar.setRange(0, 1)
+ set_progress_range(0, 1)
bar.setValue(1)
_maybe_launch_installer(str(path or ""))
_refresh_download_button()
@@ -489,7 +501,7 @@ def start_fetch():
txt.start_new_html(
f"{html.escape(tr('正在获取...'))}
"
)
- bar.setRange(0, 0)
+ set_progress_range(0, 0)
btn_open.setEnabled(False)
btn_copy.setEnabled(False)
btn_retry.setEnabled(False)
@@ -569,7 +581,7 @@ def do_download():
btn_copy.setEnabled(False)
btn_retry.setEnabled(False)
dlg.setCursor(Qt.CursorShape.BusyCursor)
- bar.setRange(0, 100)
+ set_progress_range(0, 100)
bar.setValue(0)
lbl_status.setText(tr("正在下载更新包..."))
state["downloading"] = True
diff --git a/test/test_dependency_python.py b/test/test_dependency_python.py
index c62836d9..6d11f0b2 100644
--- a/test/test_dependency_python.py
+++ b/test/test_dependency_python.py
@@ -6,12 +6,12 @@
def test_dependency_python_cleans_quoted_paths() -> None:
from runtime.dependency_python import clean_path_value, normalize_deps_base_dir
- assert clean_path_value('"E:\\LaTexSnipper\\broken\\python\\python.exe') == (
- "E:\\LaTexSnipper\\broken\\python\\python.exe"
+ assert clean_path_value('"C:\\Example Project\\broken\\python\\python.exe') == (
+ "C:\\Example Project\\broken\\python\\python.exe"
)
assert clean_path_value(
- "'E:\\LaTexSnipper\\tools\\deps\\python311\\python.exe'"
- ) == ("E:\\LaTexSnipper\\tools\\deps\\python311\\python.exe")
- assert normalize_deps_base_dir("E:\\LaTexSnipper\\deps\\python") == Path(
- "E:\\LaTexSnipper\\deps\\python"
+ "'C:\\Developer Envs\\mathcraft\\Scripts\\python.exe'"
+ ) == ("C:\\Developer Envs\\mathcraft\\Scripts\\python.exe")
+ assert normalize_deps_base_dir("C:\\Example Project\\deps\\python") == Path(
+ "C:\\Example Project\\deps\\python"
)
diff --git a/test/test_dependency_startup_state.py b/test/test_dependency_startup_state.py
new file mode 100644
index 00000000..08d8977f
--- /dev/null
+++ b/test/test_dependency_startup_state.py
@@ -0,0 +1,44 @@
+from application import dependency_controller as controller
+from ui import startup_splash
+
+
+def test_dependency_cache_reused_but_settings_checks_again(monkeypatch):
+ from bootstrap import deps_entry
+
+ calls = []
+ monkeypatch.setattr(controller, "_dependencies_ready", False)
+ monkeypatch.setattr(controller, "deps_force_entered", lambda: False)
+ monkeypatch.setattr(
+ deps_entry, "ensure_deps", lambda *args, **kwargs: calls.append(kwargs) or True
+ )
+ assert controller.ensure_deps(prompt_ui=False)
+ assert controller.ensure_deps(prompt_ui=False)
+ assert len(calls) == 1
+ assert controller.ensure_deps(prompt_ui=False, from_settings=True)
+ assert len(calls) == 2
+
+
+def test_force_enter_message_consumed_once(monkeypatch):
+ monkeypatch.setattr(startup_splash, "_FORCE_ENTERED", False)
+ monkeypatch.setattr(startup_splash, "ensure_startup_splash", lambda *args: None)
+ monkeypatch.setattr(startup_splash, "take_startup_splash", lambda *args: None)
+ startup_splash.mark_startup_force_entered()
+ assert startup_splash.startup_force_enter_pending()
+ message = startup_splash.startup_deps_resume_message()
+ assert not startup_splash.startup_force_enter_pending()
+ assert startup_splash.startup_deps_resume_message() != message
+
+
+def test_cancelled_dependency_startup_exits(monkeypatch, tmp_path):
+ import pytest
+ from application import bootstrap
+
+ monkeypatch.delenv("LATEXSNIPPER_BOOTSTRAPPED", raising=False)
+ monkeypatch.delenv("LATEXSNIPPER_OPEN_DEPENDENCY_MANAGEMENT", raising=False)
+ monkeypatch.setattr(bootstrap, "_sanitize_sys_path", lambda *args: None)
+ monkeypatch.setattr(bootstrap, "_is_packaged_mode", lambda: False)
+ monkeypatch.setattr(bootstrap, "ensure_startup_splash", lambda *args: None)
+ monkeypatch.setattr(bootstrap, "ensure_deps", lambda **kwargs: False)
+ with pytest.raises(SystemExit) as result:
+ bootstrap._bootstrap_dependencies(tmp_path, "python")
+ assert result.value.code == 0
diff --git a/test/test_download_control.py b/test/test_download_control.py
new file mode 100644
index 00000000..e19b128d
--- /dev/null
+++ b/test/test_download_control.py
@@ -0,0 +1,79 @@
+import subprocess
+import sys
+import threading
+import time
+
+from bootstrap.download_control import DownloadControl, wait_for_download
+
+
+def test_pause_stops_running_process_and_resume_continues(tmp_path):
+ heartbeat = tmp_path / "heartbeat"
+ code = (
+ "import pathlib,sys,time; p=pathlib.Path(sys.argv[1]); "
+ + "\nwhile True:\n p.write_text(str(time.monotonic()))\n time.sleep(0.02)"
+ )
+ proc = subprocess.Popen([sys.executable, "-c", code, str(heartbeat)])
+ gate = threading.Event()
+ gate.set()
+ control = DownloadControl(gate)
+ control.set_process(proc)
+ try:
+ deadline = time.monotonic() + 5
+ while not heartbeat.exists() and time.monotonic() < deadline:
+ time.sleep(0.02)
+ assert heartbeat.exists()
+ control.set_paused(True)
+ before = heartbeat.read_bytes()
+ time.sleep(0.15)
+ assert heartbeat.read_bytes() == before
+ control.set_paused(False)
+ deadline = time.monotonic() + 5
+ while heartbeat.read_bytes() == before and time.monotonic() < deadline:
+ time.sleep(0.02)
+ assert heartbeat.read_bytes() != before
+ finally:
+ control.set_paused(False)
+ proc.terminate()
+ proc.wait(timeout=5)
+ control.set_process(None)
+
+
+def test_cooperative_pause_can_resume_or_cancel():
+ gate, stop, done = threading.Event(), threading.Event(), threading.Event()
+ result = []
+
+ def run():
+ result.append(wait_for_download(gate, stop))
+ done.set()
+
+ thread = threading.Thread(target=run)
+ thread.start()
+ assert not done.wait(0.05)
+ gate.set()
+ assert done.wait(1)
+ thread.join()
+ assert result == [True]
+ gate.clear()
+ stop.set()
+ assert wait_for_download(gate, stop) is False
+
+
+def test_exit_confirmation_keeps_task_until_confirmed(monkeypatch):
+ from PyQt6.QtWidgets import QApplication, QMessageBox
+ from bootstrap.progress_dialog import InstallProgressDialog
+
+ app = QApplication.instance() or QApplication([])
+ dialog = InstallProgressDialog()
+ dialog.confirm_cancel = True
+ monkeypatch.setattr(
+ QMessageBox, "question", lambda *args: QMessageBox.StandardButton.No
+ )
+ assert dialog.confirm_exit() is False
+ assert dialog.confirm_cancel
+ monkeypatch.setattr(
+ QMessageBox, "question", lambda *args: QMessageBox.StandardButton.Yes
+ )
+ assert dialog.confirm_exit() is True
+ assert dialog.confirm_exit() is True
+ dialog.close()
+ app.processEvents()
diff --git a/test/test_main_window_import.py b/test/test_main_window_import.py
new file mode 100644
index 00000000..851b5f2a
--- /dev/null
+++ b/test/test_main_window_import.py
@@ -0,0 +1,21 @@
+"""Verify the real UI import graph in an isolated interpreter."""
+
+import os
+from pathlib import Path
+import subprocess
+import sys
+
+
+def test_main_window_imports_without_test_stubs():
+ root = Path(__file__).resolve().parents[1]
+ env = os.environ.copy()
+ env["QT_QPA_PLATFORM"] = "offscreen"
+ result = subprocess.run(
+ [sys.executable, "-c", "import sys; sys.path[:0] = sys.argv[1:]; from ui.main_window import MainWindow; assert callable(MainWindow)", str(root / "src"), str(root)],
+ cwd=root,
+ env=env,
+ capture_output=True,
+ text=True,
+ timeout=60,
+ )
+ assert result.returncode == 0, result.stdout + result.stderr
diff --git a/test/test_onboarding.py b/test/test_onboarding.py
new file mode 100644
index 00000000..e71e50ea
--- /dev/null
+++ b/test/test_onboarding.py
@@ -0,0 +1,262 @@
+"""Behavioral coverage for the shared native onboarding flow."""
+
+from PyQt6.QtCore import QPoint, pyqtSignal, Qt
+from PyQt6.QtTest import QTest
+from PyQt6.QtWidgets import (
+ QApplication,
+ QDialog,
+ QMainWindow,
+ QPushButton,
+ QVBoxLayout,
+ QWidget,
+)
+import pytest
+
+from ui.onboarding_controller import AUTO_SHOW_KEY, OnboardingController
+
+
+class Config:
+ def __init__(self):
+ self.data = {}
+
+ def get(self, key, default=None):
+ return self.data.get(key, default)
+
+ def set(self, key, value):
+ self.data[key] = value
+
+
+class Host(QMainWindow):
+ modelStatusChanged = pyqtSignal()
+
+ def __init__(self):
+ super().__init__()
+ self.cfg = Config()
+ self._model_warmup_in_progress = False
+ self.setCentralWidget(QWidget())
+ layout = QVBoxLayout(self.centralWidget())
+ for name in (
+ "capture_button",
+ "latex_editor",
+ "preview_view",
+ "export_btn",
+ "change_key_button",
+ "status_label",
+ "onboarding_button",
+ ):
+ widget = QPushButton(name)
+ setattr(self, name, widget)
+ layout.addWidget(widget)
+ self.resize(1180, 720)
+ self.onboarding = OnboardingController(self)
+
+ def set_shortcut(self):
+ self.shortcut_window = QDialog(self)
+ self.shortcut_window.show()
+
+
+@pytest.fixture(scope="session")
+def qt_app():
+ return QApplication.instance() or QApplication([])
+
+
+@pytest.fixture
+def host(qt_app):
+ app = qt_app
+ window = Host()
+ window.show()
+ app.processEvents()
+ yield window
+ window.close()
+ window.deleteLater()
+ app.processEvents()
+
+
+def test_complete_and_manual_replay_preserve_preference(host):
+ tour = host.onboarding
+ tour.start_automatically()
+ for _ in range(6):
+ tour.advance()
+ assert not tour.active
+ assert host.cfg.get(AUTO_SHOW_KEY) is False
+ tour.start_automatically()
+ assert not tour.active
+ tour.start()
+ tour.finish()
+ assert host.cfg.get(AUTO_SHOW_KEY) is False
+
+
+@pytest.mark.parametrize("action", ["skip", "close_button", "escape"])
+def test_automatic_exit_disables_future_tours(host, action):
+ tour = host.onboarding
+ tour.start_automatically()
+ tour.advance()
+ if action == "escape":
+ QTest.keyClick(tour.surface, Qt.Key.Key_Escape)
+ else:
+ getattr(tour.surface, action).click()
+ assert tour.active
+ assert host.cfg.get(AUTO_SHOW_KEY, True) is True
+ tour.surface.back.click()
+ assert tour.index == 1
+ tour.request_exit()
+ tour.surface.next.click()
+ assert not tour.active
+ assert host.cfg.get(AUTO_SHOW_KEY) is False
+
+
+@pytest.mark.parametrize("preference", [True, False])
+def test_manual_exit_and_application_close_preserve_preference(host, preference):
+ host.cfg.set(AUTO_SHOW_KEY, preference)
+ tour = host.onboarding
+ tour.start()
+ tour.finish()
+ assert host.cfg.get(AUTO_SHOW_KEY) is preference
+ tour.start(automatic=True)
+ host.close()
+ assert not tour.active
+ assert host.cfg.get(AUTO_SHOW_KEY) is preference
+
+
+def test_model_busy_only_affects_preparation_ring(host):
+ tour = host.onboarding
+ tour.start()
+ for _ in range(3):
+ tour.advance()
+ host._model_warmup_in_progress = True
+ host.modelStatusChanged.emit()
+ assert tour.surface.ring.isVisible()
+ host._model_warmup_in_progress = False
+ host.modelStatusChanged.emit()
+ assert not tour.surface.ring.isVisible()
+ tour.advance()
+ tour.advance()
+ title = tour.surface.title.text()
+ host._model_warmup_in_progress = True
+ host.modelStatusChanged.emit()
+ assert tour.surface.title.text() == title
+ assert not tour.surface.ring.isVisible()
+ tour.advance()
+ assert host._model_warmup_in_progress
+
+
+def test_shortcut_pause_resume_and_single_instance(host):
+ tour = host.onboarding
+ tour.start()
+ tour.advance()
+ tour.advance()
+ tour.action("shortcut")
+ assert not tour.surface.isVisible()
+ tour.start()
+ assert tour.index == 2
+ host.cfg.set("hotkey", "Ctrl+Shift+K")
+ host.shortcut_window.close()
+ QApplication.processEvents()
+ assert tour.surface.isVisible()
+ assert tour.surface.detail.text() == "Ctrl+Shift+K"
+
+
+def test_resize_keeps_card_inside_surface(host):
+ tour = host.onboarding
+ tour.start()
+ for _ in range(6):
+ host.resize(980, 680)
+ QApplication.processEvents()
+ assert tour.surface.rect().contains(tour.surface.card.geometry())
+ tour.advance()
+
+
+def test_tab_stays_inside_card_and_closed_tour_does_not_reopen(host):
+ tour = host.onboarding
+ tour.start()
+ for _ in range(16):
+ QTest.keyClick(QApplication.focusWidget(), Qt.Key.Key_Tab)
+ assert tour.surface.card.isAncestorOf(QApplication.focusWidget())
+ tour.finish()
+ tour.render()
+ assert not tour.surface.isVisible()
+
+
+@pytest.mark.parametrize("origin", [(140, 90), (380, 220)])
+def test_spotlights_align_with_nested_widgets_after_move_and_resize(host, origin):
+ panel = QWidget(host.centralWidget())
+ panel.setGeometry(35, 60, 700, 450)
+ panel.show()
+ target = host.capture_button
+ target.setParent(panel)
+ target.setGeometry(25, 30, 200, 40)
+ target.show()
+ host.move(*origin)
+ host.resize(1250, 780)
+ host.onboarding.start()
+ host.onboarding.advance()
+ QApplication.processEvents()
+ surface = host.onboarding.surface
+ for widget, rect in zip(surface.targets, surface.target_rects()):
+ actual = surface.mapToGlobal(rect.topLeft().toPoint() + QPoint(5, 5))
+ assert actual == widget.mapToGlobal(QPoint())
+ assert rect.width() == widget.width() + 10
+ assert rect.height() == widget.height() + 10
+ assert host.preview_view in surface.targets
+ host.onboarding.advance()
+ rect = surface.target_rects()[0]
+ assert surface.mapToGlobal(
+ rect.topLeft().toPoint() + QPoint(5, 5)
+ ) == host.change_key_button.mapToGlobal(QPoint())
+
+
+@pytest.mark.parametrize(
+ "language,page,fragment",
+ [
+ ("zh_CN", "README.zh-CN.md", "支持本项目"),
+ ("en_US", "readme.md", "support-the-project"),
+ ],
+)
+def test_combined_support_opens_readme_for_current_ui_language(
+ host, monkeypatch, language, page, fragment
+):
+ import ui.onboarding_controller as controller
+
+ opened = []
+ monkeypatch.setattr(controller, "current_ui_language", lambda: language)
+ monkeypatch.setattr(
+ controller.QDesktopServices, "openUrl", lambda url: opened.append(url) or True
+ )
+ tour = host.onboarding
+ tour.start()
+ for _ in range(4):
+ tour.advance()
+ assert tour.surface.actions.count() == 3
+ tour.surface.actions.itemAt(2).widget().click()
+ assert len(opened) == 1
+ assert opened[0].path().endswith("/" + page)
+ assert opened[0].fragment() == fragment
+ from pathlib import Path
+
+ root = Path(__file__).resolve().parents[1]
+ assert page in {entry.name for entry in root.iterdir() if entry.is_file()}
+
+
+@pytest.mark.parametrize("with_auxiliary", [False, True])
+def test_window_destruction_does_not_access_deleted_qt_objects(
+ qt_app, monkeypatch, with_auxiliary
+):
+ import sys
+ from PyQt6 import sip
+ from PyQt6.QtCore import QEvent
+
+ errors = []
+ monkeypatch.setattr(sys, "excepthook", lambda *error: errors.append(error))
+ window = Host()
+ window.show()
+ tour = window.onboarding
+ tour.start()
+ if with_auxiliary:
+ tour.action("shortcut")
+ window.shortcut_window.close()
+ sip.delete(window)
+ QApplication.processEvents()
+ assert not errors
+ assert tour.eventFilter(None, QEvent(QEvent.Type.ChildRemoved)) is False
+ tour.render()
+ tour.refresh_busy()
diff --git a/test/test_python_runtime_resolver.py b/test/test_python_runtime_resolver.py
index c6f035bb..4888e7f1 100644
--- a/test/test_python_runtime_resolver.py
+++ b/test/test_python_runtime_resolver.py
@@ -38,3 +38,19 @@ def fake_run(cmd, *args, **kwargs):
assert resolver._find_full_python(tmp_path) == str(pyexe)
assert commands
assert all("ensurepip" not in code and "venv" not in code for code in commands)
+
+
+def test_private_interpreter_relaunches_application(monkeypatch):
+ import pytest
+ from types import SimpleNamespace
+ from application import python_runtime_resolver as resolver
+
+ calls = []
+ monkeypatch.setattr(subprocess, "Popen", lambda argv, **kwargs: calls.append((argv, kwargs)) or SimpleNamespace(pid=123))
+ monkeypatch.setattr(sys, "argv", [str(SRC / "main.py"), "--example"])
+ with pytest.raises(SystemExit) as result:
+ resolver._relaunch_with(sys.executable)
+ assert result.value.code == 0
+ argv, kwargs = calls[0]
+ assert argv == [sys.executable, str(SRC / "main.py"), "--example"]
+ assert kwargs["env"]["LATEXSNIPPER_RESTART"] == "1"
diff --git a/test/test_runtime_logging.py b/test/test_runtime_logging.py
index c1e74e0b..71c7fb2d 100644
--- a/test/test_runtime_logging.py
+++ b/test/test_runtime_logging.py
@@ -39,9 +39,13 @@ def test_persistent_log_excludes_debug_and_strips_prefixes(tmp_path: Path) -> No
target = Path(sys.argv[1])
runtime_logging.app_log_dir = lambda: target
runtime_logging.app_state_dir = lambda: target / "state"
+runtime_logging.runtime_log_path().write_text("previous-session", encoding="utf-8")
runtime_logging.init_app_logging()
print("[DEBUG] debug-only")
print("[INFO] ready")
+logging.info("logging-record")
+sys.stderr.write("stderr-record\\n")
+assert "ready" in runtime_logging.runtime_log_path().read_text(encoding="utf-8")
logging.shutdown()
"""
env = os.environ.copy()
@@ -62,6 +66,11 @@ def test_persistent_log_excludes_debug_and_strips_prefixes(tmp_path: Path) -> No
assert "debug-only" not in text
assert "[INFO] [INFO] ready" not in text
assert text.count("ready") == 1
+ session = (tmp_path / "runtime-session.log").read_text(encoding="utf-8")
+ assert "previous-session" not in session
+ assert session.count("ready") == 1
+ assert session.count("logging-record") == 1
+ assert "stderr-record" in session
def test_plain_log_rotation_is_bounded(tmp_path: Path) -> None:
diff --git a/test/test_runtime_packaging.py b/test/test_runtime_packaging.py
index 37d3a6ad..7578639b 100644
--- a/test/test_runtime_packaging.py
+++ b/test/test_runtime_packaging.py
@@ -1,9 +1,13 @@
# pyright: reportMissingImports=false
+import ast
+import os
import re
import unittest
from pathlib import Path
+import pytest
+
ROOT = Path(__file__).resolve().parents[1]
@@ -21,3 +25,51 @@ def test_pyinstaller_spec_keeps_psutil_for_packaged_speed_meter(self):
self.assertIn('"psutil"', hiddenimports.group(1))
self.assertNotIn('"psutil"', excludes.group(1))
self.assertNotIn('"psutil"', prune_prefixes.group(1))
+
+
+def run_seed_collection():
+ """Execute the spec's seed-input boundary without invoking PyInstaller."""
+ path = ROOT / "LaTeXSnipper.spec"
+ nodes = ast.parse(path.read_text(encoding="utf-8")).body
+ start = next(
+ i
+ for i, node in enumerate(nodes)
+ if isinstance(node, ast.Assign)
+ and any(
+ isinstance(t, ast.Name) and t.id == "bundled_python" for t in node.targets
+ )
+ )
+ end = next(
+ i
+ for i, node in enumerate(nodes[start:], start)
+ if isinstance(node, ast.Assign)
+ and any(isinstance(t, ast.Name) and t.id == "a" for t in node.targets)
+ )
+ namespace = {
+ "os": os,
+ "Path": Path,
+ "extra_datas": [],
+ "_collect_tree_as_datas": lambda source, destination: [(source, destination)],
+ }
+ exec(
+ compile(ast.Module(body=nodes[start:end], type_ignores=[]), str(path), "exec"),
+ namespace,
+ )
+ return namespace["extra_datas"]
+
+
+def test_windows_bundle_collects_explicit_seed_path(tmp_path, monkeypatch):
+ seed = tmp_path / "runner inputs" / "python seed"
+ seed.mkdir(parents=True)
+ (seed / "python.exe").touch()
+ monkeypatch.setenv("LATEXSNIPPER_BUNDLED_PYTHON", str(seed))
+ assert run_seed_collection() == [(seed.resolve(), "deps/python311")]
+
+
+def test_windows_bundle_requires_usable_seed(tmp_path, monkeypatch):
+ monkeypatch.delenv("LATEXSNIPPER_BUNDLED_PYTHON", raising=False)
+ with pytest.raises(RuntimeError, match="must identify"):
+ run_seed_collection()
+ monkeypatch.setenv("LATEXSNIPPER_BUNDLED_PYTHON", str(tmp_path))
+ with pytest.raises(RuntimeError, match="missing python.exe"):
+ run_seed_collection()
diff --git a/user_manual/user_manual.md b/user_manual/user_manual.md
index 54fe1e4b..ee0bacdc 100644
--- a/user_manual/user_manual.md
+++ b/user_manual/user_manual.md
@@ -11,7 +11,7 @@
---
-
+
@@ -124,6 +124,8 @@ LaTeXSnipper 首次启动或检测到关键依赖缺失时会打开“依赖管
## 主窗口功能入口与识别流程
+首次进入主窗口会显示六步快速入门,介绍截图、导出、快捷键、模型准备与支持入口。完成或确认退出引导后不再自动显示;直接关闭应用不修改偏好。随时可点击“快速入门”重新查看。模型在后台准备,引导无需等待模型就绪。
+
### 主窗口按钮分别做什么
当前主窗口左侧是截图和历史区,右侧是 LaTeX 编辑、预览和工具入口:
@@ -506,7 +508,7 @@ Windows 安装包默认使用内置依赖根:
<安装目录>\_internal\deps
```
-Linux/macOS 安装包不会把构建机的 `tools/deps/python311` 或任意虚拟环境打进安装包。默认依赖根是用户可写目录:
+Linux/macOS 安装包不会把构建机的虚拟环境打进安装包。默认依赖根是用户可写目录:
```text
Linux: ~/.latexsnipper/deps
@@ -1007,65 +1009,34 @@ README 中提供了三种平台的源码运行步骤,经验证均正确可行
> - **macOS:** `python3 -m venv .venv` → `pip install -r requirements-macos.txt` → `python src/main.py`
> - `requirements-linux.txt` 和 `requirements-macos.txt` 通过 `-r requirements.txt` 自动包含公共依赖;Linux 额外使用 `pynput` 支持全局快捷键,macOS 使用原生全局快捷键实现。
-### 开发环境路径不要混用
-
-**现象:** 本地存在 `python311/` 和 `tools/deps/python311/` 两套 Python,不确定应该用哪一个。
+### 开发环境与发布构建
-**规则:**
+开发者可以自行选择 venv、Conda、uv 等环境及存放位置。激活环境后,运行、检查和 IDE 使用同一解释器即可;项目不要求固定的环境目录。
-- `python311/` 是 Windows 安装包的内置 Python 模板,只用于打包复制,不要安装开发依赖,不要执行 ruff/pyright/pytest。
-- `tools/deps/python311/` 是 IDE、开发检查和 Actions 打包使用的项目环境,可以安装 `requirements*.txt`、`ruff`、`pytest`、`pyright`。
-- Linux/macOS 安装包运行时不会携带 `tools/deps/python311`。Linux 默认依赖根是 `~/.latexsnipper/deps`,macOS 默认依赖根是 `~/Library/Application Support/LaTeXSnipper/deps`;用户切换依赖目录后,以配置中的 `install_base_dir` 为准。
-
-**Windows 开发者初始化示例:**
+Windows 示例(`.venv` 仅为示例,可替换为自己的路径):
```powershell
-py -3.11 -m venv tools\deps\python311
-.\tools\deps\python311\python.exe -m pip install --upgrade pip wheel setuptools
-.\tools\deps\python311\python.exe -m pip install -r requirements.txt
-.\tools\deps\python311\python.exe -m pip install -r requirements-build.txt
-.\tools\deps\python311\python.exe -m pip install ruff pytest pyright
+py -3.11 -m venv .venv
+.\.venv\Scripts\Activate.ps1
+python -m pip install -r requirements.txt
+python -m pip install ruff pytest pyright
+python src/main.py
```
-> [!NOTE]
-> **路径速查**
->
-> - 仓库根目录 `python311/` — Windows 模板环境,不要污染。
-> - `tools/deps/python311/` — 开发、检查、构建环境。
-> - `~/.latexsnipper/deps` — Linux 默认运行时依赖根。
-> - `~/Library/Application Support/LaTeXSnipper/deps` — macOS 默认运行时依赖根。
-> - `<依赖根>/python` — 程序创建的主 Python 依赖环境。
-> - `<应用状态目录>/tools/pandoc` — 程序创建的共享工具目录,切换依赖根后继续复用。
+Linux/macOS 使用各自的 requirements 文件,并激活所选环境。客户端安装包统一由 GitHub Actions 的 `release.yml` 工作流构建;Windows 内置运行时在 runner 临时目录准备,不收集开发者的 Python 环境。
-### 开发者验证命令注意事项
+### 开发者验证命令
-常用验证命令:
+在已激活并安装检查工具的环境中执行:
```bash
-.\tools\deps\python311\python.exe -m ruff check .
-.\tools\deps\python311\python.exe -m pytest test
-.\tools\deps\python311\python.exe -m pyright
-.\tools\deps\python311\python.exe -m compileall -q src mathcraft_ocr test
+python -m ruff check .
+python -m pytest test
+python -m pyright --pythonpath "$(python -c 'import sys; print(sys.executable)')"
+python -m compileall -q src mathcraft_ocr test
```
-> [!WARNING]
-> **运行前必做**
->
-> `ruff`、`pytest`、`pyright` 不在任何 `requirements*.txt` 中。
-> 首次运行验证前需要手动安装:
->
-> ```bash
-> .\tools\deps\python311\python.exe -m pip install ruff pytest pyright
-> ```
-
-### `pyrightconfig.json` 中的路径
-
-类型检查应使用 `tools/deps/python311` 的第三方包解析环境,根目录 `python311/` 只作为 Windows 模板排除在检查外。不要为了让 pyright 通过而向根目录模板安装开发包。
-
-> [!TIP]
-> **提示**
->
-> 如果 `tools/deps/python311` 不存在,先创建开发环境;不要改用根目录 `python311` 作为替代。
+IDE 中选择相同的解释器;类型检查通过 `--pythonpath` 指定所选解释器,无需在项目配置中硬编码第三方包目录。
---
diff --git a/user_manual/user_manual.typ b/user_manual/user_manual.typ
index 40f56ff4..a6fd311b 100644
--- a/user_manual/user_manual.typ
+++ b/user_manual/user_manual.typ
@@ -112,7 +112,7 @@
// ── 封面图 ──
#align(center)[
- #image("../docs/latexsnipper-3.0.0.png", width: 100%)
+ #image("../docs/latexsnipper.png", width: 100%)
#v(1em)
]
@@ -240,6 +240,8 @@ LaTeXSnipper 首次启动或检测到关键依赖缺失时会打开“依赖管
// ═══════════════════════════════════════════
#heading(level: 1)[主窗口功能入口与识别流程]
+首次进入主窗口会显示六步快速入门,介绍截图、导出、快捷键、模型准备与支持入口。完成或确认退出引导后不再自动显示;直接关闭应用不修改偏好。随时可点击“快速入门”重新查看。模型在后台准备,引导无需等待模型就绪。
+
== 主窗口按钮分别做什么
当前主窗口左侧是截图和历史区,右侧是 LaTeX 编辑、预览和工具入口:
@@ -674,7 +676,7 @@ LaTeXSnipper 使用 `install_base_dir` 作为活动 Python 依赖根。用户在
<安装目录>\_internal\deps
```
-Linux/macOS 安装包不会把构建机的 `tools/deps/python311` 或任意虚拟环境打进安装包。默认依赖根是用户可写目录:
+Linux/macOS 安装包不会把构建机的虚拟环境打进安装包。默认依赖根是用户可写目录:
```text
Linux: ~/.latexsnipper/deps
@@ -1266,65 +1268,34 @@ README 中提供了三种平台的源码运行步骤,经验证均正确可行
- `requirements-linux.txt` 和 `requirements-macos.txt` 通过 `-r requirements.txt` 自动包含公共依赖;Linux 额外使用 `pynput` 支持全局快捷键,macOS 使用原生全局快捷键实现。
])
-== 开发环境路径不要混用
-
-*现象:* 本地存在 `python311/` 和 `tools/deps/python311/` 两套 Python,不确定应该用哪一个。
-
-#v(0.35em)
-
-*规则:*
-- `python311/` 是 Windows 安装包的内置 Python 模板,只用于打包复制,不要安装开发依赖,不要执行 ruff/pyright/pytest。
-- `tools/deps/python311/` 是 IDE、开发检查和 Actions 打包使用的项目环境,可以安装 `requirements*.txt`、`ruff`、`pytest`、`pyright`。
-- Linux/macOS 安装包运行时不会携带 `tools/deps/python311`。Linux 默认依赖根是 `~/.latexsnipper/deps`,macOS 默认依赖根是 `~/Library/Application Support/LaTeXSnipper/deps`;用户切换依赖目录后,以配置中的 `install_base_dir` 为准。
+== 开发环境与发布构建
-#v(0.35em)
+开发者可以自行选择 venv、Conda、uv 等环境及存放位置。激活环境后,运行、检查和 IDE 使用同一解释器即可;项目不要求固定的环境目录。
-*Windows 开发者初始化示例:*
+Windows 示例(`.venv` 仅为示例,可替换为自己的路径):
-```text
-py -3.11 -m venv tools\deps\python311
-.\tools\deps\python311\python.exe -m pip install --upgrade pip wheel setuptools
-.\tools\deps\python311\python.exe -m pip install -r requirements.txt
-.\tools\deps\python311\python.exe -m pip install -r requirements-build.txt
-.\tools\deps\python311\python.exe -m pip install ruff pytest pyright
+```powershell
+py -3.11 -m venv .venv
+.\.venv\Scripts\Activate.ps1
+python -m pip install -r requirements.txt
+python -m pip install ruff pytest pyright
+python src/main.py
```
-#info-block("路径速查", [
- - 仓库根目录 `python311/` — Windows 模板环境,不要污染。
- - `tools/deps/python311/` — 开发、检查、构建环境。
- - `~/.latexsnipper/deps` — Linux 默认运行时依赖根。
- - `~/Library/Application Support/LaTeXSnipper/deps` — macOS 默认运行时依赖根。
- - `<依赖根>/python` — 程序创建的主 Python 依赖环境。
- - `<应用状态目录>/tools/pandoc` — 程序创建的共享工具目录,切换依赖根后继续复用。
-])
+Linux/macOS 使用各自的 requirements 文件,并激活所选环境。客户端安装包统一由 GitHub Actions 的 `release.yml` 工作流构建;Windows 内置运行时在 runner 临时目录准备,不收集开发者的 Python 环境。
-== 开发者验证命令注意事项
+== 开发者验证命令
-常用验证命令:
+在已激活并安装检查工具的环境中执行:
-```text
-.\tools\deps\python311\python.exe -m ruff check .
-.\tools\deps\python311\python.exe -m pytest test
-.\tools\deps\python311\python.exe -m pyright
-.\tools\deps\python311\python.exe -m compileall -q src mathcraft_ocr test
+```bash
+python -m ruff check .
+python -m pytest test
+python -m pyright --pythonpath "$(python -c 'import sys; print(sys.executable)')"
+python -m compileall -q src mathcraft_ocr test
```
-#warn-block("运行前必做", [
- `ruff`、`pytest`、`pyright` 不在任何 `requirements*.txt` 中。
- 首次运行验证前需要手动安装:
- #v(0.25em)
- ```text
- .\tools\deps\python311\python.exe -m pip install ruff pytest pyright
- ```
-])
-
-== `pyrightconfig.json` 中的路径
-
-类型检查应使用 `tools/deps/python311` 的第三方包解析环境,根目录 `python311/` 只作为 Windows 模板排除在检查外。不要为了让 pyright 通过而向根目录模板安装开发包。
-
-#tip-block("提示", [
- 如果 `tools/deps/python311` 不存在,先创建开发环境;不要改用根目录 `python311` 作为替代。
-])
+IDE 中选择相同的解释器;类型检查通过 `--pythonpath` 指定所选解释器,无需在项目配置中硬编码第三方包目录。
#pagebreak()