diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml new file mode 100644 index 0000000..ce97f9a --- /dev/null +++ b/.github/workflows/e2e.yml @@ -0,0 +1,38 @@ +name: E2E + +on: + pull_request: + push: + branches: + - master + +jobs: + playwright: + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Install Playwright browsers + run: npx playwright install --with-deps chromium + + - name: Run e2e tests + run: npm run test:e2e -- --project=chromium + + - name: Upload Playwright report + if: always() + uses: actions/upload-artifact@v4 + with: + name: playwright-report + path: playwright-report + retention-days: 7 diff --git a/.gitignore b/.gitignore index 93fe364..bea231a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,21 +1,32 @@ -# Node +*:Zone.Identifier node_modules/ - -# Logs -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* - -# OS files -.DS_Store -Thumbs.db - -# Env files +output/ +assets/*.jpg +assets/*.jpeg +assets/*.png +assets/*.JPG +assets/*.JPEG +assets/*.PNG +.venv/ +backend/.venv/ +__pycache__/ +*.pyc +backend/__pycache__/ +backend/runs/ +backend/models/*.pt +backend/models/*.onnx +backend/yolov8*.pt +/yolov8*.pt +backend/data/roi_dataset/labels/*.cache +backend/data/roi_dataset/previews/ +backend/data/roi_dataset/qa_previews/ +backend/data/roi_dataset/roi_boxes.json .env -.env.local -.env.*.local -# Build output -coverage/ -dist/ +# Playwright CLI generated artifacts +.playwright-cli/ +playwright-report/ +test-results/ + +# Local parking area for unrelated files +AOB/ diff --git a/AGENTS.md b/AGENTS.md index d10b7c5..47a239f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -3,27 +3,53 @@ ## Project Structure & Module Organization - `index.html`: Single-page UI layout and content. - `styles.css`: Global styles and visual system. -- `app.js`: Client-side logic (OCR flow, email draft generation). +- `app.js`: Thin module entrypoint that imports `src/main.js`. +- `src/main.js`: UI orchestration and event wiring. +- `src/ocr/`: Neural-ROI-first OCR pipeline with strip-first decoding and selection safeguards. +- `src/email/`: Email draft generation and link helpers. +- `src/testset/`: Manual test-set runner logic. +- `src/debug/`: Debug overlay rendering helpers. +- `backend/`: Optional FastAPI service for neural ROI + digit classifier inference and training scripts. +- `backend/build_digit_dataset.py`: Export strip/cell OCR datasets + QA previews from ROI labels. +- `backend/generate_synthetic_digit_dataset.py`: Build synthetic train-only digit sections (direct cell augmentation + optional composed windows re-split equispaced). +- `backend/plan_digit_expansion.py`: Generate prioritized capture plan for underrepresented digits. +- `backend/validate_digit_dataset.py`: Validate manifest consistency and QA preview coverage. +- `backend/train_digit_classifier.py`: Train per-cell digit classifier checkpoint. - `package.json`: Local dev scripts. - `README.md`: Project overview and setup notes. - `assets/`: Static assets and example uploads. -- `assets/meter_13012026.jpg`: Example upload asset. ## Build, Test, and Development Commands - `npm run serve`: Start a simple local web server on port 8000. - `npm run dev`: Alias of `npm run serve`. +- `cd backend && python3 -m venv .venv && source .venv/bin/activate && pip install -r requirements.txt`: Backend setup. +- `cd backend && source .venv/bin/activate && python train_roi.py --data data/roi_dataset.yaml --base-model yolov8n.pt --rotation-angles 90,180,270,360 --heavy-augment`: Fine-tune pretrained ROI detector with enforced augmentation policy. +- `cd backend && source .venv/bin/activate && python build_digit_dataset.py --clean`: Rebuild digit strip/cell exports and QA previews. +- `cd backend && source .venv/bin/activate && python validate_digit_dataset.py`: Validate dataset/manifests before training. +- `cd backend && source .venv/bin/activate && python generate_synthetic_digit_dataset.py --clean --direct-per-real 6 --compose-window-count 180`: Generate synthetic train-only digit sections from real train labels. +- `cd backend && source .venv/bin/activate && python plan_digit_expansion.py --target-train-per-digit 12 --priority-digits 4,5,6,9`: Refresh targeted capture checklist. +- `cd backend && source .venv/bin/activate && python train_digit_classifier.py --device cpu`: Train per-cell digit classifier model (real-only). +- `cd backend && source .venv/bin/activate && python train_digit_classifier.py --device cpu --synthetic-root data/digit_dataset/sections_synthetic --synthetic-target-ratio 2.0`: Train on mixed real + synthetic train split while keeping val/test real-only. +- `cd backend && source .venv/bin/activate && uvicorn app:app --host 127.0.0.1 --port 8001 --reload`: Run neural ROI API. -Open `http://localhost:8000` after running a serve command. +Open `http://localhost:8000` after running a serve command. Backend endpoints default to `http://127.0.0.1:8001/roi/detect` and `http://127.0.0.1:8001/digit/predict-cells`. ## Coding Style & Naming Conventions - Use 2-space indentation in HTML/CSS/JS. - Keep files ASCII-only unless there is a strong reason for Unicode. - Use descriptive, lower-case IDs and class names (e.g., `photo-input`, `module-grid`). -- Prefer clear, small functions in `app.js` and avoid deep nesting. +- Prefer clear, small functions in `src/` modules and avoid deep nesting. ## Testing Guidelines -- No automated tests are configured. -- Manual checks: upload image, run OCR, verify email draft fields, and confirm Gmail draft link. +- Automated browser tests are configured with Playwright. +- `npm run test:e2e`: Runs `tests/e2e/neural-roi.spec.js` (neural ROI failure handling + ROI geometry + strip-only OCR behavior). +- CI: `.github/workflows/e2e.yml` runs on each pull request and on pushes to `master`. +- Frontend manual checks: upload image, run OCR, verify email draft fields, and confirm Gmail draft link. +- OCR test-set checks: run "Run test set" and inspect `MAE`, `Exact Match`, `No-read`, `Failure Reason`, and debug stages. +- Backend sanity checks: `GET /health` and confirm `ready: true`, `roi_ready: true`, and expected `model_path`. +- Prefer running the test set from UI with debug overlay enabled. +- Before committing OCR changes, run both `npm run test:e2e` and the UI "Run test set". +- ROI training policy: always use heavy augmentation and rotation expansion (`90,180,270,360`). `train_roi.py` enforces this by default and only allows weaker runs with `--allow-no-augment-policy`. ## Commit & Pull Request Guidelines - No commit message convention is established in this repo. @@ -33,3 +59,61 @@ Open `http://localhost:8000` after running a serve command. ## Security & Configuration Tips - The Gmail draft flow opens a client-side draft; no credentials are stored in code. - OCR runs in the browser; avoid adding API keys to the client without a secure proxy. +- Backend is intended for local use; keep host/CORS scoped to localhost unless explicitly deploying. + +## IMPORTANT +- When using Playwright in this environment, global `playwright-cli` may be more reliable than the wrapper if npm network is flaky. + +## OCR Working State + +- App + backend run locally on `127.0.0.1:8000` and `127.0.0.1:8001`. +- Neural ROI is mandatory in the frontend OCR flow (heuristic ROI fallback removed). +- On neural ROI failure, the UI shows an explicit reason and asks for manual measurement input. +- Backend default ROI model is pinned to `backend/models/roi-rotaug-e30-640.pt` (override with `ROI_MODEL_PATH`). +- `train_roi.py` enforces augmentation policy by default: heavy online augmentation + rotation expansion `90,180,270,360`. +- Digit-classifier inference is optional behind `OCR_CONFIG.digitClassifier.enabled` (default `false`). +- Backend serves ROI + digit endpoints and reports readiness via `GET /health`. +- Test-set table includes `Detected`, `Absolute Error`, `Failure Reason`, and `Result`. +- Frontend OCR branch evaluation is strip-only (word-pass + sparse scan); the 4-cell refine stage is removed from the active pipeline. +- ROI word-pass defaults to raw candidate input (`roiDeterministic.wordPassModes: ['raw']`); debug stage `6. OCR input candidate` mirrors this mode. +- `roiDeterministic.minWordPassHits` is `1`, but isolated edge-only single hits are rejected unless corroborated by non-edge evidence or very strong per-cell confidence. +- Edge-derived candidate generation is toggleable via `roiDeterministic.useEdgeCandidates` (default `true`) for controlled A/B experiments. +- Current local benchmark set has `15` images. +- Historical checkpoint comparison (March 2, 2026, fallback `OFF`, 14-image snapshot): + - `roi-rotaug-e30-640.pt` (default pinned): exact-match `0/14`, failure mix `ocr-no-digits` (7), `mismatch` (6), `no-detection` (1). + - `roi.pt` (challenger): exact-match `0/14`, failure mix `ocr-no-digits` (10), `mismatch` (4), `no-detection` (0). +- Automated diff workflow is available via `npm run benchmark:roi-diff` (recent artifacts: `output/roi-checkpoint-diff/20260303-194206-fallback-off/roi-diff-report.md`). +- Gated digit-classifier fallback is implemented in pipeline but remains disabled by default (`digitClassifier.enabled: false`). +- Historical fallback benchmark (March 2, 2026, 14-image snapshot): + - Fallback `OFF` (`output/roi-checkpoint-diff/20260302-083324-fallback-off`): baseline `mismatch` 6 / `ocr-no-digits` 7; challenger `mismatch` 4 / `ocr-no-digits` 10. + - Fallback `ON` (`output/roi-checkpoint-diff/20260302-083529-fallback-on`): baseline `mismatch` 10 / `ocr-no-digits` 3; challenger `mismatch` 13 / `ocr-no-digits` 1. + - Net: no exact-match gain (`0/14` stays `0/14`), with strong false-positive shift (`ocr-no-digits` -> `mismatch`), so fallback stays disabled. +- Promotion and rollback decisions should now use `MAE` from `roi-diff-report` as the primary signal, with exact-match and no-read as guardrails. +- ROI diff reports now include per-image selected metadata columns (`sourceLabel`, `method`, `preprocessMode`) and explicitly export the last stage `6. OCR input candidate` snapshot. + +## Next TODOs + +1. Keep `roi-rotaug-e30-640.pt` as default until a challenger beats it on end-to-end OCR metrics, not only detection presence. +2. Re-run `npm run benchmark:roi-diff` after each ROI challenger to track per-image movement (`Detected`, stage `5/6` snapshots, reject reason), then summarize deltas in notes/PR. +3. Tune strip preprocessing and candidate ranking for the current hard failures (`meter_07012020.JPEG`, `meter_02192026.JPEG`, `meter_02202026.JPEG`, `meter_02242026.JPEG`). +4. Keep classifier fallback disabled until it beats fallback-off on `MAE` while respecting exact-match and no-read guardrails; focus on stricter fallback acceptance/ranking before re-testing. +5. Enforce checkpoint promotion gates from docs: no MAE regression, no exact-match regression, no no-read regression, and no regression in `ocr-no-digits`. +6. Keep running both `npm run test:e2e` and UI `Run test set` before commits; include histogram deltas in commit/PR notes. +7. Medium-term: evaluate YOLO OBB ROI detection to reduce rotation/edge ambiguity; this requires OBB relabeling, retraining, and backend response/schema changes before frontend adoption. + +### OBB Notes (Re-verify Before Implementation) + +- OBB inference outputs rotated geometry (`xywhr`) and polygon corners. +- OBB training labels use corners format: `class x1 y1 x2 y2 x3 y3 x4 y4`. +- OBB angle handling has constraints (Ultralytics OBB uses angles in the `0-90` exclusive range). + +## Dataset Expansion Loop (`4/5/6/9`) + +1. Refresh capture planning: + - `cd backend && source .venv/bin/activate && python plan_digit_expansion.py --target-train-per-digit 12 --priority-digits 4,5,6,9` +2. Add labeled captures with QA previews. +3. Validate manifests after each dataset update: + - `cd backend && source .venv/bin/activate && python validate_digit_dataset.py` +4. Retrain classifier only after class coverage improves: + - `cd backend && source .venv/bin/activate && python train_digit_classifier.py --device cpu` +5. Keep classifier fallback disabled by default; only enable if benchmarked `MAE` improves without exact-match/no-read guardrail regressions. diff --git a/README.md b/README.md index 92537ab..c8571d2 100644 --- a/README.md +++ b/README.md @@ -2,14 +2,22 @@ Jarvis is a lightweight personal assistant web app. The first module helps you read a water meter photo, review the detected value, and draft an email in Gmail. +## Documentation + +- Docs index: [`docs/README.md`](./docs/README.md) +- OCR app logic flow: [`docs/app-logic.md`](./docs/app-logic.md) +- Backend API guide: [`docs/backend-api.md`](./docs/backend-api.md) +- OCR tuning playbook: [`docs/ocr-tuning-playbook.md`](./docs/ocr-tuning-playbook.md) + ## Features - Upload a meter photo and preview it. -- OCR the reading (manual override supported). +- OCR from a neural-ROI crop with conservative acceptance (unsupported OCR guesses are rejected to manual input). - Auto-fill an email draft with the current date in Italian format. - Open a Gmail draft or use a mailto fallback. +- Run a built-in OCR test set table with `Detected`, `Absolute Error`, and `Failure Reason` columns plus MAE/exact-match/no-read summary stats. ## Local Development -1. Install dependencies (none required beyond Python). +1. Ensure Python 3 and Node.js are installed. 2. Run the dev server: ```bash @@ -18,19 +26,119 @@ npm run serve Then open `http://localhost:8000`. +If you also want to run Playwright checks, install JS dependencies once: + +```bash +npm install +``` + +### Optional Neural ROI Backend (recommended) +You can run a Python backend that detects the meter digit window using a fine-tuned pretrained model. + +1. Open a second terminal and set up backend dependencies: + +```bash +cd backend +python3 -m venv .venv +source .venv/bin/activate +pip install -r requirements.txt +``` + +For CPU-only environments (for example Vercel), install: + +```bash +pip install -r requirements-cpu.txt +``` + + +2. Train/fine-tune a model (copies best checkpoint to `backend/models/roi.pt`): + +```bash +python train_roi.py \ + --data data/roi_dataset.yaml \ + --base-model yolov8n.pt \ + --rotation-angles 90,180,270,360 \ + --heavy-augment +``` + +The API default ROI checkpoint is pinned to `backend/models/roi-rotaug-e30-640.pt`. +To run with a newly trained checkpoint, set `ROI_MODEL_PATH` explicitly before starting the backend. +`train_roi.py` now enforces heavy augmentation + rotation expansion by default; weaker runs require explicit `--allow-no-augment-policy`. + +Optional: train the per-cell digit classifier checkpoint: + +```bash +python train_digit_classifier.py --device cpu +``` + +For dataset expansion/QA before retraining: + +```bash +python plan_digit_expansion.py --target-train-per-digit 12 --priority-digits 4,5,6,9 +python validate_digit_dataset.py +``` + +3. Start the API: + +```bash +uvicorn app:app --host 127.0.0.1 --port 8001 --reload +``` + +By default, the frontend calls `http://127.0.0.1:8001/roi/detect` and requires neural ROI detection before OCR. +The frontend can also call `http://127.0.0.1:8001/digit/predict-cells` when `OCR_CONFIG.digitClassifier.enabled` is set to `true`. +Check backend readiness with: + +```bash +curl -s http://127.0.0.1:8001/health +``` + +### E2E Tests + +Run Playwright checks for neural-ROI failure handling and OCR selection guard regressions: + +```bash +npm run test:e2e +``` + +Generate a per-image ROI checkpoint comparison report (`roi-rotaug-e30-640.pt` vs `roi.pt`) with stage `5/6` debug snapshots: + +```bash +npm run benchmark:roi-diff +``` + +Report artifacts are written under `output/roi-checkpoint-diff//`. +Per-image diff tables include selected OCR metadata (`sourceLabel`, `method`, `preprocessMode`) and stage `6` exports use the last `6. OCR input candidate` frame from each debug session. +To benchmark with digit-classifier fallback enabled (gated to `ocr-no-digits`), run: + +```bash +JARVIS_DIGIT_FALLBACK=1 npm run benchmark:roi-diff +``` + +CI runs these tests on every pull request and on pushes to `master`. + ## File Overview - `index.html`: UI layout. - `styles.css`: Styling. -- `app.js`: OCR + email draft logic. +- `app.js`: Thin entrypoint that imports `src/main.js`. +- `src/main.js`: UI orchestration and event wiring. +- `src/ocr/`: OCR pipeline and neural ROI integration. +- `src/testset/`: Manual OCR test-set runner. +- `backend/`: Optional FastAPI service for neural ROI and digit-classifier inference/training. - `AGENTS.md`: Contributor guide. - `assets/`: Static assets and example uploads. ## Notes - OCR runs fully in the browser using Tesseract.js. +- OCR now relies on neural ROI detection; if the backend is unavailable or ROI fails, the app asks for manual reading input. +- ROI word-pass defaults to raw strip input; stage `6. OCR input candidate` mirrors the configured OCR input mode. +- Edge-derived ROI strip candidates are enabled by default and can be toggled with `OCR_CONFIG.roiDeterministic.useEdgeCandidates`. +- Digit decoding can optionally use a backend classifier (`src/ocr/config.js` -> `digitClassifier.enabled`), which is `false` by default. +- The selection layer is fail-safe: isolated edge-only single hits are rejected unless independently corroborated. +- Use the UI `Run test set` action plus `npm run test:e2e` for OCR regressions before and after tuning. - The Gmail flow opens a draft; you always review and send manually. ## Asset Naming (Meter Images) - Use the EXIF `DateTimeOriginal` value as the source of truth for the acquisition date. - Rename files to `meter_mmddyyyy` (zero-padded) and keep the original extension. -- If multiple images share the same date, keep one as-is and add suffixes to the rest (e.g., `_b`, `_c`). +- If multiple images share the same date, keep one as-is and add numeric suffixes to the rest (e.g., `_1`, `_2`). - If EXIF is missing, prefer a known date from the filename or capture notes and document it. diff --git a/app.js b/app.js index 07dbe3f..2d70ce7 100644 --- a/app.js +++ b/app.js @@ -1,252 +1 @@ -const photoInput = document.getElementById('photo-input'); -const photoPreview = document.getElementById('photo-preview'); -const readBtn = document.getElementById('read-btn'); -const ocrStatus = document.getElementById('ocr-status'); -const readingInput = document.getElementById('reading-input'); -const dateInput = document.getElementById('date-input'); -const fromInput = document.getElementById('from-input'); -const toInput = document.getElementById('to-input'); -const subjectInput = document.getElementById('subject-input'); -const bodyInput = document.getElementById('body-input'); -const regenBtn = document.getElementById('regen-btn'); -const sendBtn = document.getElementById('send-btn'); -const mailtoLink = document.getElementById('mailto-link'); - -const DEFAULT_SUBJECT = 'Lettura acqua da F9C397'; -const CUSTOMER_DETAILS = { - name: 'Andrea Panizza', - address: 'Via delle cinque giornate 15 piano 1 e 1/2', - city: 'Firenze FI', - code: 'F9C397' -}; - -let currentPhotoFile = null; -let bodyTouched = false; -let subjectTouched = false; - -const formatItalianDate = (dateValue) => { - if (!dateValue) { - return ''; - } - const [year, month, day] = dateValue.split('-').map(Number); - if (!year || !month || !day) { - return ''; - } - const localDate = new Date(year, month - 1, day); - return new Intl.DateTimeFormat('it-IT').format(localDate); -}; - -const buildBodyTemplate = (dateDisplay, readingValue) => { - const safeReading = readingValue || '____'; - return [ - `Intestatario: ${CUSTOMER_DETAILS.name}`, - CUSTOMER_DETAILS.address, - CUSTOMER_DETAILS.city, - `Codice Utente: ${CUSTOMER_DETAILS.code}`, - `Data: ${dateDisplay}`, - `Lettura: ${safeReading}` - ].join('\n'); -}; - -const replaceLine = (body, label, value) => { - if (!body.trim()) { - return ''; - } - const lines = body.split('\n'); - const index = lines.findIndex((line) => line.trim().startsWith(label)); - const nextLine = `${label} ${value}`; - if (index >= 0) { - lines[index] = nextLine; - } else { - lines.push(nextLine); - } - return lines.join('\n'); -}; - -const extractReading = (text) => { - const matches = text.match(/\d{4,7}/g); - if (!matches || matches.length === 0) { - return ''; - } - const longestLength = Math.max(...matches.map((value) => value.length)); - const candidates = matches.filter((value) => value.length === longestLength); - return candidates[candidates.length - 1]; -}; - -const setStatus = (message) => { - ocrStatus.textContent = message; -}; - -const buildEmailDraft = () => { - return { - to: toInput.value.trim(), - subject: subjectInput.value.trim() || DEFAULT_SUBJECT, - body: bodyInput.value.trim() - }; -}; - -const updateMailLinks = () => { - const { to, subject, body } = buildEmailDraft(); - const gmailUrl = `https://mail.google.com/mail/?view=cm&fs=1&to=${encodeURIComponent(to)}&su=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`; - sendBtn.dataset.gmailUrl = gmailUrl; - mailtoLink.href = `mailto:${encodeURIComponent(to)}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`; -}; - -const updateSubject = ({ force = false } = {}) => { - if (force || !subjectTouched) { - subjectInput.value = DEFAULT_SUBJECT; - subjectTouched = false; - } - updateMailLinks(); -}; - -const updateBody = ({ force = false } = {}) => { - const dateDisplay = formatItalianDate(dateInput.value); - const readingValue = readingInput.value.trim(); - - if (force || !bodyTouched) { - bodyInput.value = buildBodyTemplate(dateDisplay, readingValue); - bodyTouched = false; - } else { - let updated = bodyInput.value; - updated = replaceLine(updated, 'Data:', dateDisplay); - updated = replaceLine(updated, 'Lettura:', readingValue || '____'); - bodyInput.value = updated || buildBodyTemplate(dateDisplay, readingValue); - } - - updateMailLinks(); -}; - -photoInput.addEventListener('click', () => { - photoInput.value = ''; -}); - -photoInput.addEventListener('change', () => { - const file = photoInput.files && photoInput.files[0]; - if (!file) { - currentPhotoFile = null; - photoPreview.innerHTML = '

No photo loaded yet.

'; - setStatus('Waiting for a photo.'); - return; - } - - currentPhotoFile = file; - const reader = new FileReader(); - reader.onload = () => { - photoPreview.innerHTML = ''; - const img = document.createElement('img'); - img.src = reader.result; - img.alt = 'Water meter preview'; - photoPreview.appendChild(img); - }; - reader.onerror = () => { - photoPreview.innerHTML = '

Preview unavailable.

'; - }; - reader.readAsDataURL(file); - setStatus('Photo ready. Click "Read meter".'); -}); - -readBtn.addEventListener('click', async () => { - if (!currentPhotoFile) { - setStatus('Upload a photo first.'); - return; - } - - if (!window.Tesseract) { - setStatus('OCR library not available. Check your connection and try again.'); - return; - } - - readBtn.disabled = true; - setStatus('Reading image...'); - - try { - const { data } = await Tesseract.recognize(currentPhotoFile, 'eng', { - tessedit_char_whitelist: '0123456789', - logger: (message) => { - if (message.status === 'recognizing text') { - const progress = Math.round(message.progress * 100); - setStatus(`Reading image ${progress}%`); - } else if (message.status) { - setStatus(message.status); - } - } - }); - - const detected = extractReading(data.text || ''); - if (detected) { - readingInput.value = detected; - setStatus(`Reading detected: ${detected}. Review if needed.`); - updateBody(); - } else { - setStatus('No clear reading detected. Enter it manually.'); - } - } catch (error) { - console.error(error); - setStatus('OCR failed. Enter the reading manually.'); - } finally { - readBtn.disabled = false; - } -}); - -readingInput.addEventListener('input', () => { - const sanitized = readingInput.value.replace(/\D/g, ''); - if (sanitized !== readingInput.value) { - readingInput.value = sanitized; - } - updateBody(); -}); - -readingInput.addEventListener('blur', () => { - updateBody(); -}); - -dateInput.addEventListener('change', () => { - updateBody(); -}); - -toInput.addEventListener('input', () => { - updateMailLinks(); -}); - -subjectInput.addEventListener('input', () => { - subjectTouched = true; - updateMailLinks(); -}); - -bodyInput.addEventListener('input', () => { - bodyTouched = true; - updateMailLinks(); -}); - -regenBtn.addEventListener('click', () => { - bodyTouched = false; - subjectTouched = false; - updateSubject({ force: true }); - updateBody({ force: true }); -}); - -sendBtn.addEventListener('click', () => { - if (!sendBtn.dataset.gmailUrl) { - updateMailLinks(); - } - window.open(sendBtn.dataset.gmailUrl, '_blank', 'noopener'); -}); - -const init = () => { - const now = new Date(); - const isoDate = [ - now.getFullYear(), - String(now.getMonth() + 1).padStart(2, '0'), - String(now.getDate()).padStart(2, '0') - ].join('-'); - - dateInput.value = isoDate; - fromInput.value = fromInput.value || 'andrea.panizza75@gmail.com'; - - updateSubject({ force: true }); - updateBody({ force: true }); - updateMailLinks(); -}; - -init(); +import './src/main.js'; diff --git a/assets/meter_readings.csv b/assets/meter_readings.csv new file mode 100644 index 0000000..a4b783d --- /dev/null +++ b/assets/meter_readings.csv @@ -0,0 +1,16 @@ +filename,value +meter_07012020.JPEG,1784 +meter_11112020.JPEG,1819 +meter_10092025.JPEG,2279 +meter_01122026.JPEG,2302 +meter_01132026.jpg,2302 +meter_01302026.JPEG,2307 +meter_02022026.JPEG,2308 +meter_02102026.JPEG,2310 +meter_02142026.JPEG,2311 +meter_02162026.JPEG,2312 +meter_02192026.JPEG,2312 +meter_02202026.JPEG,2313 +meter_02242026.JPEG,2314 +meter_02272026.JPEG,2315 +meter_03032026.JPEG,2316 diff --git a/backend/README.md b/backend/README.md new file mode 100644 index 0000000..44a2cfd --- /dev/null +++ b/backend/README.md @@ -0,0 +1,149 @@ +# ROI Backend + +Python service for neural ROI detection (digit window) plus optional per-cell digit-classifier inference. + +## 1) Install + +```bash +cd backend +python3 -m venv .venv +source .venv/bin/activate +pip install -r requirements.txt +``` + +For CPU-only environments (for example Vercel), install: + +```bash +pip install -r requirements-cpu.txt +``` + +## 2) Fine-tune on your dataset + +Prepare a YOLO dataset and YAML file. A template is available at `data/roi_dataset.example.yaml`. + +Expected labels: one class (`digit_window`) with normalized YOLO boxes. + +```bash +cd backend +source .venv/bin/activate +python train_roi.py \ + --data data/roi_dataset.yaml \ + --base-model yolov8n.pt \ + --rotation-angles 90,180,270,360 \ + --heavy-augment +``` + +The training script enforces this augmentation policy by default (heavy online augmentation + 90/180/270/360 train rotations). You can bypass it only for explicit ablations with `--allow-no-augment-policy`. + +```bash +python train_roi.py --data data/roi_dataset.yaml --base-model yolov8n.pt --allow-no-augment-policy --no-heavy-augment --rotation-angles 90,180 +``` + +After training, best weights are copied to `backend/models/roi.pt`. +The API default is pinned to `backend/models/roi-rotaug-e30-640.pt`; use `ROI_MODEL_PATH` to explicitly test/use `roi.pt` or another checkpoint. + +## Build a digit OCR dataset from ROI labels + +This creates: +- strip crops + sequence labels (`data/digit_dataset/strips`, `data/digit_dataset/strip_labels`) +- per-cell crops grouped by digit class (`data/digit_dataset/cells`) +- manifests and QA previews (`data/digit_dataset/manifests`, `data/digit_dataset/qa_previews`) + +```bash +cd backend +source .venv/bin/activate +python build_digit_dataset.py --clean +``` + +Validate manifest/file consistency and QA preview coverage: + +```bash +python validate_digit_dataset.py +``` + +Generate a prioritized capture checklist for underrepresented digits: + +```bash +python plan_digit_expansion.py --target-train-per-digit 12 --priority-digits 4,5,6,9 +``` + +## Train a dedicated digit classifier + +This trains a small per-cell CNN on real labeled sections (`data/digit_dataset/sections_labeled`) and writes: +- weights: `backend/models/digit_classifier.pt` +- training summary: `backend/runs/digit-classifier/digit_classifier_summary.json` + +```bash +cd backend +source .venv/bin/activate +python train_digit_classifier.py --device cpu +``` + +Optional: generate synthetic **train-only** sections from real train patches, then mix real + synthetic in training. +Val/test remain strictly real-only. + +```bash +cd backend +source .venv/bin/activate +python generate_synthetic_digit_dataset.py --clean --direct-per-real 6 --compose-window-count 180 +python train_digit_classifier.py \ + --device cpu \ + --synthetic-root data/digit_dataset/sections_synthetic \ + --synthetic-target-ratio 2.0 \ + --name digit-classifier-synth-v1 +``` + +## 3) Start the API + +```bash +cd backend +source .venv/bin/activate +uvicorn app:app --host 127.0.0.1 --port 8001 --reload +``` + +Readiness check: + +```bash +curl -s http://127.0.0.1:8001/health +``` + +## 4) Endpoints + +- `GET /health`: model readiness (`ready`, `roi_ready`, `digit_ready`) + effective model/device config. +- `POST /roi/detect`: multipart upload (`image`) and returns normalized bbox + confidence. +- `POST /digit/predict`: multipart upload (`image`) and returns the predicted digit + confidence. +- `POST /digit/predict-cells`: multipart upload (`images`, repeated field) for batch cell decoding. + +Frontend integration defaults: +- ROI detection path is `http://127.0.0.1:8001/roi/detect` and is required for OCR. +- Digit classifier path is `http://127.0.0.1:8001/digit/predict-cells` and is only used when `OCR_CONFIG.digitClassifier.enabled=true`. +- Frontend ROI OCR currently uses conservative acceptance guards (`minWordPassHits` plus edge-candidate corroboration/cell-verification gates) to avoid high-confidence false positives. + +## Environment Variables + +- `ROI_MODEL_PATH`: path to `.pt` weights (default: `backend/models/roi-rotaug-e30-640.pt`) +- `ROI_DEFAULT_CONFIDENCE`: detection confidence threshold (default: `0.05`) +- `ROI_DEFAULT_IOU`: NMS IoU threshold (default: `0.5`) +- `ROI_DEFAULT_IMGSZ`: inference size (default: `960`) +- `ROI_CLASS_INDEX`: optional class id filter +- `ROI_DEVICE`: inference device (default: `cpu`). + - Use `cpu` for CPU-only deploys (recommended on Vercel). + - Use `auto` to let Ultralytics choose. + - Use `0` or `cuda:0` to force GPU. +- `DIGIT_MODEL_PATH`: path to digit classifier checkpoint (default: `backend/models/digit_classifier.pt`) +- `DIGIT_DEVICE`: inference device for digit classifier (default follows `ROI_DEVICE`) +- `DIGIT_MIN_CONFIDENCE`: minimum accepted confidence for digit predictions (default: `0.0`) +- `DIGIT_TOP_K`: number of top classes returned by digit endpoints (default: `3`) + +## CPU-only vs GPU + +- CPU-only install (recommended for Vercel/serverless): + - `pip install -r requirements-cpu.txt` +- GPU-capable install: + - install a CUDA-enabled PyTorch build, then `pip install -r requirements.txt`. + +Training can also be pinned with `--device`: + +```bash +python train_roi.py --data data/roi_dataset.yaml --base-model yolov8n.pt --device cpu --rotation-angles 90,180,270,360 --heavy-augment +``` diff --git a/backend/__init__.py b/backend/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/backend/__init__.py @@ -0,0 +1 @@ + diff --git a/backend/app.py b/backend/app.py new file mode 100644 index 0000000..52c39ee --- /dev/null +++ b/backend/app.py @@ -0,0 +1,295 @@ +from __future__ import annotations + +import io +import os +from pathlib import Path + +import numpy as np +from fastapi import FastAPI, File, HTTPException, UploadFile +from fastapi.middleware.cors import CORSMiddleware +from PIL import Image, UnidentifiedImageError + +try: + from .detector import DetectorUnavailableError, RoiDetector +except ImportError: + from detector import DetectorUnavailableError, RoiDetector + +try: + from .digit_classifier import DigitClassifier, DigitClassifierUnavailableError +except ImportError: + from digit_classifier import DigitClassifier, DigitClassifierUnavailableError + + +def _env_float(name: str, default: float) -> float: + value = os.getenv(name) + if value is None: + return default + try: + return float(value) + except ValueError: + return default + + +def _env_int(name: str, default: int) -> int: + value = os.getenv(name) + if value is None: + return default + try: + return int(value) + except ValueError: + return default + + +BASE_DIR = Path(__file__).resolve().parent +DEFAULT_MODEL_PATH = BASE_DIR / "models" / "roi-rotaug-e30-640.pt" +MODEL_PATH_ENV = os.getenv("ROI_MODEL_PATH") +MODEL_PATH = Path(MODEL_PATH_ENV or str(DEFAULT_MODEL_PATH)).expanduser().resolve() +MODEL_SOURCE = "env" if MODEL_PATH_ENV else "default" +DEFAULT_CONFIDENCE = _env_float("ROI_DEFAULT_CONFIDENCE", 0.05) +DEFAULT_IOU = _env_float("ROI_DEFAULT_IOU", 0.5) +DEFAULT_IMGSZ = _env_int("ROI_DEFAULT_IMGSZ", 960) +DEFAULT_DIGIT_MODEL_PATH = BASE_DIR / "models" / "digit_classifier.pt" +DIGIT_MODEL_PATH = Path(os.getenv("DIGIT_MODEL_PATH", str(DEFAULT_DIGIT_MODEL_PATH))).expanduser().resolve() +DIGIT_MIN_CONFIDENCE = _env_float("DIGIT_MIN_CONFIDENCE", 0.0) +DIGIT_TOP_K = _env_int("DIGIT_TOP_K", 3) +CLASS_INDEX = os.getenv("ROI_CLASS_INDEX") +DEVICE_RAW = os.getenv("ROI_DEVICE", "cpu").strip() +if not DEVICE_RAW: + DEVICE_RAW = "cpu" +DEVICE = None if DEVICE_RAW.lower() == "auto" else DEVICE_RAW +DIGIT_DEVICE_RAW = os.getenv("DIGIT_DEVICE", DEVICE_RAW).strip() +if not DIGIT_DEVICE_RAW: + DIGIT_DEVICE_RAW = DEVICE_RAW +DIGIT_DEVICE = None if DIGIT_DEVICE_RAW.lower() == "auto" else DIGIT_DEVICE_RAW + +if CLASS_INDEX is None: + CLASS_INDEX_VALUE = None +else: + try: + CLASS_INDEX_VALUE = int(CLASS_INDEX) + except ValueError: + CLASS_INDEX_VALUE = None + +app = FastAPI( + title="Jarvis ROI API", + description="Detects water-meter digit window ROI using a fine-tuned pretrained detector.", + version="0.1.0" +) + +app.add_middleware( + CORSMiddleware, + allow_origins=[ + "http://localhost:8000", + "http://127.0.0.1:8000" + ], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"] +) + +_detector: RoiDetector | None = None +_detector_error: str | None = None +_digit_classifier: DigitClassifier | None = None +_digit_classifier_error: str | None = None + + +def get_detector() -> RoiDetector: + global _detector, _detector_error + if _detector: + return _detector + try: + _detector = RoiDetector(MODEL_PATH, class_index=CLASS_INDEX_VALUE, device=DEVICE) + _detector_error = None + return _detector + except DetectorUnavailableError as error: + _detector_error = str(error) + raise + + +def get_digit_classifier() -> DigitClassifier: + global _digit_classifier, _digit_classifier_error + if _digit_classifier: + return _digit_classifier + try: + _digit_classifier = DigitClassifier(DIGIT_MODEL_PATH, device=DIGIT_DEVICE) + _digit_classifier_error = None + return _digit_classifier + except DigitClassifierUnavailableError as error: + _digit_classifier_error = str(error) + raise + + +def _load_rgb_image(file_bytes: bytes) -> np.ndarray: + try: + with Image.open(io.BytesIO(file_bytes)) as image: + rgb = image.convert("RGB") + return np.array(rgb) + except UnidentifiedImageError as error: + raise HTTPException(status_code=400, detail="Unsupported image format.") from error + +@app.get("/health") +def health() -> dict: + roi_model_exists = MODEL_PATH.exists() + digit_model_exists = DIGIT_MODEL_PATH.exists() + roi_ready = False + digit_ready = False + roi_error = _detector_error + digit_error = _digit_classifier_error + try: + get_detector() + roi_ready = True + roi_error = None + except DetectorUnavailableError as detector_error: + roi_error = str(detector_error) + try: + get_digit_classifier() + digit_ready = True + digit_error = None + except DigitClassifierUnavailableError as classifier_error: + digit_error = str(classifier_error) + + return { + "ok": True, + "ready": roi_ready, + "roi_ready": roi_ready, + "digit_ready": digit_ready, + "model_path": str(MODEL_PATH), + "model_source": MODEL_SOURCE, + "default_model_path": str(DEFAULT_MODEL_PATH), + "model_exists": roi_model_exists, + "digit_model_path": str(DIGIT_MODEL_PATH), + "digit_model_exists": digit_model_exists, + "device": DEVICE_RAW if roi_ready else (DEVICE or "auto"), + "digit_device": DIGIT_DEVICE_RAW if digit_ready else (DIGIT_DEVICE or "auto"), + "default_confidence": DEFAULT_CONFIDENCE, + "default_iou": DEFAULT_IOU, + "default_imgsz": DEFAULT_IMGSZ, + "digit_min_confidence": DIGIT_MIN_CONFIDENCE, + "digit_top_k": DIGIT_TOP_K, + "error": roi_error, + "digit_error": digit_error + } + + +@app.post("/roi/detect") +async def detect_roi(image: UploadFile = File(...)) -> dict: + try: + detector = get_detector() + except DetectorUnavailableError as error: + raise HTTPException(status_code=503, detail=str(error)) from error + + file_bytes = await image.read() + if not file_bytes: + raise HTTPException(status_code=400, detail="Empty upload.") + + image_rgb = _load_rgb_image(file_bytes) + height, width = image_rgb.shape[:2] + detection = detector.detect( + image_rgb=image_rgb, + conf=DEFAULT_CONFIDENCE, + iou=DEFAULT_IOU, + imgsz=DEFAULT_IMGSZ + ) + + if not detection: + return { + "ok": False, + "model": detector.model_name, + "device": detector.device_name, + "bbox_norm": None, + "confidence": 0.0, + "class_id": None, + "class_name": None, + "image_size": {"width": width, "height": height} + } + + return { + "ok": True, + "model": detector.model_name, + "device": detector.device_name, + "bbox_norm": detection.to_normalized_bbox(width=width, height=height), + "confidence": detection.confidence, + "class_id": detection.class_id, + "class_name": detection.class_name, + "image_size": {"width": width, "height": height} + } + + +@app.post("/digit/predict") +async def predict_digit(image: UploadFile = File(...)) -> dict: + try: + classifier = get_digit_classifier() + except DigitClassifierUnavailableError as error: + raise HTTPException(status_code=503, detail=str(error)) from error + + file_bytes = await image.read() + if not file_bytes: + raise HTTPException(status_code=400, detail="Empty upload.") + + image_rgb = _load_rgb_image(file_bytes) + prediction = classifier.predict(image_rgb=image_rgb, top_k=DIGIT_TOP_K) + accepted = prediction.confidence >= DIGIT_MIN_CONFIDENCE + + return { + "ok": accepted, + "accepted": accepted, + "model": classifier.model_name, + "device": classifier.device_name, + "digit": prediction.digit if accepted else None, + "predicted_digit": prediction.digit, + "confidence": prediction.confidence, + "min_confidence": DIGIT_MIN_CONFIDENCE, + "top_k": prediction.top_k + } + + +@app.post("/digit/predict-cells") +async def predict_digit_cells(images: list[UploadFile] = File(...)) -> dict: + try: + classifier = get_digit_classifier() + except DigitClassifierUnavailableError as error: + raise HTTPException(status_code=503, detail=str(error)) from error + + if not images: + raise HTTPException(status_code=400, detail="At least one image is required.") + if len(images) > 16: + raise HTTPException(status_code=400, detail="Too many images; limit is 16.") + + predictions = [] + accepted_count = 0 + for upload in images: + file_bytes = await upload.read() + if not file_bytes: + predictions.append({ + "ok": False, + "accepted": False, + "digit": None, + "predicted_digit": None, + "confidence": 0.0, + "error": "empty-upload" + }) + continue + + image_rgb = _load_rgb_image(file_bytes) + prediction = classifier.predict(image_rgb=image_rgb, top_k=DIGIT_TOP_K) + accepted = prediction.confidence >= DIGIT_MIN_CONFIDENCE + if accepted: + accepted_count += 1 + predictions.append({ + "ok": accepted, + "accepted": accepted, + "digit": prediction.digit if accepted else None, + "predicted_digit": prediction.digit, + "confidence": prediction.confidence, + "top_k": prediction.top_k + }) + + return { + "ok": True, + "model": classifier.model_name, + "device": classifier.device_name, + "min_confidence": DIGIT_MIN_CONFIDENCE, + "accepted_count": accepted_count, + "total": len(images), + "predictions": predictions + } diff --git a/backend/build_digit_dataset.py b/backend/build_digit_dataset.py new file mode 100644 index 0000000..33ef8b8 --- /dev/null +++ b/backend/build_digit_dataset.py @@ -0,0 +1,365 @@ +from __future__ import annotations + +import argparse +import csv +import json +import shutil +from dataclasses import dataclass +from pathlib import Path + +from PIL import Image, ImageDraw + + +@dataclass +class ExportRow: + split: str + filename: str + strip_path: Path + label_path: Path + label: str + roi_x: int + roi_y: int + roi_w: int + roi_h: int + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description="Build digit OCR datasets (strip-level + per-cell) from ROI labels and readings CSV." + ) + parser.add_argument( + "--csv", + default="../assets/meter_readings.csv", + help="CSV with filename,value rows (default resolves from backend/)." + ) + parser.add_argument( + "--roi-dataset-dir", + default="data/roi_dataset", + help="ROI dataset root containing images/ and labels/." + ) + parser.add_argument( + "--out-dir", + default="data/digit_dataset", + help="Output dataset root." + ) + parser.add_argument( + "--cell-count", + type=int, + default=4, + help="Number of cells (default: 4)." + ) + parser.add_argument( + "--cell-overlap", + type=float, + default=0.03, + help="Horizontal overlap ratio per cell (default: 0.03)." + ) + parser.add_argument( + "--expand-x", + type=float, + default=0.0, + help="Expand ROI horizontally by this ratio of ROI width on each side." + ) + parser.add_argument( + "--expand-y", + type=float, + default=0.0, + help="Expand ROI vertically by this ratio of ROI height on each side." + ) + parser.add_argument( + "--clean", + action="store_true", + help="Delete output directory before writing." + ) + return parser.parse_args() + + +def resolve(base_dir: Path, value: str) -> Path: + path = Path(value) + if path.is_absolute(): + return path + return (base_dir / path).resolve() + + +def clamp(value: float, low: float, high: float) -> float: + return max(low, min(high, value)) + + +def read_value_map(csv_path: Path) -> dict[str, str]: + mapping: dict[str, str] = {} + with csv_path.open("r", encoding="utf-8") as handle: + reader = csv.DictReader(handle) + for row in reader: + filename = (row.get("filename") or "").strip() + value = (row.get("value") or "").strip() + if filename and value: + mapping[filename] = value + return mapping + + +def load_roi_label(label_path: Path) -> tuple[float, float, float, float]: + raw = label_path.read_text(encoding="utf-8").strip().splitlines() + for line in raw: + parts = line.strip().split() + if len(parts) != 5: + continue + _, x_center, y_center, width, height = parts + return float(x_center), float(y_center), float(width), float(height) + raise ValueError(f"No valid YOLO row in label file: {label_path}") + + +def yolo_to_pixel_rect( + image_width: int, + image_height: int, + x_center: float, + y_center: float, + width: float, + height: float, + expand_x: float, + expand_y: float +) -> tuple[int, int, int, int]: + x = (x_center - width * 0.5) * image_width + y = (y_center - height * 0.5) * image_height + w = width * image_width + h = height * image_height + + x -= w * expand_x + y -= h * expand_y + w *= (1 + 2 * expand_x) + h *= (1 + 2 * expand_y) + + x0 = int(round(clamp(x, 0, image_width - 1))) + y0 = int(round(clamp(y, 0, image_height - 1))) + x1 = int(round(clamp(x + w, x0 + 1, image_width))) + y1 = int(round(clamp(y + h, y0 + 1, image_height))) + return x0, y0, x1 - x0, y1 - y0 + + +def split_cells(strip: Image.Image, count: int, overlap_ratio: float) -> list[tuple[int, int, int, int]]: + width, height = strip.size + cell_width = width / count + overlap = cell_width * overlap_ratio + boxes: list[tuple[int, int, int, int]] = [] + for index in range(count): + x0 = int(round(clamp(cell_width * index - overlap, 0, width - 1))) + x1 = int(round(clamp(cell_width * (index + 1) + overlap, x0 + 1, width))) + boxes.append((x0, 0, x1, height)) + return boxes + + +def ensure_dirs(root: Path) -> None: + (root / "strips").mkdir(parents=True, exist_ok=True) + (root / "strip_labels").mkdir(parents=True, exist_ok=True) + (root / "cells").mkdir(parents=True, exist_ok=True) + (root / "qa_previews").mkdir(parents=True, exist_ok=True) + (root / "manifests").mkdir(parents=True, exist_ok=True) + for split in ("train", "val", "test"): + (root / "strips" / split).mkdir(parents=True, exist_ok=True) + (root / "strip_labels" / split).mkdir(parents=True, exist_ok=True) + (root / "qa_previews" / split).mkdir(parents=True, exist_ok=True) + for digit in range(10): + (root / "cells" / split / str(digit)).mkdir(parents=True, exist_ok=True) + + +def write_qa_preview( + source: Image.Image, + out_path: Path, + roi_rect: tuple[int, int, int, int], + cell_boxes: list[tuple[int, int, int, int]] +) -> None: + preview = source.convert("RGB").copy() + draw = ImageDraw.Draw(preview) + x, y, w, h = roi_rect + line_width = max(2, source.width // 350) + draw.rectangle((x, y, x + w, y + h), outline=(33, 188, 255), width=line_width) + for box in cell_boxes: + cx0, cy0, cx1, cy1 = box + draw.line((x + cx0, y + cy0, x + cx0, y + cy1), fill=(255, 204, 0), width=max(1, line_width - 1)) + draw.line((x + cx1, y + cy0, x + cx1, y + cy1), fill=(255, 204, 0), width=max(1, line_width - 1)) + out_path.parent.mkdir(parents=True, exist_ok=True) + preview.save(out_path, quality=92) + + +def write_csv(path: Path, rows: list[dict[str, str]], headers: list[str]) -> None: + with path.open("w", encoding="utf-8", newline="") as handle: + writer = csv.DictWriter(handle, fieldnames=headers) + writer.writeheader() + writer.writerows(rows) + + +def main() -> None: + args = parse_args() + base_dir = Path(__file__).resolve().parent + + csv_path = resolve(base_dir, args.csv) + roi_dataset_dir = resolve(base_dir, args.roi_dataset_dir) + out_dir = resolve(base_dir, args.out_dir) + + if not csv_path.exists(): + raise FileNotFoundError(f"CSV not found: {csv_path}") + if not roi_dataset_dir.exists(): + raise FileNotFoundError(f"ROI dataset dir not found: {roi_dataset_dir}") + if args.cell_count <= 0: + raise ValueError("--cell-count must be positive.") + if args.cell_overlap < 0: + raise ValueError("--cell-overlap must be >= 0.") + + value_map = read_value_map(csv_path) + if not value_map: + raise RuntimeError(f"No readings found in CSV: {csv_path}") + + if args.clean and out_dir.exists(): + shutil.rmtree(out_dir) + ensure_dirs(out_dir) + + strip_manifest_rows: list[dict[str, str]] = [] + cell_manifest_rows: list[dict[str, str]] = [] + exported_rows: list[ExportRow] = [] + + split_counts = {"train": 0, "val": 0, "test": 0} + cell_counts_by_split = {"train": 0, "val": 0, "test": 0} + cell_counts_by_digit = {str(digit): 0 for digit in range(10)} + skipped: list[dict[str, str]] = [] + + for split in ("train", "val", "test"): + image_dir = roi_dataset_dir / "images" / split + label_dir = roi_dataset_dir / "labels" / split + if not image_dir.exists(): + continue + for image_path in sorted(image_dir.iterdir()): + if not image_path.is_file(): + continue + if image_path.suffix.lower() not in {".jpg", ".jpeg", ".png"}: + continue + + filename = image_path.name + reading = value_map.get(filename) + if not reading: + skipped.append({"filename": filename, "reason": "missing-reading"}) + continue + if len(reading) != args.cell_count or not reading.isdigit(): + skipped.append({"filename": filename, "reason": f"unsupported-reading:{reading}"}) + continue + + label_path = label_dir / f"{image_path.stem}.txt" + if not label_path.exists(): + skipped.append({"filename": filename, "reason": "missing-roi-label"}) + continue + + x_center, y_center, width_norm, height_norm = load_roi_label(label_path) + + with Image.open(image_path) as source: + source_rgb = source.convert("RGB") + roi_x, roi_y, roi_w, roi_h = yolo_to_pixel_rect( + image_width=source_rgb.width, + image_height=source_rgb.height, + x_center=x_center, + y_center=y_center, + width=width_norm, + height=height_norm, + expand_x=args.expand_x, + expand_y=args.expand_y + ) + strip = source_rgb.crop((roi_x, roi_y, roi_x + roi_w, roi_y + roi_h)) + + strip_path = out_dir / "strips" / split / f"{image_path.stem}.png" + label_target = out_dir / "strip_labels" / split / f"{image_path.stem}.txt" + strip.save(strip_path) + label_target.write_text(f"{reading}\n", encoding="utf-8") + + strip_manifest_rows.append({ + "split": split, + "filename": filename, + "strip_path": str(strip_path.relative_to(out_dir)), + "label_path": str(label_target.relative_to(out_dir)), + "label": reading, + "roi_x": str(roi_x), + "roi_y": str(roi_y), + "roi_w": str(roi_w), + "roi_h": str(roi_h) + }) + + cell_boxes = split_cells(strip, args.cell_count, args.cell_overlap) + for cell_index, cell_box in enumerate(cell_boxes): + digit_label = reading[cell_index] + cell = strip.crop(cell_box) + cell_name = f"{image_path.stem}_c{cell_index}_{digit_label}.png" + cell_path = out_dir / "cells" / split / digit_label / cell_name + cell.save(cell_path) + cell_manifest_rows.append({ + "split": split, + "filename": filename, + "cell_index": str(cell_index), + "digit": digit_label, + "cell_path": str(cell_path.relative_to(out_dir)) + }) + cell_counts_by_split[split] += 1 + cell_counts_by_digit[digit_label] += 1 + + qa_path = out_dir / "qa_previews" / split / f"{image_path.stem}_qa.jpg" + write_qa_preview(source_rgb, qa_path, (roi_x, roi_y, roi_w, roi_h), cell_boxes) + + exported_rows.append( + ExportRow( + split=split, + filename=filename, + strip_path=strip_path, + label_path=label_target, + label=reading, + roi_x=roi_x, + roi_y=roi_y, + roi_w=roi_w, + roi_h=roi_h + ) + ) + split_counts[split] += 1 + + write_csv( + out_dir / "manifests" / "strips.csv", + strip_manifest_rows, + ["split", "filename", "strip_path", "label_path", "label", "roi_x", "roi_y", "roi_w", "roi_h"] + ) + write_csv( + out_dir / "manifests" / "cells.csv", + cell_manifest_rows, + ["split", "filename", "cell_index", "digit", "cell_path"] + ) + write_csv( + out_dir / "manifests" / "skipped.csv", + skipped, + ["filename", "reason"] + ) + + summary = { + "rows_exported": len(exported_rows), + "rows_skipped": len(skipped), + "splits": split_counts, + "cells_per_split": cell_counts_by_split, + "cells_per_digit": cell_counts_by_digit, + "cell_count": args.cell_count, + "cell_overlap": args.cell_overlap, + "expand_x": args.expand_x, + "expand_y": args.expand_y, + "csv": str(csv_path), + "roi_dataset_dir": str(roi_dataset_dir), + "out_dir": str(out_dir) + } + (out_dir / "manifests" / "summary.json").write_text(json.dumps(summary, indent=2), encoding="utf-8") + + print(f"Digit dataset exported: {out_dir}") + print(f"Rows: {summary['rows_exported']} (skipped={summary['rows_skipped']})") + print( + "Split rows: " + f"train={split_counts['train']} val={split_counts['val']} test={split_counts['test']}" + ) + print( + "Cells: " + f"train={cell_counts_by_split['train']} " + f"val={cell_counts_by_split['val']} " + f"test={cell_counts_by_split['test']}" + ) + print(f"Manifests: {out_dir / 'manifests'}") + + +if __name__ == "__main__": + main() diff --git a/backend/build_roi_dataset.py b/backend/build_roi_dataset.py new file mode 100644 index 0000000..9fb7a56 --- /dev/null +++ b/backend/build_roi_dataset.py @@ -0,0 +1,197 @@ +from __future__ import annotations + +import argparse +import csv +import json +import shutil +from pathlib import Path + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description="Build a YOLO dataset for digit_window ROI from assets + ROI manifest." + ) + parser.add_argument( + "--csv", + default="../assets/meter_readings.csv", + help="Path to CSV with filename,value rows (relative to backend/ by default)." + ) + parser.add_argument( + "--roi-json", + required=True, + help="Path to ROI JSON list (from browser extraction)." + ) + parser.add_argument( + "--assets-dir", + default="../assets", + help="Directory containing source images (relative to backend/ by default)." + ) + parser.add_argument( + "--out-dir", + default="data/roi_dataset", + help="Output dataset root directory (relative to backend/ by default)." + ) + parser.add_argument( + "--preview-dir", + default="data/roi_dataset/previews", + help="Directory for visual QA previews (relative to backend/ by default)." + ) + return parser.parse_args() + + +def resolve(base_dir: Path, value: str) -> Path: + path = Path(value) + if path.is_absolute(): + return path + return (base_dir / path).resolve() + + +def read_rows(csv_path: Path) -> list[dict[str, str]]: + rows: list[dict[str, str]] = [] + with csv_path.open("r", encoding="utf-8") as handle: + reader = csv.DictReader(handle) + for row in reader: + filename = (row.get("filename") or "").strip() + value = (row.get("value") or "").strip() + if filename and value: + rows.append({"filename": filename, "value": value}) + return rows + + +def read_roi_map(roi_json_path: Path) -> dict[str, dict]: + payload = json.loads(roi_json_path.read_text(encoding="utf-8")) + roi_map: dict[str, dict] = {} + for item in payload: + filename = item.get("filename") + rect_norm = item.get("rectNorm") + if not filename or not isinstance(rect_norm, dict): + continue + roi_map[str(filename)] = rect_norm + return roi_map + + +def clamp01(value: float) -> float: + return max(0.0, min(1.0, value)) + + +def normalize_yolo(rect_norm: dict) -> tuple[float, float, float, float]: + x = float(rect_norm.get("x", 0)) + y = float(rect_norm.get("y", 0)) + width = float(rect_norm.get("width", 0)) + height = float(rect_norm.get("height", 0)) + + x = clamp01(x) + y = clamp01(y) + width = clamp01(width) + height = clamp01(height) + + if width <= 0 or height <= 0: + raise ValueError("Invalid ROI width/height in manifest.") + + if x + width > 1: + width = max(0.001, 1 - x) + if y + height > 1: + height = max(0.001, 1 - y) + + xc = clamp01(x + width * 0.5) + yc = clamp01(y + height * 0.5) + return xc, yc, width, height + + +def split_for_index(index: int, total: int) -> str: + test_count = 1 if total >= 3 else 0 + val_count = 1 if total >= 4 else 0 + train_count = max(1, total - val_count - test_count) + + if index < train_count: + return "train" + if index < train_count + val_count: + return "val" + return "test" + + +def ensure_dataset_dirs(root: Path) -> None: + for split in ("train", "val", "test"): + (root / "images" / split).mkdir(parents=True, exist_ok=True) + (root / "labels" / split).mkdir(parents=True, exist_ok=True) + + +def write_preview(preview_path: Path, image_path: Path, rect_norm: dict) -> None: + try: + from PIL import Image, ImageDraw + except ImportError: + return + + with Image.open(image_path) as image: + width, height = image.size + x = clamp01(float(rect_norm.get("x", 0))) * width + y = clamp01(float(rect_norm.get("y", 0))) * height + w = clamp01(float(rect_norm.get("width", 0))) * width + h = clamp01(float(rect_norm.get("height", 0))) * height + draw = ImageDraw.Draw(image) + draw.rectangle((x, y, x + w, y + h), outline=(0, 255, 255), width=max(2, width // 350)) + preview_path.parent.mkdir(parents=True, exist_ok=True) + image.save(preview_path) + + +def main() -> None: + args = parse_args() + base_dir = Path(__file__).resolve().parent + + csv_path = resolve(base_dir, args.csv) + roi_json_path = resolve(base_dir, args.roi_json) + assets_dir = resolve(base_dir, args.assets_dir) + out_dir = resolve(base_dir, args.out_dir) + preview_dir = resolve(base_dir, args.preview_dir) + + if not csv_path.exists(): + raise FileNotFoundError(f"CSV not found: {csv_path}") + if not roi_json_path.exists(): + raise FileNotFoundError(f"ROI manifest not found: {roi_json_path}") + if not assets_dir.exists(): + raise FileNotFoundError(f"Assets dir not found: {assets_dir}") + + rows = read_rows(csv_path) + if not rows: + raise RuntimeError(f"No dataset rows found in CSV: {csv_path}") + + roi_map = read_roi_map(roi_json_path) + ensure_dataset_dirs(out_dir) + + created = [] + for index, row in enumerate(rows): + filename = row["filename"] + source_image = assets_dir / filename + if not source_image.exists(): + raise FileNotFoundError(f"Missing source image: {source_image}") + if filename not in roi_map: + raise KeyError(f"Missing ROI entry for {filename} in {roi_json_path}") + + split = split_for_index(index, len(rows)) + target_image = out_dir / "images" / split / filename + target_label = out_dir / "labels" / split / f"{Path(filename).stem}.txt" + + shutil.copy2(source_image, target_image) + + xc, yc, width, height = normalize_yolo(roi_map[filename]) + target_label.write_text(f"0 {xc:.6f} {yc:.6f} {width:.6f} {height:.6f}\n", encoding="utf-8") + + preview_path = preview_dir / f"{Path(filename).stem}_bbox.jpg" + write_preview(preview_path, source_image, roi_map[filename]) + created.append((filename, split, target_image, target_label, preview_path)) + + manifest_target = out_dir / "roi_boxes.json" + shutil.copy2(roi_json_path, manifest_target) + + split_counts = {"train": 0, "val": 0, "test": 0} + for _, split, *_ in created: + split_counts[split] += 1 + + print(f"Built ROI dataset at: {out_dir}") + print(f"Rows: {len(created)} (train={split_counts['train']}, val={split_counts['val']}, test={split_counts['test']})") + print(f"ROI manifest copied to: {manifest_target}") + print(f"Preview images: {preview_dir}") + + +if __name__ == "__main__": + main() diff --git a/backend/data/digit_dataset/manifests/canonical_windows.csv b/backend/data/digit_dataset/manifests/canonical_windows.csv new file mode 100644 index 0000000..9fc43a0 --- /dev/null +++ b/backend/data/digit_dataset/manifests/canonical_windows.csv @@ -0,0 +1,16 @@ +split,filename,reading,source_window_path,canonical_window_path,source_width,source_height,canonical_width,canonical_height,source_major_axis,canonical_major_axis,axis_rotation,direction_flip,direction_source,applied_rotation,primary_lowerness,flipped_lowerness +train,meter_01122026.JPEG,2302,windows/train/meter_01122026.png,windows_canonical/train/meter_01122026.png,94,268,268,94,y,x,270,0,default,270,0.487746,0.512254 +train,meter_01132026.jpg,2302,windows/train/meter_01132026.png,windows_canonical/train/meter_01132026.png,526,194,526,194,x,x,0,0,default,0,0.483826,0.516174 +train,meter_01302026.JPEG,2307,windows/train/meter_01302026.png,windows_canonical/train/meter_01302026.png,107,297,297,107,y,x,270,0,default,270,0.496398,0.503602 +train,meter_02162026.JPEG,2312,windows/train/meter_02162026.png,windows_canonical/train/meter_02162026.png,269,102,269,102,x,x,0,1,override,180,0.506975,0.493025 +train,meter_02192026.JPEG,2312,windows/train/meter_02192026.png,windows_canonical/train/meter_02192026.png,111,282,282,111,y,x,270,0,default,270,0.488476,0.511524 +train,meter_02202026.JPEG,2313,windows/train/meter_02202026.png,windows_canonical/train/meter_02202026.png,74,191,191,74,y,x,270,0,default,270,0.483687,0.516313 +train,meter_02242026.JPEG,2314,windows/train/meter_02242026.png,windows_canonical/train/meter_02242026.png,115,288,288,115,y,x,270,0,default,270,0.482225,0.517775 +train,meter_02272026.JPEG,2315,windows/train/meter_02272026.png,windows_canonical/train/meter_02272026.png,118,312,312,118,y,x,270,0,default,270,0.480342,0.519658 +train,meter_03032026.JPEG,2316,windows/train/meter_03032026.png,windows_canonical/train/meter_03032026.png,97,286,286,97,y,x,270,0,default,270,0.487200,0.512800 +train,meter_07012020.JPEG,1784,windows/train/meter_07012020.png,windows_canonical/train/meter_07012020.png,221,75,221,75,x,x,0,1,override,180,0.508502,0.491498 +train,meter_10092025.JPEG,2279,windows/train/meter_10092025.png,windows_canonical/train/meter_10092025.png,115,282,282,115,y,x,270,0,default,270,0.478585,0.521415 +train,meter_11112020.JPEG,1819,windows/train/meter_11112020.png,windows_canonical/train/meter_11112020.png,420,132,420,132,x,x,0,0,default,0,0.483978,0.516022 +val,meter_02022026.JPEG,2308,windows/val/meter_02022026.png,windows_canonical/val/meter_02022026.png,62,209,209,62,y,x,270,0,default,270,0.496436,0.503564 +val,meter_02142026.JPEG,2311,windows/val/meter_02142026.png,windows_canonical/val/meter_02142026.png,97,241,241,97,y,x,270,0,default,270,0.476736,0.523264 +test,meter_02102026.JPEG,2310,windows/test/meter_02102026.png,windows_canonical/test/meter_02102026.png,99,293,293,99,y,x,270,0,default,270,0.489590,0.510410 diff --git a/backend/data/digit_dataset/manifests/direction_overrides.csv b/backend/data/digit_dataset/manifests/direction_overrides.csv new file mode 100644 index 0000000..e40b99a --- /dev/null +++ b/backend/data/digit_dataset/manifests/direction_overrides.csv @@ -0,0 +1,3 @@ +filename,flip180 +meter_07012020.JPEG,1 +meter_02162026.JPEG,1 diff --git a/backend/data/digit_dataset/manifests/section_labels.csv b/backend/data/digit_dataset/manifests/section_labels.csv new file mode 100644 index 0000000..15304aa --- /dev/null +++ b/backend/data/digit_dataset/manifests/section_labels.csv @@ -0,0 +1,61 @@ +split,filename,reading,section_index,digit,section_path,labeled_path,source_window_path,canonical_window_path +train,meter_01122026.JPEG,2302,0,2,sections/train/meter_01122026_s0.png,sections_labeled/train/2/meter_01122026_s0_d2.png,windows/train/meter_01122026.png,windows_canonical/train/meter_01122026.png +train,meter_01122026.JPEG,2302,1,3,sections/train/meter_01122026_s1.png,sections_labeled/train/3/meter_01122026_s1_d3.png,windows/train/meter_01122026.png,windows_canonical/train/meter_01122026.png +train,meter_01122026.JPEG,2302,2,0,sections/train/meter_01122026_s2.png,sections_labeled/train/0/meter_01122026_s2_d0.png,windows/train/meter_01122026.png,windows_canonical/train/meter_01122026.png +train,meter_01122026.JPEG,2302,3,2,sections/train/meter_01122026_s3.png,sections_labeled/train/2/meter_01122026_s3_d2.png,windows/train/meter_01122026.png,windows_canonical/train/meter_01122026.png +train,meter_01132026.jpg,2302,0,2,sections/train/meter_01132026_s0.png,sections_labeled/train/2/meter_01132026_s0_d2.png,windows/train/meter_01132026.png,windows_canonical/train/meter_01132026.png +train,meter_01132026.jpg,2302,1,3,sections/train/meter_01132026_s1.png,sections_labeled/train/3/meter_01132026_s1_d3.png,windows/train/meter_01132026.png,windows_canonical/train/meter_01132026.png +train,meter_01132026.jpg,2302,2,0,sections/train/meter_01132026_s2.png,sections_labeled/train/0/meter_01132026_s2_d0.png,windows/train/meter_01132026.png,windows_canonical/train/meter_01132026.png +train,meter_01132026.jpg,2302,3,2,sections/train/meter_01132026_s3.png,sections_labeled/train/2/meter_01132026_s3_d2.png,windows/train/meter_01132026.png,windows_canonical/train/meter_01132026.png +train,meter_01302026.JPEG,2307,0,2,sections/train/meter_01302026_s0.png,sections_labeled/train/2/meter_01302026_s0_d2.png,windows/train/meter_01302026.png,windows_canonical/train/meter_01302026.png +train,meter_01302026.JPEG,2307,1,3,sections/train/meter_01302026_s1.png,sections_labeled/train/3/meter_01302026_s1_d3.png,windows/train/meter_01302026.png,windows_canonical/train/meter_01302026.png +train,meter_01302026.JPEG,2307,2,0,sections/train/meter_01302026_s2.png,sections_labeled/train/0/meter_01302026_s2_d0.png,windows/train/meter_01302026.png,windows_canonical/train/meter_01302026.png +train,meter_01302026.JPEG,2307,3,7,sections/train/meter_01302026_s3.png,sections_labeled/train/7/meter_01302026_s3_d7.png,windows/train/meter_01302026.png,windows_canonical/train/meter_01302026.png +train,meter_02162026.JPEG,2312,0,2,sections/train/meter_02162026_s0.png,sections_labeled/train/2/meter_02162026_s0_d2.png,windows/train/meter_02162026.png,windows_canonical/train/meter_02162026.png +train,meter_02162026.JPEG,2312,1,3,sections/train/meter_02162026_s1.png,sections_labeled/train/3/meter_02162026_s1_d3.png,windows/train/meter_02162026.png,windows_canonical/train/meter_02162026.png +train,meter_02162026.JPEG,2312,2,1,sections/train/meter_02162026_s2.png,sections_labeled/train/1/meter_02162026_s2_d1.png,windows/train/meter_02162026.png,windows_canonical/train/meter_02162026.png +train,meter_02162026.JPEG,2312,3,2,sections/train/meter_02162026_s3.png,sections_labeled/train/2/meter_02162026_s3_d2.png,windows/train/meter_02162026.png,windows_canonical/train/meter_02162026.png +train,meter_02192026.JPEG,2312,0,2,sections/train/meter_02192026_s0.png,sections_labeled/train/2/meter_02192026_s0_d2.png,windows/train/meter_02192026.png,windows_canonical/train/meter_02192026.png +train,meter_02192026.JPEG,2312,1,3,sections/train/meter_02192026_s1.png,sections_labeled/train/3/meter_02192026_s1_d3.png,windows/train/meter_02192026.png,windows_canonical/train/meter_02192026.png +train,meter_02192026.JPEG,2312,2,1,sections/train/meter_02192026_s2.png,sections_labeled/train/1/meter_02192026_s2_d1.png,windows/train/meter_02192026.png,windows_canonical/train/meter_02192026.png +train,meter_02192026.JPEG,2312,3,2,sections/train/meter_02192026_s3.png,sections_labeled/train/2/meter_02192026_s3_d2.png,windows/train/meter_02192026.png,windows_canonical/train/meter_02192026.png +train,meter_02202026.JPEG,2313,0,2,sections/train/meter_02202026_s0.png,sections_labeled/train/2/meter_02202026_s0_d2.png,windows/train/meter_02202026.png,windows_canonical/train/meter_02202026.png +train,meter_02202026.JPEG,2313,1,3,sections/train/meter_02202026_s1.png,sections_labeled/train/3/meter_02202026_s1_d3.png,windows/train/meter_02202026.png,windows_canonical/train/meter_02202026.png +train,meter_02202026.JPEG,2313,2,1,sections/train/meter_02202026_s2.png,sections_labeled/train/1/meter_02202026_s2_d1.png,windows/train/meter_02202026.png,windows_canonical/train/meter_02202026.png +train,meter_02202026.JPEG,2313,3,3,sections/train/meter_02202026_s3.png,sections_labeled/train/3/meter_02202026_s3_d3.png,windows/train/meter_02202026.png,windows_canonical/train/meter_02202026.png +train,meter_02242026.JPEG,2314,0,2,sections/train/meter_02242026_s0.png,sections_labeled/train/2/meter_02242026_s0_d2.png,windows/train/meter_02242026.png,windows_canonical/train/meter_02242026.png +train,meter_02242026.JPEG,2314,1,3,sections/train/meter_02242026_s1.png,sections_labeled/train/3/meter_02242026_s1_d3.png,windows/train/meter_02242026.png,windows_canonical/train/meter_02242026.png +train,meter_02242026.JPEG,2314,2,1,sections/train/meter_02242026_s2.png,sections_labeled/train/1/meter_02242026_s2_d1.png,windows/train/meter_02242026.png,windows_canonical/train/meter_02242026.png +train,meter_02242026.JPEG,2314,3,4,sections/train/meter_02242026_s3.png,sections_labeled/train/4/meter_02242026_s3_d4.png,windows/train/meter_02242026.png,windows_canonical/train/meter_02242026.png +train,meter_02272026.JPEG,2315,0,2,sections/train/meter_02272026_s0.png,sections_labeled/train/2/meter_02272026_s0_d2.png,windows/train/meter_02272026.png,windows_canonical/train/meter_02272026.png +train,meter_02272026.JPEG,2315,1,3,sections/train/meter_02272026_s1.png,sections_labeled/train/3/meter_02272026_s1_d3.png,windows/train/meter_02272026.png,windows_canonical/train/meter_02272026.png +train,meter_02272026.JPEG,2315,2,1,sections/train/meter_02272026_s2.png,sections_labeled/train/1/meter_02272026_s2_d1.png,windows/train/meter_02272026.png,windows_canonical/train/meter_02272026.png +train,meter_02272026.JPEG,2315,3,5,sections/train/meter_02272026_s3.png,sections_labeled/train/5/meter_02272026_s3_d5.png,windows/train/meter_02272026.png,windows_canonical/train/meter_02272026.png +train,meter_03032026.JPEG,2316,0,2,sections/train/meter_03032026_s0.png,sections_labeled/train/2/meter_03032026_s0_d2.png,windows/train/meter_03032026.png,windows_canonical/train/meter_03032026.png +train,meter_03032026.JPEG,2316,1,3,sections/train/meter_03032026_s1.png,sections_labeled/train/3/meter_03032026_s1_d3.png,windows/train/meter_03032026.png,windows_canonical/train/meter_03032026.png +train,meter_03032026.JPEG,2316,2,1,sections/train/meter_03032026_s2.png,sections_labeled/train/1/meter_03032026_s2_d1.png,windows/train/meter_03032026.png,windows_canonical/train/meter_03032026.png +train,meter_03032026.JPEG,2316,3,6,sections/train/meter_03032026_s3.png,sections_labeled/train/6/meter_03032026_s3_d6.png,windows/train/meter_03032026.png,windows_canonical/train/meter_03032026.png +train,meter_07012020.JPEG,1784,0,1,sections/train/meter_07012020_s0.png,sections_labeled/train/1/meter_07012020_s0_d1.png,windows/train/meter_07012020.png,windows_canonical/train/meter_07012020.png +train,meter_07012020.JPEG,1784,1,7,sections/train/meter_07012020_s1.png,sections_labeled/train/7/meter_07012020_s1_d7.png,windows/train/meter_07012020.png,windows_canonical/train/meter_07012020.png +train,meter_07012020.JPEG,1784,2,8,sections/train/meter_07012020_s2.png,sections_labeled/train/8/meter_07012020_s2_d8.png,windows/train/meter_07012020.png,windows_canonical/train/meter_07012020.png +train,meter_07012020.JPEG,1784,3,4,sections/train/meter_07012020_s3.png,sections_labeled/train/4/meter_07012020_s3_d4.png,windows/train/meter_07012020.png,windows_canonical/train/meter_07012020.png +train,meter_10092025.JPEG,2279,0,2,sections/train/meter_10092025_s0.png,sections_labeled/train/2/meter_10092025_s0_d2.png,windows/train/meter_10092025.png,windows_canonical/train/meter_10092025.png +train,meter_10092025.JPEG,2279,1,2,sections/train/meter_10092025_s1.png,sections_labeled/train/2/meter_10092025_s1_d2.png,windows/train/meter_10092025.png,windows_canonical/train/meter_10092025.png +train,meter_10092025.JPEG,2279,2,7,sections/train/meter_10092025_s2.png,sections_labeled/train/7/meter_10092025_s2_d7.png,windows/train/meter_10092025.png,windows_canonical/train/meter_10092025.png +train,meter_10092025.JPEG,2279,3,9,sections/train/meter_10092025_s3.png,sections_labeled/train/9/meter_10092025_s3_d9.png,windows/train/meter_10092025.png,windows_canonical/train/meter_10092025.png +train,meter_11112020.JPEG,1819,0,1,sections/train/meter_11112020_s0.png,sections_labeled/train/1/meter_11112020_s0_d1.png,windows/train/meter_11112020.png,windows_canonical/train/meter_11112020.png +train,meter_11112020.JPEG,1819,1,8,sections/train/meter_11112020_s1.png,sections_labeled/train/8/meter_11112020_s1_d8.png,windows/train/meter_11112020.png,windows_canonical/train/meter_11112020.png +train,meter_11112020.JPEG,1819,2,1,sections/train/meter_11112020_s2.png,sections_labeled/train/1/meter_11112020_s2_d1.png,windows/train/meter_11112020.png,windows_canonical/train/meter_11112020.png +train,meter_11112020.JPEG,1819,3,9,sections/train/meter_11112020_s3.png,sections_labeled/train/9/meter_11112020_s3_d9.png,windows/train/meter_11112020.png,windows_canonical/train/meter_11112020.png +val,meter_02022026.JPEG,2308,0,2,sections/val/meter_02022026_s0.png,sections_labeled/val/2/meter_02022026_s0_d2.png,windows/val/meter_02022026.png,windows_canonical/val/meter_02022026.png +val,meter_02022026.JPEG,2308,1,3,sections/val/meter_02022026_s1.png,sections_labeled/val/3/meter_02022026_s1_d3.png,windows/val/meter_02022026.png,windows_canonical/val/meter_02022026.png +val,meter_02022026.JPEG,2308,2,0,sections/val/meter_02022026_s2.png,sections_labeled/val/0/meter_02022026_s2_d0.png,windows/val/meter_02022026.png,windows_canonical/val/meter_02022026.png +val,meter_02022026.JPEG,2308,3,8,sections/val/meter_02022026_s3.png,sections_labeled/val/8/meter_02022026_s3_d8.png,windows/val/meter_02022026.png,windows_canonical/val/meter_02022026.png +val,meter_02142026.JPEG,2311,0,2,sections/val/meter_02142026_s0.png,sections_labeled/val/2/meter_02142026_s0_d2.png,windows/val/meter_02142026.png,windows_canonical/val/meter_02142026.png +val,meter_02142026.JPEG,2311,1,3,sections/val/meter_02142026_s1.png,sections_labeled/val/3/meter_02142026_s1_d3.png,windows/val/meter_02142026.png,windows_canonical/val/meter_02142026.png +val,meter_02142026.JPEG,2311,2,1,sections/val/meter_02142026_s2.png,sections_labeled/val/1/meter_02142026_s2_d1.png,windows/val/meter_02142026.png,windows_canonical/val/meter_02142026.png +val,meter_02142026.JPEG,2311,3,1,sections/val/meter_02142026_s3.png,sections_labeled/val/1/meter_02142026_s3_d1.png,windows/val/meter_02142026.png,windows_canonical/val/meter_02142026.png +test,meter_02102026.JPEG,2310,0,2,sections/test/meter_02102026_s0.png,sections_labeled/test/2/meter_02102026_s0_d2.png,windows/test/meter_02102026.png,windows_canonical/test/meter_02102026.png +test,meter_02102026.JPEG,2310,1,3,sections/test/meter_02102026_s1.png,sections_labeled/test/3/meter_02102026_s1_d3.png,windows/test/meter_02102026.png,windows_canonical/test/meter_02102026.png +test,meter_02102026.JPEG,2310,2,1,sections/test/meter_02102026_s2.png,sections_labeled/test/1/meter_02102026_s2_d1.png,windows/test/meter_02102026.png,windows_canonical/test/meter_02102026.png +test,meter_02102026.JPEG,2310,3,0,sections/test/meter_02102026_s3.png,sections_labeled/test/0/meter_02102026_s3_d0.png,windows/test/meter_02102026.png,windows_canonical/test/meter_02102026.png diff --git a/backend/data/digit_dataset/manifests/section_labels_skipped.csv b/backend/data/digit_dataset/manifests/section_labels_skipped.csv new file mode 100644 index 0000000..e3edf0c --- /dev/null +++ b/backend/data/digit_dataset/manifests/section_labels_skipped.csv @@ -0,0 +1 @@ +split,filename,section_index,section_path,reason diff --git a/backend/data/digit_dataset/manifests/section_labels_summary.json b/backend/data/digit_dataset/manifests/section_labels_summary.json new file mode 100644 index 0000000..e6d2e69 --- /dev/null +++ b/backend/data/digit_dataset/manifests/section_labels_summary.json @@ -0,0 +1,24 @@ +{ + "labels_exported": 60, + "labels_skipped": 0, + "section_count": 4, + "split_counts": { + "train": 48, + "val": 8, + "test": 4 + }, + "digit_counts": { + "0": 5, + "1": 12, + "2": 18, + "3": 13, + "4": 2, + "5": 1, + "6": 1, + "7": 3, + "8": 3, + "9": 2 + }, + "dataset_dir": "/home/andrea/GitHubRepositories/Jarvis/backend/data/digit_dataset_v2", + "sections_manifest": "/home/andrea/GitHubRepositories/Jarvis/backend/data/digit_dataset_v2/manifests/sections.csv" +} \ No newline at end of file diff --git a/backend/data/digit_dataset/manifests/sections.csv b/backend/data/digit_dataset/manifests/sections.csv new file mode 100644 index 0000000..65a4749 --- /dev/null +++ b/backend/data/digit_dataset/manifests/sections.csv @@ -0,0 +1,61 @@ +split,filename,reading,source_window_path,canonical_window_path,section_index,major_axis,applied_rotation,x0,y0,x1,y1,section_path +train,meter_01122026.JPEG,2302,windows/train/meter_01122026.png,windows_canonical/train/meter_01122026.png,0,x,270,0,0,67,94,sections/train/meter_01122026_s0.png +train,meter_01122026.JPEG,2302,windows/train/meter_01122026.png,windows_canonical/train/meter_01122026.png,1,x,270,67,0,134,94,sections/train/meter_01122026_s1.png +train,meter_01122026.JPEG,2302,windows/train/meter_01122026.png,windows_canonical/train/meter_01122026.png,2,x,270,134,0,201,94,sections/train/meter_01122026_s2.png +train,meter_01122026.JPEG,2302,windows/train/meter_01122026.png,windows_canonical/train/meter_01122026.png,3,x,270,201,0,268,94,sections/train/meter_01122026_s3.png +train,meter_01132026.jpg,2302,windows/train/meter_01132026.png,windows_canonical/train/meter_01132026.png,0,x,0,0,0,132,194,sections/train/meter_01132026_s0.png +train,meter_01132026.jpg,2302,windows/train/meter_01132026.png,windows_canonical/train/meter_01132026.png,1,x,0,132,0,263,194,sections/train/meter_01132026_s1.png +train,meter_01132026.jpg,2302,windows/train/meter_01132026.png,windows_canonical/train/meter_01132026.png,2,x,0,263,0,394,194,sections/train/meter_01132026_s2.png +train,meter_01132026.jpg,2302,windows/train/meter_01132026.png,windows_canonical/train/meter_01132026.png,3,x,0,394,0,526,194,sections/train/meter_01132026_s3.png +train,meter_01302026.JPEG,2307,windows/train/meter_01302026.png,windows_canonical/train/meter_01302026.png,0,x,270,0,0,74,107,sections/train/meter_01302026_s0.png +train,meter_01302026.JPEG,2307,windows/train/meter_01302026.png,windows_canonical/train/meter_01302026.png,1,x,270,74,0,148,107,sections/train/meter_01302026_s1.png +train,meter_01302026.JPEG,2307,windows/train/meter_01302026.png,windows_canonical/train/meter_01302026.png,2,x,270,148,0,223,107,sections/train/meter_01302026_s2.png +train,meter_01302026.JPEG,2307,windows/train/meter_01302026.png,windows_canonical/train/meter_01302026.png,3,x,270,223,0,297,107,sections/train/meter_01302026_s3.png +train,meter_02162026.JPEG,2312,windows/train/meter_02162026.png,windows_canonical/train/meter_02162026.png,0,x,180,0,0,67,102,sections/train/meter_02162026_s0.png +train,meter_02162026.JPEG,2312,windows/train/meter_02162026.png,windows_canonical/train/meter_02162026.png,1,x,180,67,0,134,102,sections/train/meter_02162026_s1.png +train,meter_02162026.JPEG,2312,windows/train/meter_02162026.png,windows_canonical/train/meter_02162026.png,2,x,180,134,0,202,102,sections/train/meter_02162026_s2.png +train,meter_02162026.JPEG,2312,windows/train/meter_02162026.png,windows_canonical/train/meter_02162026.png,3,x,180,202,0,269,102,sections/train/meter_02162026_s3.png +train,meter_02192026.JPEG,2312,windows/train/meter_02192026.png,windows_canonical/train/meter_02192026.png,0,x,270,0,0,70,111,sections/train/meter_02192026_s0.png +train,meter_02192026.JPEG,2312,windows/train/meter_02192026.png,windows_canonical/train/meter_02192026.png,1,x,270,70,0,141,111,sections/train/meter_02192026_s1.png +train,meter_02192026.JPEG,2312,windows/train/meter_02192026.png,windows_canonical/train/meter_02192026.png,2,x,270,141,0,212,111,sections/train/meter_02192026_s2.png +train,meter_02192026.JPEG,2312,windows/train/meter_02192026.png,windows_canonical/train/meter_02192026.png,3,x,270,212,0,282,111,sections/train/meter_02192026_s3.png +train,meter_02202026.JPEG,2313,windows/train/meter_02202026.png,windows_canonical/train/meter_02202026.png,0,x,270,0,0,48,74,sections/train/meter_02202026_s0.png +train,meter_02202026.JPEG,2313,windows/train/meter_02202026.png,windows_canonical/train/meter_02202026.png,1,x,270,48,0,96,74,sections/train/meter_02202026_s1.png +train,meter_02202026.JPEG,2313,windows/train/meter_02202026.png,windows_canonical/train/meter_02202026.png,2,x,270,96,0,143,74,sections/train/meter_02202026_s2.png +train,meter_02202026.JPEG,2313,windows/train/meter_02202026.png,windows_canonical/train/meter_02202026.png,3,x,270,143,0,191,74,sections/train/meter_02202026_s3.png +train,meter_02242026.JPEG,2314,windows/train/meter_02242026.png,windows_canonical/train/meter_02242026.png,0,x,270,0,0,72,115,sections/train/meter_02242026_s0.png +train,meter_02242026.JPEG,2314,windows/train/meter_02242026.png,windows_canonical/train/meter_02242026.png,1,x,270,72,0,144,115,sections/train/meter_02242026_s1.png +train,meter_02242026.JPEG,2314,windows/train/meter_02242026.png,windows_canonical/train/meter_02242026.png,2,x,270,144,0,216,115,sections/train/meter_02242026_s2.png +train,meter_02242026.JPEG,2314,windows/train/meter_02242026.png,windows_canonical/train/meter_02242026.png,3,x,270,216,0,288,115,sections/train/meter_02242026_s3.png +train,meter_02272026.JPEG,2315,windows/train/meter_02272026.png,windows_canonical/train/meter_02272026.png,0,x,270,0,0,78,118,sections/train/meter_02272026_s0.png +train,meter_02272026.JPEG,2315,windows/train/meter_02272026.png,windows_canonical/train/meter_02272026.png,1,x,270,78,0,156,118,sections/train/meter_02272026_s1.png +train,meter_02272026.JPEG,2315,windows/train/meter_02272026.png,windows_canonical/train/meter_02272026.png,2,x,270,156,0,234,118,sections/train/meter_02272026_s2.png +train,meter_02272026.JPEG,2315,windows/train/meter_02272026.png,windows_canonical/train/meter_02272026.png,3,x,270,234,0,312,118,sections/train/meter_02272026_s3.png +train,meter_03032026.JPEG,2316,windows/train/meter_03032026.png,windows_canonical/train/meter_03032026.png,0,x,270,0,0,72,97,sections/train/meter_03032026_s0.png +train,meter_03032026.JPEG,2316,windows/train/meter_03032026.png,windows_canonical/train/meter_03032026.png,1,x,270,72,0,143,97,sections/train/meter_03032026_s1.png +train,meter_03032026.JPEG,2316,windows/train/meter_03032026.png,windows_canonical/train/meter_03032026.png,2,x,270,143,0,214,97,sections/train/meter_03032026_s2.png +train,meter_03032026.JPEG,2316,windows/train/meter_03032026.png,windows_canonical/train/meter_03032026.png,3,x,270,214,0,286,97,sections/train/meter_03032026_s3.png +train,meter_07012020.JPEG,1784,windows/train/meter_07012020.png,windows_canonical/train/meter_07012020.png,0,x,180,0,0,55,75,sections/train/meter_07012020_s0.png +train,meter_07012020.JPEG,1784,windows/train/meter_07012020.png,windows_canonical/train/meter_07012020.png,1,x,180,55,0,110,75,sections/train/meter_07012020_s1.png +train,meter_07012020.JPEG,1784,windows/train/meter_07012020.png,windows_canonical/train/meter_07012020.png,2,x,180,110,0,166,75,sections/train/meter_07012020_s2.png +train,meter_07012020.JPEG,1784,windows/train/meter_07012020.png,windows_canonical/train/meter_07012020.png,3,x,180,166,0,221,75,sections/train/meter_07012020_s3.png +train,meter_10092025.JPEG,2279,windows/train/meter_10092025.png,windows_canonical/train/meter_10092025.png,0,x,270,0,0,70,115,sections/train/meter_10092025_s0.png +train,meter_10092025.JPEG,2279,windows/train/meter_10092025.png,windows_canonical/train/meter_10092025.png,1,x,270,70,0,141,115,sections/train/meter_10092025_s1.png +train,meter_10092025.JPEG,2279,windows/train/meter_10092025.png,windows_canonical/train/meter_10092025.png,2,x,270,141,0,212,115,sections/train/meter_10092025_s2.png +train,meter_10092025.JPEG,2279,windows/train/meter_10092025.png,windows_canonical/train/meter_10092025.png,3,x,270,212,0,282,115,sections/train/meter_10092025_s3.png +train,meter_11112020.JPEG,1819,windows/train/meter_11112020.png,windows_canonical/train/meter_11112020.png,0,x,0,0,0,105,132,sections/train/meter_11112020_s0.png +train,meter_11112020.JPEG,1819,windows/train/meter_11112020.png,windows_canonical/train/meter_11112020.png,1,x,0,105,0,210,132,sections/train/meter_11112020_s1.png +train,meter_11112020.JPEG,1819,windows/train/meter_11112020.png,windows_canonical/train/meter_11112020.png,2,x,0,210,0,315,132,sections/train/meter_11112020_s2.png +train,meter_11112020.JPEG,1819,windows/train/meter_11112020.png,windows_canonical/train/meter_11112020.png,3,x,0,315,0,420,132,sections/train/meter_11112020_s3.png +val,meter_02022026.JPEG,2308,windows/val/meter_02022026.png,windows_canonical/val/meter_02022026.png,0,x,270,0,0,52,62,sections/val/meter_02022026_s0.png +val,meter_02022026.JPEG,2308,windows/val/meter_02022026.png,windows_canonical/val/meter_02022026.png,1,x,270,52,0,104,62,sections/val/meter_02022026_s1.png +val,meter_02022026.JPEG,2308,windows/val/meter_02022026.png,windows_canonical/val/meter_02022026.png,2,x,270,104,0,157,62,sections/val/meter_02022026_s2.png +val,meter_02022026.JPEG,2308,windows/val/meter_02022026.png,windows_canonical/val/meter_02022026.png,3,x,270,157,0,209,62,sections/val/meter_02022026_s3.png +val,meter_02142026.JPEG,2311,windows/val/meter_02142026.png,windows_canonical/val/meter_02142026.png,0,x,270,0,0,60,97,sections/val/meter_02142026_s0.png +val,meter_02142026.JPEG,2311,windows/val/meter_02142026.png,windows_canonical/val/meter_02142026.png,1,x,270,60,0,120,97,sections/val/meter_02142026_s1.png +val,meter_02142026.JPEG,2311,windows/val/meter_02142026.png,windows_canonical/val/meter_02142026.png,2,x,270,120,0,181,97,sections/val/meter_02142026_s2.png +val,meter_02142026.JPEG,2311,windows/val/meter_02142026.png,windows_canonical/val/meter_02142026.png,3,x,270,181,0,241,97,sections/val/meter_02142026_s3.png +test,meter_02102026.JPEG,2310,windows/test/meter_02102026.png,windows_canonical/test/meter_02102026.png,0,x,270,0,0,73,99,sections/test/meter_02102026_s0.png +test,meter_02102026.JPEG,2310,windows/test/meter_02102026.png,windows_canonical/test/meter_02102026.png,1,x,270,73,0,146,99,sections/test/meter_02102026_s1.png +test,meter_02102026.JPEG,2310,windows/test/meter_02102026.png,windows_canonical/test/meter_02102026.png,2,x,270,146,0,220,99,sections/test/meter_02102026_s2.png +test,meter_02102026.JPEG,2310,windows/test/meter_02102026.png,windows_canonical/test/meter_02102026.png,3,x,270,220,0,293,99,sections/test/meter_02102026_s3.png diff --git a/backend/data/digit_dataset/manifests/sections_skipped.csv b/backend/data/digit_dataset/manifests/sections_skipped.csv new file mode 100644 index 0000000..d674248 --- /dev/null +++ b/backend/data/digit_dataset/manifests/sections_skipped.csv @@ -0,0 +1 @@ +split,filename,reason diff --git a/backend/data/digit_dataset/manifests/sections_summary.json b/backend/data/digit_dataset/manifests/sections_summary.json new file mode 100644 index 0000000..cffa9fa --- /dev/null +++ b/backend/data/digit_dataset/manifests/sections_summary.json @@ -0,0 +1,29 @@ +{ + "windows_processed": 15, + "sections_exported": 60, + "windows_skipped": 0, + "section_count": 4, + "splits": { + "train": 12, + "val": 2, + "test": 1 + }, + "source_axis_counts": { + "x": 4, + "y": 11 + }, + "canonical_axis_counts": { + "x": 15, + "y": 0 + }, + "applied_rotation_counts": { + "0": 2, + "90": 0, + "180": 2, + "270": 11 + }, + "dataset_dir": "/home/andrea/GitHubRepositories/Jarvis/backend/data/digit_dataset_v2", + "windows_manifest": "/home/andrea/GitHubRepositories/Jarvis/backend/data/digit_dataset_v2/manifests/windows.csv", + "direction_overrides": "/home/andrea/GitHubRepositories/Jarvis/backend/data/digit_dataset_v2/manifests/direction_overrides.csv", + "direction_override_count": 2 +} \ No newline at end of file diff --git a/backend/data/digit_dataset/manifests/skipped.csv b/backend/data/digit_dataset/manifests/skipped.csv new file mode 100644 index 0000000..d674248 --- /dev/null +++ b/backend/data/digit_dataset/manifests/skipped.csv @@ -0,0 +1 @@ +split,filename,reason diff --git a/backend/data/digit_dataset/manifests/summary.json b/backend/data/digit_dataset/manifests/summary.json new file mode 100644 index 0000000..0dabf0b --- /dev/null +++ b/backend/data/digit_dataset/manifests/summary.json @@ -0,0 +1,15 @@ +{ + "rows_exported": 15, + "rows_skipped": 0, + "splits": { + "train": 12, + "val": 2, + "test": 1 + }, + "missing_reading_count": 0, + "expand_x": 0.0, + "expand_y": 0.0, + "csv": "/home/andrea/GitHubRepositories/Jarvis/assets/meter_readings.csv", + "roi_dataset_dir": "/home/andrea/GitHubRepositories/Jarvis/backend/data/roi_dataset", + "out_dir": "/home/andrea/GitHubRepositories/Jarvis/backend/data/digit_dataset_v2" +} \ No newline at end of file diff --git a/backend/data/digit_dataset/manifests/windows.csv b/backend/data/digit_dataset/manifests/windows.csv new file mode 100644 index 0000000..7b576b0 --- /dev/null +++ b/backend/data/digit_dataset/manifests/windows.csv @@ -0,0 +1,16 @@ +split,filename,window_path,reading,roi_x,roi_y,roi_w,roi_h +train,meter_01122026.JPEG,windows/train/meter_01122026.png,2302,639,978,94,268 +train,meter_01132026.jpg,windows/train/meter_01132026.png,2302,1585,1255,526,194 +train,meter_01302026.JPEG,windows/train/meter_01302026.png,2307,602,874,107,297 +train,meter_02162026.JPEG,windows/train/meter_02162026.png,2312,734,821,269,102 +train,meter_02192026.JPEG,windows/train/meter_02192026.png,2312,554,699,111,282 +train,meter_02202026.JPEG,windows/train/meter_02202026.png,2313,684,914,74,191 +train,meter_02242026.JPEG,windows/train/meter_02242026.png,2314,590,744,115,288 +train,meter_02272026.JPEG,windows/train/meter_02272026.png,2315,578,663,118,312 +train,meter_03032026.JPEG,windows/train/meter_03032026.png,2316,585,604,97,286 +train,meter_07012020.JPEG,windows/train/meter_07012020.png,1784,676,796,221,75 +train,meter_10092025.JPEG,windows/train/meter_10092025.png,2279,629,887,115,282 +train,meter_11112020.JPEG,windows/train/meter_11112020.png,1819,769,525,420,132 +val,meter_02022026.JPEG,windows/val/meter_02022026.png,2308,631,823,62,209 +val,meter_02142026.JPEG,windows/val/meter_02142026.png,2311,590,894,97,241 +test,meter_02102026.JPEG,windows/test/meter_02102026.png,2310,602,710,99,293 diff --git a/backend/data/digit_dataset/sections/test/meter_02102026_s0.png b/backend/data/digit_dataset/sections/test/meter_02102026_s0.png new file mode 100644 index 0000000..fba0620 Binary files /dev/null and b/backend/data/digit_dataset/sections/test/meter_02102026_s0.png differ diff --git a/backend/data/digit_dataset/sections/test/meter_02102026_s1.png b/backend/data/digit_dataset/sections/test/meter_02102026_s1.png new file mode 100644 index 0000000..f7e730b Binary files /dev/null and b/backend/data/digit_dataset/sections/test/meter_02102026_s1.png differ diff --git a/backend/data/digit_dataset/sections/test/meter_02102026_s2.png b/backend/data/digit_dataset/sections/test/meter_02102026_s2.png new file mode 100644 index 0000000..77682df Binary files /dev/null and b/backend/data/digit_dataset/sections/test/meter_02102026_s2.png differ diff --git a/backend/data/digit_dataset/sections/test/meter_02102026_s3.png b/backend/data/digit_dataset/sections/test/meter_02102026_s3.png new file mode 100644 index 0000000..60ba517 Binary files /dev/null and b/backend/data/digit_dataset/sections/test/meter_02102026_s3.png differ diff --git a/backend/data/digit_dataset/sections/train/meter_01122026_s0.png b/backend/data/digit_dataset/sections/train/meter_01122026_s0.png new file mode 100644 index 0000000..07cd00b Binary files /dev/null and b/backend/data/digit_dataset/sections/train/meter_01122026_s0.png differ diff --git a/backend/data/digit_dataset/sections/train/meter_01122026_s1.png b/backend/data/digit_dataset/sections/train/meter_01122026_s1.png new file mode 100644 index 0000000..2a2a4ec Binary files /dev/null and b/backend/data/digit_dataset/sections/train/meter_01122026_s1.png differ diff --git a/backend/data/digit_dataset/sections/train/meter_01122026_s2.png b/backend/data/digit_dataset/sections/train/meter_01122026_s2.png new file mode 100644 index 0000000..3c930d6 Binary files /dev/null and b/backend/data/digit_dataset/sections/train/meter_01122026_s2.png differ diff --git a/backend/data/digit_dataset/sections/train/meter_01122026_s3.png b/backend/data/digit_dataset/sections/train/meter_01122026_s3.png new file mode 100644 index 0000000..90cf4de Binary files /dev/null and b/backend/data/digit_dataset/sections/train/meter_01122026_s3.png differ diff --git a/backend/data/digit_dataset/sections/train/meter_01132026_s0.png b/backend/data/digit_dataset/sections/train/meter_01132026_s0.png new file mode 100644 index 0000000..e8a0299 Binary files /dev/null and b/backend/data/digit_dataset/sections/train/meter_01132026_s0.png differ diff --git a/backend/data/digit_dataset/sections/train/meter_01132026_s1.png b/backend/data/digit_dataset/sections/train/meter_01132026_s1.png new file mode 100644 index 0000000..291fef5 Binary files /dev/null and b/backend/data/digit_dataset/sections/train/meter_01132026_s1.png differ diff --git a/backend/data/digit_dataset/sections/train/meter_01132026_s2.png b/backend/data/digit_dataset/sections/train/meter_01132026_s2.png new file mode 100644 index 0000000..b96d40c Binary files /dev/null and b/backend/data/digit_dataset/sections/train/meter_01132026_s2.png differ diff --git a/backend/data/digit_dataset/sections/train/meter_01132026_s3.png b/backend/data/digit_dataset/sections/train/meter_01132026_s3.png new file mode 100644 index 0000000..12b1e50 Binary files /dev/null and b/backend/data/digit_dataset/sections/train/meter_01132026_s3.png differ diff --git a/backend/data/digit_dataset/sections/train/meter_01302026_s0.png b/backend/data/digit_dataset/sections/train/meter_01302026_s0.png new file mode 100644 index 0000000..2d31101 Binary files /dev/null and b/backend/data/digit_dataset/sections/train/meter_01302026_s0.png differ diff --git a/backend/data/digit_dataset/sections/train/meter_01302026_s1.png b/backend/data/digit_dataset/sections/train/meter_01302026_s1.png new file mode 100644 index 0000000..c272fd7 Binary files /dev/null and b/backend/data/digit_dataset/sections/train/meter_01302026_s1.png differ diff --git a/backend/data/digit_dataset/sections/train/meter_01302026_s2.png b/backend/data/digit_dataset/sections/train/meter_01302026_s2.png new file mode 100644 index 0000000..b5b4232 Binary files /dev/null and b/backend/data/digit_dataset/sections/train/meter_01302026_s2.png differ diff --git a/backend/data/digit_dataset/sections/train/meter_01302026_s3.png b/backend/data/digit_dataset/sections/train/meter_01302026_s3.png new file mode 100644 index 0000000..4315b92 Binary files /dev/null and b/backend/data/digit_dataset/sections/train/meter_01302026_s3.png differ diff --git a/backend/data/digit_dataset/sections/train/meter_02162026_s0.png b/backend/data/digit_dataset/sections/train/meter_02162026_s0.png new file mode 100644 index 0000000..25a5414 Binary files /dev/null and b/backend/data/digit_dataset/sections/train/meter_02162026_s0.png differ diff --git a/backend/data/digit_dataset/sections/train/meter_02162026_s1.png b/backend/data/digit_dataset/sections/train/meter_02162026_s1.png new file mode 100644 index 0000000..236a3e5 Binary files /dev/null and b/backend/data/digit_dataset/sections/train/meter_02162026_s1.png differ diff --git a/backend/data/digit_dataset/sections/train/meter_02162026_s2.png b/backend/data/digit_dataset/sections/train/meter_02162026_s2.png new file mode 100644 index 0000000..4e5cde8 Binary files /dev/null and b/backend/data/digit_dataset/sections/train/meter_02162026_s2.png differ diff --git a/backend/data/digit_dataset/sections/train/meter_02162026_s3.png b/backend/data/digit_dataset/sections/train/meter_02162026_s3.png new file mode 100644 index 0000000..dcefb76 Binary files /dev/null and b/backend/data/digit_dataset/sections/train/meter_02162026_s3.png differ diff --git a/backend/data/digit_dataset/sections/train/meter_02192026_s0.png b/backend/data/digit_dataset/sections/train/meter_02192026_s0.png new file mode 100644 index 0000000..8da1e08 Binary files /dev/null and b/backend/data/digit_dataset/sections/train/meter_02192026_s0.png differ diff --git a/backend/data/digit_dataset/sections/train/meter_02192026_s1.png b/backend/data/digit_dataset/sections/train/meter_02192026_s1.png new file mode 100644 index 0000000..6ce9476 Binary files /dev/null and b/backend/data/digit_dataset/sections/train/meter_02192026_s1.png differ diff --git a/backend/data/digit_dataset/sections/train/meter_02192026_s2.png b/backend/data/digit_dataset/sections/train/meter_02192026_s2.png new file mode 100644 index 0000000..30b84d5 Binary files /dev/null and b/backend/data/digit_dataset/sections/train/meter_02192026_s2.png differ diff --git a/backend/data/digit_dataset/sections/train/meter_02192026_s3.png b/backend/data/digit_dataset/sections/train/meter_02192026_s3.png new file mode 100644 index 0000000..2c4e145 Binary files /dev/null and b/backend/data/digit_dataset/sections/train/meter_02192026_s3.png differ diff --git a/backend/data/digit_dataset/sections/train/meter_02202026_s0.png b/backend/data/digit_dataset/sections/train/meter_02202026_s0.png new file mode 100644 index 0000000..4f9e71a Binary files /dev/null and b/backend/data/digit_dataset/sections/train/meter_02202026_s0.png differ diff --git a/backend/data/digit_dataset/sections/train/meter_02202026_s1.png b/backend/data/digit_dataset/sections/train/meter_02202026_s1.png new file mode 100644 index 0000000..c481624 Binary files /dev/null and b/backend/data/digit_dataset/sections/train/meter_02202026_s1.png differ diff --git a/backend/data/digit_dataset/sections/train/meter_02202026_s2.png b/backend/data/digit_dataset/sections/train/meter_02202026_s2.png new file mode 100644 index 0000000..c08d834 Binary files /dev/null and b/backend/data/digit_dataset/sections/train/meter_02202026_s2.png differ diff --git a/backend/data/digit_dataset/sections/train/meter_02202026_s3.png b/backend/data/digit_dataset/sections/train/meter_02202026_s3.png new file mode 100644 index 0000000..86b883c Binary files /dev/null and b/backend/data/digit_dataset/sections/train/meter_02202026_s3.png differ diff --git a/backend/data/digit_dataset/sections/train/meter_02242026_s0.png b/backend/data/digit_dataset/sections/train/meter_02242026_s0.png new file mode 100644 index 0000000..f40b99d Binary files /dev/null and b/backend/data/digit_dataset/sections/train/meter_02242026_s0.png differ diff --git a/backend/data/digit_dataset/sections/train/meter_02242026_s1.png b/backend/data/digit_dataset/sections/train/meter_02242026_s1.png new file mode 100644 index 0000000..3e35e71 Binary files /dev/null and b/backend/data/digit_dataset/sections/train/meter_02242026_s1.png differ diff --git a/backend/data/digit_dataset/sections/train/meter_02242026_s2.png b/backend/data/digit_dataset/sections/train/meter_02242026_s2.png new file mode 100644 index 0000000..3c11e79 Binary files /dev/null and b/backend/data/digit_dataset/sections/train/meter_02242026_s2.png differ diff --git a/backend/data/digit_dataset/sections/train/meter_02242026_s3.png b/backend/data/digit_dataset/sections/train/meter_02242026_s3.png new file mode 100644 index 0000000..4c82cb3 Binary files /dev/null and b/backend/data/digit_dataset/sections/train/meter_02242026_s3.png differ diff --git a/backend/data/digit_dataset/sections/train/meter_02272026_s0.png b/backend/data/digit_dataset/sections/train/meter_02272026_s0.png new file mode 100644 index 0000000..376cb85 Binary files /dev/null and b/backend/data/digit_dataset/sections/train/meter_02272026_s0.png differ diff --git a/backend/data/digit_dataset/sections/train/meter_02272026_s1.png b/backend/data/digit_dataset/sections/train/meter_02272026_s1.png new file mode 100644 index 0000000..f150f28 Binary files /dev/null and b/backend/data/digit_dataset/sections/train/meter_02272026_s1.png differ diff --git a/backend/data/digit_dataset/sections/train/meter_02272026_s2.png b/backend/data/digit_dataset/sections/train/meter_02272026_s2.png new file mode 100644 index 0000000..c5f405f Binary files /dev/null and b/backend/data/digit_dataset/sections/train/meter_02272026_s2.png differ diff --git a/backend/data/digit_dataset/sections/train/meter_02272026_s3.png b/backend/data/digit_dataset/sections/train/meter_02272026_s3.png new file mode 100644 index 0000000..44a626d Binary files /dev/null and b/backend/data/digit_dataset/sections/train/meter_02272026_s3.png differ diff --git a/backend/data/digit_dataset/sections/train/meter_03032026_s0.png b/backend/data/digit_dataset/sections/train/meter_03032026_s0.png new file mode 100644 index 0000000..c3909d4 Binary files /dev/null and b/backend/data/digit_dataset/sections/train/meter_03032026_s0.png differ diff --git a/backend/data/digit_dataset/sections/train/meter_03032026_s1.png b/backend/data/digit_dataset/sections/train/meter_03032026_s1.png new file mode 100644 index 0000000..46cacaa Binary files /dev/null and b/backend/data/digit_dataset/sections/train/meter_03032026_s1.png differ diff --git a/backend/data/digit_dataset/sections/train/meter_03032026_s2.png b/backend/data/digit_dataset/sections/train/meter_03032026_s2.png new file mode 100644 index 0000000..76c6186 Binary files /dev/null and b/backend/data/digit_dataset/sections/train/meter_03032026_s2.png differ diff --git a/backend/data/digit_dataset/sections/train/meter_03032026_s3.png b/backend/data/digit_dataset/sections/train/meter_03032026_s3.png new file mode 100644 index 0000000..4fa3c52 Binary files /dev/null and b/backend/data/digit_dataset/sections/train/meter_03032026_s3.png differ diff --git a/backend/data/digit_dataset/sections/train/meter_07012020_s0.png b/backend/data/digit_dataset/sections/train/meter_07012020_s0.png new file mode 100644 index 0000000..3af1ea5 Binary files /dev/null and b/backend/data/digit_dataset/sections/train/meter_07012020_s0.png differ diff --git a/backend/data/digit_dataset/sections/train/meter_07012020_s1.png b/backend/data/digit_dataset/sections/train/meter_07012020_s1.png new file mode 100644 index 0000000..e7a4dc3 Binary files /dev/null and b/backend/data/digit_dataset/sections/train/meter_07012020_s1.png differ diff --git a/backend/data/digit_dataset/sections/train/meter_07012020_s2.png b/backend/data/digit_dataset/sections/train/meter_07012020_s2.png new file mode 100644 index 0000000..3a8c61a Binary files /dev/null and b/backend/data/digit_dataset/sections/train/meter_07012020_s2.png differ diff --git a/backend/data/digit_dataset/sections/train/meter_07012020_s3.png b/backend/data/digit_dataset/sections/train/meter_07012020_s3.png new file mode 100644 index 0000000..27b4adf Binary files /dev/null and b/backend/data/digit_dataset/sections/train/meter_07012020_s3.png differ diff --git a/backend/data/digit_dataset/sections/train/meter_10092025_s0.png b/backend/data/digit_dataset/sections/train/meter_10092025_s0.png new file mode 100644 index 0000000..b923107 Binary files /dev/null and b/backend/data/digit_dataset/sections/train/meter_10092025_s0.png differ diff --git a/backend/data/digit_dataset/sections/train/meter_10092025_s1.png b/backend/data/digit_dataset/sections/train/meter_10092025_s1.png new file mode 100644 index 0000000..c901871 Binary files /dev/null and b/backend/data/digit_dataset/sections/train/meter_10092025_s1.png differ diff --git a/backend/data/digit_dataset/sections/train/meter_10092025_s2.png b/backend/data/digit_dataset/sections/train/meter_10092025_s2.png new file mode 100644 index 0000000..f7d1c2b Binary files /dev/null and b/backend/data/digit_dataset/sections/train/meter_10092025_s2.png differ diff --git a/backend/data/digit_dataset/sections/train/meter_10092025_s3.png b/backend/data/digit_dataset/sections/train/meter_10092025_s3.png new file mode 100644 index 0000000..3bf1967 Binary files /dev/null and b/backend/data/digit_dataset/sections/train/meter_10092025_s3.png differ diff --git a/backend/data/digit_dataset/sections/train/meter_11112020_s0.png b/backend/data/digit_dataset/sections/train/meter_11112020_s0.png new file mode 100644 index 0000000..611d7e4 Binary files /dev/null and b/backend/data/digit_dataset/sections/train/meter_11112020_s0.png differ diff --git a/backend/data/digit_dataset/sections/train/meter_11112020_s1.png b/backend/data/digit_dataset/sections/train/meter_11112020_s1.png new file mode 100644 index 0000000..f1fc7b5 Binary files /dev/null and b/backend/data/digit_dataset/sections/train/meter_11112020_s1.png differ diff --git a/backend/data/digit_dataset/sections/train/meter_11112020_s2.png b/backend/data/digit_dataset/sections/train/meter_11112020_s2.png new file mode 100644 index 0000000..de200ce Binary files /dev/null and b/backend/data/digit_dataset/sections/train/meter_11112020_s2.png differ diff --git a/backend/data/digit_dataset/sections/train/meter_11112020_s3.png b/backend/data/digit_dataset/sections/train/meter_11112020_s3.png new file mode 100644 index 0000000..b33a2e2 Binary files /dev/null and b/backend/data/digit_dataset/sections/train/meter_11112020_s3.png differ diff --git a/backend/data/digit_dataset/sections/val/meter_02022026_s0.png b/backend/data/digit_dataset/sections/val/meter_02022026_s0.png new file mode 100644 index 0000000..448754a Binary files /dev/null and b/backend/data/digit_dataset/sections/val/meter_02022026_s0.png differ diff --git a/backend/data/digit_dataset/sections/val/meter_02022026_s1.png b/backend/data/digit_dataset/sections/val/meter_02022026_s1.png new file mode 100644 index 0000000..6735ac1 Binary files /dev/null and b/backend/data/digit_dataset/sections/val/meter_02022026_s1.png differ diff --git a/backend/data/digit_dataset/sections/val/meter_02022026_s2.png b/backend/data/digit_dataset/sections/val/meter_02022026_s2.png new file mode 100644 index 0000000..ba092cf Binary files /dev/null and b/backend/data/digit_dataset/sections/val/meter_02022026_s2.png differ diff --git a/backend/data/digit_dataset/sections/val/meter_02022026_s3.png b/backend/data/digit_dataset/sections/val/meter_02022026_s3.png new file mode 100644 index 0000000..14afb67 Binary files /dev/null and b/backend/data/digit_dataset/sections/val/meter_02022026_s3.png differ diff --git a/backend/data/digit_dataset/sections/val/meter_02142026_s0.png b/backend/data/digit_dataset/sections/val/meter_02142026_s0.png new file mode 100644 index 0000000..e7ffa10 Binary files /dev/null and b/backend/data/digit_dataset/sections/val/meter_02142026_s0.png differ diff --git a/backend/data/digit_dataset/sections/val/meter_02142026_s1.png b/backend/data/digit_dataset/sections/val/meter_02142026_s1.png new file mode 100644 index 0000000..4944033 Binary files /dev/null and b/backend/data/digit_dataset/sections/val/meter_02142026_s1.png differ diff --git a/backend/data/digit_dataset/sections/val/meter_02142026_s2.png b/backend/data/digit_dataset/sections/val/meter_02142026_s2.png new file mode 100644 index 0000000..ee5dcfd Binary files /dev/null and b/backend/data/digit_dataset/sections/val/meter_02142026_s2.png differ diff --git a/backend/data/digit_dataset/sections/val/meter_02142026_s3.png b/backend/data/digit_dataset/sections/val/meter_02142026_s3.png new file mode 100644 index 0000000..58baf38 Binary files /dev/null and b/backend/data/digit_dataset/sections/val/meter_02142026_s3.png differ diff --git a/backend/data/digit_dataset/sections_labeled/test/0/meter_02102026_s3_d0.png b/backend/data/digit_dataset/sections_labeled/test/0/meter_02102026_s3_d0.png new file mode 100644 index 0000000..60ba517 Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/test/0/meter_02102026_s3_d0.png differ diff --git a/backend/data/digit_dataset/sections_labeled/test/1/meter_02102026_s2_d1.png b/backend/data/digit_dataset/sections_labeled/test/1/meter_02102026_s2_d1.png new file mode 100644 index 0000000..77682df Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/test/1/meter_02102026_s2_d1.png differ diff --git a/backend/data/digit_dataset/sections_labeled/test/2/meter_02102026_s0_d2.png b/backend/data/digit_dataset/sections_labeled/test/2/meter_02102026_s0_d2.png new file mode 100644 index 0000000..fba0620 Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/test/2/meter_02102026_s0_d2.png differ diff --git a/backend/data/digit_dataset/sections_labeled/test/3/meter_02102026_s1_d3.png b/backend/data/digit_dataset/sections_labeled/test/3/meter_02102026_s1_d3.png new file mode 100644 index 0000000..f7e730b Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/test/3/meter_02102026_s1_d3.png differ diff --git a/backend/data/digit_dataset/sections_labeled/train/0/meter_01122026_s2_d0.png b/backend/data/digit_dataset/sections_labeled/train/0/meter_01122026_s2_d0.png new file mode 100644 index 0000000..3c930d6 Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/train/0/meter_01122026_s2_d0.png differ diff --git a/backend/data/digit_dataset/sections_labeled/train/0/meter_01132026_s2_d0.png b/backend/data/digit_dataset/sections_labeled/train/0/meter_01132026_s2_d0.png new file mode 100644 index 0000000..b96d40c Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/train/0/meter_01132026_s2_d0.png differ diff --git a/backend/data/digit_dataset/sections_labeled/train/0/meter_01302026_s2_d0.png b/backend/data/digit_dataset/sections_labeled/train/0/meter_01302026_s2_d0.png new file mode 100644 index 0000000..b5b4232 Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/train/0/meter_01302026_s2_d0.png differ diff --git a/backend/data/digit_dataset/sections_labeled/train/1/meter_02162026_s2_d1.png b/backend/data/digit_dataset/sections_labeled/train/1/meter_02162026_s2_d1.png new file mode 100644 index 0000000..4e5cde8 Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/train/1/meter_02162026_s2_d1.png differ diff --git a/backend/data/digit_dataset/sections_labeled/train/1/meter_02192026_s2_d1.png b/backend/data/digit_dataset/sections_labeled/train/1/meter_02192026_s2_d1.png new file mode 100644 index 0000000..30b84d5 Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/train/1/meter_02192026_s2_d1.png differ diff --git a/backend/data/digit_dataset/sections_labeled/train/1/meter_02202026_s2_d1.png b/backend/data/digit_dataset/sections_labeled/train/1/meter_02202026_s2_d1.png new file mode 100644 index 0000000..c08d834 Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/train/1/meter_02202026_s2_d1.png differ diff --git a/backend/data/digit_dataset/sections_labeled/train/1/meter_02242026_s2_d1.png b/backend/data/digit_dataset/sections_labeled/train/1/meter_02242026_s2_d1.png new file mode 100644 index 0000000..3c11e79 Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/train/1/meter_02242026_s2_d1.png differ diff --git a/backend/data/digit_dataset/sections_labeled/train/1/meter_02272026_s2_d1.png b/backend/data/digit_dataset/sections_labeled/train/1/meter_02272026_s2_d1.png new file mode 100644 index 0000000..c5f405f Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/train/1/meter_02272026_s2_d1.png differ diff --git a/backend/data/digit_dataset/sections_labeled/train/1/meter_03032026_s2_d1.png b/backend/data/digit_dataset/sections_labeled/train/1/meter_03032026_s2_d1.png new file mode 100644 index 0000000..76c6186 Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/train/1/meter_03032026_s2_d1.png differ diff --git a/backend/data/digit_dataset/sections_labeled/train/1/meter_07012020_s0_d1.png b/backend/data/digit_dataset/sections_labeled/train/1/meter_07012020_s0_d1.png new file mode 100644 index 0000000..3af1ea5 Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/train/1/meter_07012020_s0_d1.png differ diff --git a/backend/data/digit_dataset/sections_labeled/train/1/meter_11112020_s0_d1.png b/backend/data/digit_dataset/sections_labeled/train/1/meter_11112020_s0_d1.png new file mode 100644 index 0000000..611d7e4 Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/train/1/meter_11112020_s0_d1.png differ diff --git a/backend/data/digit_dataset/sections_labeled/train/1/meter_11112020_s2_d1.png b/backend/data/digit_dataset/sections_labeled/train/1/meter_11112020_s2_d1.png new file mode 100644 index 0000000..de200ce Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/train/1/meter_11112020_s2_d1.png differ diff --git a/backend/data/digit_dataset/sections_labeled/train/2/meter_01122026_s0_d2.png b/backend/data/digit_dataset/sections_labeled/train/2/meter_01122026_s0_d2.png new file mode 100644 index 0000000..07cd00b Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/train/2/meter_01122026_s0_d2.png differ diff --git a/backend/data/digit_dataset/sections_labeled/train/2/meter_01122026_s3_d2.png b/backend/data/digit_dataset/sections_labeled/train/2/meter_01122026_s3_d2.png new file mode 100644 index 0000000..90cf4de Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/train/2/meter_01122026_s3_d2.png differ diff --git a/backend/data/digit_dataset/sections_labeled/train/2/meter_01132026_s0_d2.png b/backend/data/digit_dataset/sections_labeled/train/2/meter_01132026_s0_d2.png new file mode 100644 index 0000000..e8a0299 Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/train/2/meter_01132026_s0_d2.png differ diff --git a/backend/data/digit_dataset/sections_labeled/train/2/meter_01132026_s3_d2.png b/backend/data/digit_dataset/sections_labeled/train/2/meter_01132026_s3_d2.png new file mode 100644 index 0000000..12b1e50 Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/train/2/meter_01132026_s3_d2.png differ diff --git a/backend/data/digit_dataset/sections_labeled/train/2/meter_01302026_s0_d2.png b/backend/data/digit_dataset/sections_labeled/train/2/meter_01302026_s0_d2.png new file mode 100644 index 0000000..2d31101 Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/train/2/meter_01302026_s0_d2.png differ diff --git a/backend/data/digit_dataset/sections_labeled/train/2/meter_02162026_s0_d2.png b/backend/data/digit_dataset/sections_labeled/train/2/meter_02162026_s0_d2.png new file mode 100644 index 0000000..25a5414 Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/train/2/meter_02162026_s0_d2.png differ diff --git a/backend/data/digit_dataset/sections_labeled/train/2/meter_02162026_s3_d2.png b/backend/data/digit_dataset/sections_labeled/train/2/meter_02162026_s3_d2.png new file mode 100644 index 0000000..dcefb76 Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/train/2/meter_02162026_s3_d2.png differ diff --git a/backend/data/digit_dataset/sections_labeled/train/2/meter_02192026_s0_d2.png b/backend/data/digit_dataset/sections_labeled/train/2/meter_02192026_s0_d2.png new file mode 100644 index 0000000..8da1e08 Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/train/2/meter_02192026_s0_d2.png differ diff --git a/backend/data/digit_dataset/sections_labeled/train/2/meter_02192026_s3_d2.png b/backend/data/digit_dataset/sections_labeled/train/2/meter_02192026_s3_d2.png new file mode 100644 index 0000000..2c4e145 Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/train/2/meter_02192026_s3_d2.png differ diff --git a/backend/data/digit_dataset/sections_labeled/train/2/meter_02202026_s0_d2.png b/backend/data/digit_dataset/sections_labeled/train/2/meter_02202026_s0_d2.png new file mode 100644 index 0000000..4f9e71a Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/train/2/meter_02202026_s0_d2.png differ diff --git a/backend/data/digit_dataset/sections_labeled/train/2/meter_02242026_s0_d2.png b/backend/data/digit_dataset/sections_labeled/train/2/meter_02242026_s0_d2.png new file mode 100644 index 0000000..f40b99d Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/train/2/meter_02242026_s0_d2.png differ diff --git a/backend/data/digit_dataset/sections_labeled/train/2/meter_02272026_s0_d2.png b/backend/data/digit_dataset/sections_labeled/train/2/meter_02272026_s0_d2.png new file mode 100644 index 0000000..376cb85 Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/train/2/meter_02272026_s0_d2.png differ diff --git a/backend/data/digit_dataset/sections_labeled/train/2/meter_03032026_s0_d2.png b/backend/data/digit_dataset/sections_labeled/train/2/meter_03032026_s0_d2.png new file mode 100644 index 0000000..c3909d4 Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/train/2/meter_03032026_s0_d2.png differ diff --git a/backend/data/digit_dataset/sections_labeled/train/2/meter_10092025_s0_d2.png b/backend/data/digit_dataset/sections_labeled/train/2/meter_10092025_s0_d2.png new file mode 100644 index 0000000..b923107 Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/train/2/meter_10092025_s0_d2.png differ diff --git a/backend/data/digit_dataset/sections_labeled/train/2/meter_10092025_s1_d2.png b/backend/data/digit_dataset/sections_labeled/train/2/meter_10092025_s1_d2.png new file mode 100644 index 0000000..c901871 Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/train/2/meter_10092025_s1_d2.png differ diff --git a/backend/data/digit_dataset/sections_labeled/train/3/meter_01122026_s1_d3.png b/backend/data/digit_dataset/sections_labeled/train/3/meter_01122026_s1_d3.png new file mode 100644 index 0000000..2a2a4ec Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/train/3/meter_01122026_s1_d3.png differ diff --git a/backend/data/digit_dataset/sections_labeled/train/3/meter_01132026_s1_d3.png b/backend/data/digit_dataset/sections_labeled/train/3/meter_01132026_s1_d3.png new file mode 100644 index 0000000..291fef5 Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/train/3/meter_01132026_s1_d3.png differ diff --git a/backend/data/digit_dataset/sections_labeled/train/3/meter_01302026_s1_d3.png b/backend/data/digit_dataset/sections_labeled/train/3/meter_01302026_s1_d3.png new file mode 100644 index 0000000..c272fd7 Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/train/3/meter_01302026_s1_d3.png differ diff --git a/backend/data/digit_dataset/sections_labeled/train/3/meter_02162026_s1_d3.png b/backend/data/digit_dataset/sections_labeled/train/3/meter_02162026_s1_d3.png new file mode 100644 index 0000000..236a3e5 Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/train/3/meter_02162026_s1_d3.png differ diff --git a/backend/data/digit_dataset/sections_labeled/train/3/meter_02192026_s1_d3.png b/backend/data/digit_dataset/sections_labeled/train/3/meter_02192026_s1_d3.png new file mode 100644 index 0000000..6ce9476 Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/train/3/meter_02192026_s1_d3.png differ diff --git a/backend/data/digit_dataset/sections_labeled/train/3/meter_02202026_s1_d3.png b/backend/data/digit_dataset/sections_labeled/train/3/meter_02202026_s1_d3.png new file mode 100644 index 0000000..c481624 Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/train/3/meter_02202026_s1_d3.png differ diff --git a/backend/data/digit_dataset/sections_labeled/train/3/meter_02202026_s3_d3.png b/backend/data/digit_dataset/sections_labeled/train/3/meter_02202026_s3_d3.png new file mode 100644 index 0000000..86b883c Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/train/3/meter_02202026_s3_d3.png differ diff --git a/backend/data/digit_dataset/sections_labeled/train/3/meter_02242026_s1_d3.png b/backend/data/digit_dataset/sections_labeled/train/3/meter_02242026_s1_d3.png new file mode 100644 index 0000000..3e35e71 Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/train/3/meter_02242026_s1_d3.png differ diff --git a/backend/data/digit_dataset/sections_labeled/train/3/meter_02272026_s1_d3.png b/backend/data/digit_dataset/sections_labeled/train/3/meter_02272026_s1_d3.png new file mode 100644 index 0000000..f150f28 Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/train/3/meter_02272026_s1_d3.png differ diff --git a/backend/data/digit_dataset/sections_labeled/train/3/meter_03032026_s1_d3.png b/backend/data/digit_dataset/sections_labeled/train/3/meter_03032026_s1_d3.png new file mode 100644 index 0000000..46cacaa Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/train/3/meter_03032026_s1_d3.png differ diff --git a/backend/data/digit_dataset/sections_labeled/train/4/meter_02242026_s3_d4.png b/backend/data/digit_dataset/sections_labeled/train/4/meter_02242026_s3_d4.png new file mode 100644 index 0000000..4c82cb3 Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/train/4/meter_02242026_s3_d4.png differ diff --git a/backend/data/digit_dataset/sections_labeled/train/4/meter_07012020_s3_d4.png b/backend/data/digit_dataset/sections_labeled/train/4/meter_07012020_s3_d4.png new file mode 100644 index 0000000..27b4adf Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/train/4/meter_07012020_s3_d4.png differ diff --git a/backend/data/digit_dataset/sections_labeled/train/5/meter_02272026_s3_d5.png b/backend/data/digit_dataset/sections_labeled/train/5/meter_02272026_s3_d5.png new file mode 100644 index 0000000..44a626d Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/train/5/meter_02272026_s3_d5.png differ diff --git a/backend/data/digit_dataset/sections_labeled/train/6/meter_03032026_s3_d6.png b/backend/data/digit_dataset/sections_labeled/train/6/meter_03032026_s3_d6.png new file mode 100644 index 0000000..4fa3c52 Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/train/6/meter_03032026_s3_d6.png differ diff --git a/backend/data/digit_dataset/sections_labeled/train/7/meter_01302026_s3_d7.png b/backend/data/digit_dataset/sections_labeled/train/7/meter_01302026_s3_d7.png new file mode 100644 index 0000000..4315b92 Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/train/7/meter_01302026_s3_d7.png differ diff --git a/backend/data/digit_dataset/sections_labeled/train/7/meter_07012020_s1_d7.png b/backend/data/digit_dataset/sections_labeled/train/7/meter_07012020_s1_d7.png new file mode 100644 index 0000000..e7a4dc3 Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/train/7/meter_07012020_s1_d7.png differ diff --git a/backend/data/digit_dataset/sections_labeled/train/7/meter_10092025_s2_d7.png b/backend/data/digit_dataset/sections_labeled/train/7/meter_10092025_s2_d7.png new file mode 100644 index 0000000..f7d1c2b Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/train/7/meter_10092025_s2_d7.png differ diff --git a/backend/data/digit_dataset/sections_labeled/train/8/meter_07012020_s2_d8.png b/backend/data/digit_dataset/sections_labeled/train/8/meter_07012020_s2_d8.png new file mode 100644 index 0000000..3a8c61a Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/train/8/meter_07012020_s2_d8.png differ diff --git a/backend/data/digit_dataset/sections_labeled/train/8/meter_11112020_s1_d8.png b/backend/data/digit_dataset/sections_labeled/train/8/meter_11112020_s1_d8.png new file mode 100644 index 0000000..f1fc7b5 Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/train/8/meter_11112020_s1_d8.png differ diff --git a/backend/data/digit_dataset/sections_labeled/train/9/meter_10092025_s3_d9.png b/backend/data/digit_dataset/sections_labeled/train/9/meter_10092025_s3_d9.png new file mode 100644 index 0000000..3bf1967 Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/train/9/meter_10092025_s3_d9.png differ diff --git a/backend/data/digit_dataset/sections_labeled/train/9/meter_11112020_s3_d9.png b/backend/data/digit_dataset/sections_labeled/train/9/meter_11112020_s3_d9.png new file mode 100644 index 0000000..b33a2e2 Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/train/9/meter_11112020_s3_d9.png differ diff --git a/backend/data/digit_dataset/sections_labeled/val/0/meter_02022026_s2_d0.png b/backend/data/digit_dataset/sections_labeled/val/0/meter_02022026_s2_d0.png new file mode 100644 index 0000000..ba092cf Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/val/0/meter_02022026_s2_d0.png differ diff --git a/backend/data/digit_dataset/sections_labeled/val/1/meter_02142026_s2_d1.png b/backend/data/digit_dataset/sections_labeled/val/1/meter_02142026_s2_d1.png new file mode 100644 index 0000000..ee5dcfd Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/val/1/meter_02142026_s2_d1.png differ diff --git a/backend/data/digit_dataset/sections_labeled/val/1/meter_02142026_s3_d1.png b/backend/data/digit_dataset/sections_labeled/val/1/meter_02142026_s3_d1.png new file mode 100644 index 0000000..58baf38 Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/val/1/meter_02142026_s3_d1.png differ diff --git a/backend/data/digit_dataset/sections_labeled/val/2/meter_02022026_s0_d2.png b/backend/data/digit_dataset/sections_labeled/val/2/meter_02022026_s0_d2.png new file mode 100644 index 0000000..448754a Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/val/2/meter_02022026_s0_d2.png differ diff --git a/backend/data/digit_dataset/sections_labeled/val/2/meter_02142026_s0_d2.png b/backend/data/digit_dataset/sections_labeled/val/2/meter_02142026_s0_d2.png new file mode 100644 index 0000000..e7ffa10 Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/val/2/meter_02142026_s0_d2.png differ diff --git a/backend/data/digit_dataset/sections_labeled/val/3/meter_02022026_s1_d3.png b/backend/data/digit_dataset/sections_labeled/val/3/meter_02022026_s1_d3.png new file mode 100644 index 0000000..6735ac1 Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/val/3/meter_02022026_s1_d3.png differ diff --git a/backend/data/digit_dataset/sections_labeled/val/3/meter_02142026_s1_d3.png b/backend/data/digit_dataset/sections_labeled/val/3/meter_02142026_s1_d3.png new file mode 100644 index 0000000..4944033 Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/val/3/meter_02142026_s1_d3.png differ diff --git a/backend/data/digit_dataset/sections_labeled/val/8/meter_02022026_s3_d8.png b/backend/data/digit_dataset/sections_labeled/val/8/meter_02022026_s3_d8.png new file mode 100644 index 0000000..14afb67 Binary files /dev/null and b/backend/data/digit_dataset/sections_labeled/val/8/meter_02022026_s3_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/manifests/summary.json b/backend/data/digit_dataset/sections_synthetic/manifests/summary.json new file mode 100644 index 0000000..fdeb195 --- /dev/null +++ b/backend/data/digit_dataset/sections_synthetic/manifests/summary.json @@ -0,0 +1,24 @@ +{ + "dataset_root": "/home/andrea/GitHubRepositories/Jarvis/backend/data/digit_dataset/sections_labeled", + "output_root": "/home/andrea/GitHubRepositories/Jarvis/backend/data/digit_dataset/sections_synthetic", + "seed": 42, + "train_source_count": 48, + "direct_per_real": 6, + "compose_window_count": 180, + "direct_cells": 288, + "composed_cells": 720, + "total_cells": 1008, + "digit_counts": { + "0": 95, + "1": 139, + "2": 160, + "3": 119, + "4": 84, + "5": 68, + "6": 80, + "7": 98, + "8": 78, + "9": 87 + }, + "manifest_path": "/home/andrea/GitHubRepositories/Jarvis/backend/data/digit_dataset/sections_synthetic/manifests/synthetic_cells.csv" +} \ No newline at end of file diff --git a/backend/data/digit_dataset/sections_synthetic/manifests/synthetic_cells.csv b/backend/data/digit_dataset/sections_synthetic/manifests/synthetic_cells.csv new file mode 100644 index 0000000..c5c9d2f --- /dev/null +++ b/backend/data/digit_dataset/sections_synthetic/manifests/synthetic_cells.csv @@ -0,0 +1,1009 @@ +kind,digit,output_path,source_path,window_id,cell_index,sequence +direct,0,train/0/meter_01122026_s2_d0__direct_r00.png,train/0/meter_01122026_s2_d0.png,,,0 +direct,0,train/0/meter_01122026_s2_d0__direct_r01.png,train/0/meter_01122026_s2_d0.png,,,0 +direct,0,train/0/meter_01122026_s2_d0__direct_r02.png,train/0/meter_01122026_s2_d0.png,,,0 +direct,0,train/0/meter_01122026_s2_d0__direct_r03.png,train/0/meter_01122026_s2_d0.png,,,0 +direct,0,train/0/meter_01122026_s2_d0__direct_r04.png,train/0/meter_01122026_s2_d0.png,,,0 +direct,0,train/0/meter_01122026_s2_d0__direct_r05.png,train/0/meter_01122026_s2_d0.png,,,0 +direct,0,train/0/meter_01132026_s2_d0__direct_r00.png,train/0/meter_01132026_s2_d0.png,,,0 +direct,0,train/0/meter_01132026_s2_d0__direct_r01.png,train/0/meter_01132026_s2_d0.png,,,0 +direct,0,train/0/meter_01132026_s2_d0__direct_r02.png,train/0/meter_01132026_s2_d0.png,,,0 +direct,0,train/0/meter_01132026_s2_d0__direct_r03.png,train/0/meter_01132026_s2_d0.png,,,0 +direct,0,train/0/meter_01132026_s2_d0__direct_r04.png,train/0/meter_01132026_s2_d0.png,,,0 +direct,0,train/0/meter_01132026_s2_d0__direct_r05.png,train/0/meter_01132026_s2_d0.png,,,0 +direct,0,train/0/meter_01302026_s2_d0__direct_r00.png,train/0/meter_01302026_s2_d0.png,,,0 +direct,0,train/0/meter_01302026_s2_d0__direct_r01.png,train/0/meter_01302026_s2_d0.png,,,0 +direct,0,train/0/meter_01302026_s2_d0__direct_r02.png,train/0/meter_01302026_s2_d0.png,,,0 +direct,0,train/0/meter_01302026_s2_d0__direct_r03.png,train/0/meter_01302026_s2_d0.png,,,0 +direct,0,train/0/meter_01302026_s2_d0__direct_r04.png,train/0/meter_01302026_s2_d0.png,,,0 +direct,0,train/0/meter_01302026_s2_d0__direct_r05.png,train/0/meter_01302026_s2_d0.png,,,0 +direct,1,train/1/meter_02162026_s2_d1__direct_r00.png,train/1/meter_02162026_s2_d1.png,,,1 +direct,1,train/1/meter_02162026_s2_d1__direct_r01.png,train/1/meter_02162026_s2_d1.png,,,1 +direct,1,train/1/meter_02162026_s2_d1__direct_r02.png,train/1/meter_02162026_s2_d1.png,,,1 +direct,1,train/1/meter_02162026_s2_d1__direct_r03.png,train/1/meter_02162026_s2_d1.png,,,1 +direct,1,train/1/meter_02162026_s2_d1__direct_r04.png,train/1/meter_02162026_s2_d1.png,,,1 +direct,1,train/1/meter_02162026_s2_d1__direct_r05.png,train/1/meter_02162026_s2_d1.png,,,1 +direct,1,train/1/meter_02192026_s2_d1__direct_r00.png,train/1/meter_02192026_s2_d1.png,,,1 +direct,1,train/1/meter_02192026_s2_d1__direct_r01.png,train/1/meter_02192026_s2_d1.png,,,1 +direct,1,train/1/meter_02192026_s2_d1__direct_r02.png,train/1/meter_02192026_s2_d1.png,,,1 +direct,1,train/1/meter_02192026_s2_d1__direct_r03.png,train/1/meter_02192026_s2_d1.png,,,1 +direct,1,train/1/meter_02192026_s2_d1__direct_r04.png,train/1/meter_02192026_s2_d1.png,,,1 +direct,1,train/1/meter_02192026_s2_d1__direct_r05.png,train/1/meter_02192026_s2_d1.png,,,1 +direct,1,train/1/meter_02202026_s2_d1__direct_r00.png,train/1/meter_02202026_s2_d1.png,,,1 +direct,1,train/1/meter_02202026_s2_d1__direct_r01.png,train/1/meter_02202026_s2_d1.png,,,1 +direct,1,train/1/meter_02202026_s2_d1__direct_r02.png,train/1/meter_02202026_s2_d1.png,,,1 +direct,1,train/1/meter_02202026_s2_d1__direct_r03.png,train/1/meter_02202026_s2_d1.png,,,1 +direct,1,train/1/meter_02202026_s2_d1__direct_r04.png,train/1/meter_02202026_s2_d1.png,,,1 +direct,1,train/1/meter_02202026_s2_d1__direct_r05.png,train/1/meter_02202026_s2_d1.png,,,1 +direct,1,train/1/meter_02242026_s2_d1__direct_r00.png,train/1/meter_02242026_s2_d1.png,,,1 +direct,1,train/1/meter_02242026_s2_d1__direct_r01.png,train/1/meter_02242026_s2_d1.png,,,1 +direct,1,train/1/meter_02242026_s2_d1__direct_r02.png,train/1/meter_02242026_s2_d1.png,,,1 +direct,1,train/1/meter_02242026_s2_d1__direct_r03.png,train/1/meter_02242026_s2_d1.png,,,1 +direct,1,train/1/meter_02242026_s2_d1__direct_r04.png,train/1/meter_02242026_s2_d1.png,,,1 +direct,1,train/1/meter_02242026_s2_d1__direct_r05.png,train/1/meter_02242026_s2_d1.png,,,1 +direct,1,train/1/meter_02272026_s2_d1__direct_r00.png,train/1/meter_02272026_s2_d1.png,,,1 +direct,1,train/1/meter_02272026_s2_d1__direct_r01.png,train/1/meter_02272026_s2_d1.png,,,1 +direct,1,train/1/meter_02272026_s2_d1__direct_r02.png,train/1/meter_02272026_s2_d1.png,,,1 +direct,1,train/1/meter_02272026_s2_d1__direct_r03.png,train/1/meter_02272026_s2_d1.png,,,1 +direct,1,train/1/meter_02272026_s2_d1__direct_r04.png,train/1/meter_02272026_s2_d1.png,,,1 +direct,1,train/1/meter_02272026_s2_d1__direct_r05.png,train/1/meter_02272026_s2_d1.png,,,1 +direct,1,train/1/meter_03032026_s2_d1__direct_r00.png,train/1/meter_03032026_s2_d1.png,,,1 +direct,1,train/1/meter_03032026_s2_d1__direct_r01.png,train/1/meter_03032026_s2_d1.png,,,1 +direct,1,train/1/meter_03032026_s2_d1__direct_r02.png,train/1/meter_03032026_s2_d1.png,,,1 +direct,1,train/1/meter_03032026_s2_d1__direct_r03.png,train/1/meter_03032026_s2_d1.png,,,1 +direct,1,train/1/meter_03032026_s2_d1__direct_r04.png,train/1/meter_03032026_s2_d1.png,,,1 +direct,1,train/1/meter_03032026_s2_d1__direct_r05.png,train/1/meter_03032026_s2_d1.png,,,1 +direct,1,train/1/meter_07012020_s0_d1__direct_r00.png,train/1/meter_07012020_s0_d1.png,,,1 +direct,1,train/1/meter_07012020_s0_d1__direct_r01.png,train/1/meter_07012020_s0_d1.png,,,1 +direct,1,train/1/meter_07012020_s0_d1__direct_r02.png,train/1/meter_07012020_s0_d1.png,,,1 +direct,1,train/1/meter_07012020_s0_d1__direct_r03.png,train/1/meter_07012020_s0_d1.png,,,1 +direct,1,train/1/meter_07012020_s0_d1__direct_r04.png,train/1/meter_07012020_s0_d1.png,,,1 +direct,1,train/1/meter_07012020_s0_d1__direct_r05.png,train/1/meter_07012020_s0_d1.png,,,1 +direct,1,train/1/meter_11112020_s0_d1__direct_r00.png,train/1/meter_11112020_s0_d1.png,,,1 +direct,1,train/1/meter_11112020_s0_d1__direct_r01.png,train/1/meter_11112020_s0_d1.png,,,1 +direct,1,train/1/meter_11112020_s0_d1__direct_r02.png,train/1/meter_11112020_s0_d1.png,,,1 +direct,1,train/1/meter_11112020_s0_d1__direct_r03.png,train/1/meter_11112020_s0_d1.png,,,1 +direct,1,train/1/meter_11112020_s0_d1__direct_r04.png,train/1/meter_11112020_s0_d1.png,,,1 +direct,1,train/1/meter_11112020_s0_d1__direct_r05.png,train/1/meter_11112020_s0_d1.png,,,1 +direct,1,train/1/meter_11112020_s2_d1__direct_r00.png,train/1/meter_11112020_s2_d1.png,,,1 +direct,1,train/1/meter_11112020_s2_d1__direct_r01.png,train/1/meter_11112020_s2_d1.png,,,1 +direct,1,train/1/meter_11112020_s2_d1__direct_r02.png,train/1/meter_11112020_s2_d1.png,,,1 +direct,1,train/1/meter_11112020_s2_d1__direct_r03.png,train/1/meter_11112020_s2_d1.png,,,1 +direct,1,train/1/meter_11112020_s2_d1__direct_r04.png,train/1/meter_11112020_s2_d1.png,,,1 +direct,1,train/1/meter_11112020_s2_d1__direct_r05.png,train/1/meter_11112020_s2_d1.png,,,1 +direct,2,train/2/meter_01122026_s0_d2__direct_r00.png,train/2/meter_01122026_s0_d2.png,,,2 +direct,2,train/2/meter_01122026_s0_d2__direct_r01.png,train/2/meter_01122026_s0_d2.png,,,2 +direct,2,train/2/meter_01122026_s0_d2__direct_r02.png,train/2/meter_01122026_s0_d2.png,,,2 +direct,2,train/2/meter_01122026_s0_d2__direct_r03.png,train/2/meter_01122026_s0_d2.png,,,2 +direct,2,train/2/meter_01122026_s0_d2__direct_r04.png,train/2/meter_01122026_s0_d2.png,,,2 +direct,2,train/2/meter_01122026_s0_d2__direct_r05.png,train/2/meter_01122026_s0_d2.png,,,2 +direct,2,train/2/meter_01122026_s3_d2__direct_r00.png,train/2/meter_01122026_s3_d2.png,,,2 +direct,2,train/2/meter_01122026_s3_d2__direct_r01.png,train/2/meter_01122026_s3_d2.png,,,2 +direct,2,train/2/meter_01122026_s3_d2__direct_r02.png,train/2/meter_01122026_s3_d2.png,,,2 +direct,2,train/2/meter_01122026_s3_d2__direct_r03.png,train/2/meter_01122026_s3_d2.png,,,2 +direct,2,train/2/meter_01122026_s3_d2__direct_r04.png,train/2/meter_01122026_s3_d2.png,,,2 +direct,2,train/2/meter_01122026_s3_d2__direct_r05.png,train/2/meter_01122026_s3_d2.png,,,2 +direct,2,train/2/meter_01132026_s0_d2__direct_r00.png,train/2/meter_01132026_s0_d2.png,,,2 +direct,2,train/2/meter_01132026_s0_d2__direct_r01.png,train/2/meter_01132026_s0_d2.png,,,2 +direct,2,train/2/meter_01132026_s0_d2__direct_r02.png,train/2/meter_01132026_s0_d2.png,,,2 +direct,2,train/2/meter_01132026_s0_d2__direct_r03.png,train/2/meter_01132026_s0_d2.png,,,2 +direct,2,train/2/meter_01132026_s0_d2__direct_r04.png,train/2/meter_01132026_s0_d2.png,,,2 +direct,2,train/2/meter_01132026_s0_d2__direct_r05.png,train/2/meter_01132026_s0_d2.png,,,2 +direct,2,train/2/meter_01132026_s3_d2__direct_r00.png,train/2/meter_01132026_s3_d2.png,,,2 +direct,2,train/2/meter_01132026_s3_d2__direct_r01.png,train/2/meter_01132026_s3_d2.png,,,2 +direct,2,train/2/meter_01132026_s3_d2__direct_r02.png,train/2/meter_01132026_s3_d2.png,,,2 +direct,2,train/2/meter_01132026_s3_d2__direct_r03.png,train/2/meter_01132026_s3_d2.png,,,2 +direct,2,train/2/meter_01132026_s3_d2__direct_r04.png,train/2/meter_01132026_s3_d2.png,,,2 +direct,2,train/2/meter_01132026_s3_d2__direct_r05.png,train/2/meter_01132026_s3_d2.png,,,2 +direct,2,train/2/meter_01302026_s0_d2__direct_r00.png,train/2/meter_01302026_s0_d2.png,,,2 +direct,2,train/2/meter_01302026_s0_d2__direct_r01.png,train/2/meter_01302026_s0_d2.png,,,2 +direct,2,train/2/meter_01302026_s0_d2__direct_r02.png,train/2/meter_01302026_s0_d2.png,,,2 +direct,2,train/2/meter_01302026_s0_d2__direct_r03.png,train/2/meter_01302026_s0_d2.png,,,2 +direct,2,train/2/meter_01302026_s0_d2__direct_r04.png,train/2/meter_01302026_s0_d2.png,,,2 +direct,2,train/2/meter_01302026_s0_d2__direct_r05.png,train/2/meter_01302026_s0_d2.png,,,2 +direct,2,train/2/meter_02162026_s0_d2__direct_r00.png,train/2/meter_02162026_s0_d2.png,,,2 +direct,2,train/2/meter_02162026_s0_d2__direct_r01.png,train/2/meter_02162026_s0_d2.png,,,2 +direct,2,train/2/meter_02162026_s0_d2__direct_r02.png,train/2/meter_02162026_s0_d2.png,,,2 +direct,2,train/2/meter_02162026_s0_d2__direct_r03.png,train/2/meter_02162026_s0_d2.png,,,2 +direct,2,train/2/meter_02162026_s0_d2__direct_r04.png,train/2/meter_02162026_s0_d2.png,,,2 +direct,2,train/2/meter_02162026_s0_d2__direct_r05.png,train/2/meter_02162026_s0_d2.png,,,2 +direct,2,train/2/meter_02162026_s3_d2__direct_r00.png,train/2/meter_02162026_s3_d2.png,,,2 +direct,2,train/2/meter_02162026_s3_d2__direct_r01.png,train/2/meter_02162026_s3_d2.png,,,2 +direct,2,train/2/meter_02162026_s3_d2__direct_r02.png,train/2/meter_02162026_s3_d2.png,,,2 +direct,2,train/2/meter_02162026_s3_d2__direct_r03.png,train/2/meter_02162026_s3_d2.png,,,2 +direct,2,train/2/meter_02162026_s3_d2__direct_r04.png,train/2/meter_02162026_s3_d2.png,,,2 +direct,2,train/2/meter_02162026_s3_d2__direct_r05.png,train/2/meter_02162026_s3_d2.png,,,2 +direct,2,train/2/meter_02192026_s0_d2__direct_r00.png,train/2/meter_02192026_s0_d2.png,,,2 +direct,2,train/2/meter_02192026_s0_d2__direct_r01.png,train/2/meter_02192026_s0_d2.png,,,2 +direct,2,train/2/meter_02192026_s0_d2__direct_r02.png,train/2/meter_02192026_s0_d2.png,,,2 +direct,2,train/2/meter_02192026_s0_d2__direct_r03.png,train/2/meter_02192026_s0_d2.png,,,2 +direct,2,train/2/meter_02192026_s0_d2__direct_r04.png,train/2/meter_02192026_s0_d2.png,,,2 +direct,2,train/2/meter_02192026_s0_d2__direct_r05.png,train/2/meter_02192026_s0_d2.png,,,2 +direct,2,train/2/meter_02192026_s3_d2__direct_r00.png,train/2/meter_02192026_s3_d2.png,,,2 +direct,2,train/2/meter_02192026_s3_d2__direct_r01.png,train/2/meter_02192026_s3_d2.png,,,2 +direct,2,train/2/meter_02192026_s3_d2__direct_r02.png,train/2/meter_02192026_s3_d2.png,,,2 +direct,2,train/2/meter_02192026_s3_d2__direct_r03.png,train/2/meter_02192026_s3_d2.png,,,2 +direct,2,train/2/meter_02192026_s3_d2__direct_r04.png,train/2/meter_02192026_s3_d2.png,,,2 +direct,2,train/2/meter_02192026_s3_d2__direct_r05.png,train/2/meter_02192026_s3_d2.png,,,2 +direct,2,train/2/meter_02202026_s0_d2__direct_r00.png,train/2/meter_02202026_s0_d2.png,,,2 +direct,2,train/2/meter_02202026_s0_d2__direct_r01.png,train/2/meter_02202026_s0_d2.png,,,2 +direct,2,train/2/meter_02202026_s0_d2__direct_r02.png,train/2/meter_02202026_s0_d2.png,,,2 +direct,2,train/2/meter_02202026_s0_d2__direct_r03.png,train/2/meter_02202026_s0_d2.png,,,2 +direct,2,train/2/meter_02202026_s0_d2__direct_r04.png,train/2/meter_02202026_s0_d2.png,,,2 +direct,2,train/2/meter_02202026_s0_d2__direct_r05.png,train/2/meter_02202026_s0_d2.png,,,2 +direct,2,train/2/meter_02242026_s0_d2__direct_r00.png,train/2/meter_02242026_s0_d2.png,,,2 +direct,2,train/2/meter_02242026_s0_d2__direct_r01.png,train/2/meter_02242026_s0_d2.png,,,2 +direct,2,train/2/meter_02242026_s0_d2__direct_r02.png,train/2/meter_02242026_s0_d2.png,,,2 +direct,2,train/2/meter_02242026_s0_d2__direct_r03.png,train/2/meter_02242026_s0_d2.png,,,2 +direct,2,train/2/meter_02242026_s0_d2__direct_r04.png,train/2/meter_02242026_s0_d2.png,,,2 +direct,2,train/2/meter_02242026_s0_d2__direct_r05.png,train/2/meter_02242026_s0_d2.png,,,2 +direct,2,train/2/meter_02272026_s0_d2__direct_r00.png,train/2/meter_02272026_s0_d2.png,,,2 +direct,2,train/2/meter_02272026_s0_d2__direct_r01.png,train/2/meter_02272026_s0_d2.png,,,2 +direct,2,train/2/meter_02272026_s0_d2__direct_r02.png,train/2/meter_02272026_s0_d2.png,,,2 +direct,2,train/2/meter_02272026_s0_d2__direct_r03.png,train/2/meter_02272026_s0_d2.png,,,2 +direct,2,train/2/meter_02272026_s0_d2__direct_r04.png,train/2/meter_02272026_s0_d2.png,,,2 +direct,2,train/2/meter_02272026_s0_d2__direct_r05.png,train/2/meter_02272026_s0_d2.png,,,2 +direct,2,train/2/meter_03032026_s0_d2__direct_r00.png,train/2/meter_03032026_s0_d2.png,,,2 +direct,2,train/2/meter_03032026_s0_d2__direct_r01.png,train/2/meter_03032026_s0_d2.png,,,2 +direct,2,train/2/meter_03032026_s0_d2__direct_r02.png,train/2/meter_03032026_s0_d2.png,,,2 +direct,2,train/2/meter_03032026_s0_d2__direct_r03.png,train/2/meter_03032026_s0_d2.png,,,2 +direct,2,train/2/meter_03032026_s0_d2__direct_r04.png,train/2/meter_03032026_s0_d2.png,,,2 +direct,2,train/2/meter_03032026_s0_d2__direct_r05.png,train/2/meter_03032026_s0_d2.png,,,2 +direct,2,train/2/meter_10092025_s0_d2__direct_r00.png,train/2/meter_10092025_s0_d2.png,,,2 +direct,2,train/2/meter_10092025_s0_d2__direct_r01.png,train/2/meter_10092025_s0_d2.png,,,2 +direct,2,train/2/meter_10092025_s0_d2__direct_r02.png,train/2/meter_10092025_s0_d2.png,,,2 +direct,2,train/2/meter_10092025_s0_d2__direct_r03.png,train/2/meter_10092025_s0_d2.png,,,2 +direct,2,train/2/meter_10092025_s0_d2__direct_r04.png,train/2/meter_10092025_s0_d2.png,,,2 +direct,2,train/2/meter_10092025_s0_d2__direct_r05.png,train/2/meter_10092025_s0_d2.png,,,2 +direct,2,train/2/meter_10092025_s1_d2__direct_r00.png,train/2/meter_10092025_s1_d2.png,,,2 +direct,2,train/2/meter_10092025_s1_d2__direct_r01.png,train/2/meter_10092025_s1_d2.png,,,2 +direct,2,train/2/meter_10092025_s1_d2__direct_r02.png,train/2/meter_10092025_s1_d2.png,,,2 +direct,2,train/2/meter_10092025_s1_d2__direct_r03.png,train/2/meter_10092025_s1_d2.png,,,2 +direct,2,train/2/meter_10092025_s1_d2__direct_r04.png,train/2/meter_10092025_s1_d2.png,,,2 +direct,2,train/2/meter_10092025_s1_d2__direct_r05.png,train/2/meter_10092025_s1_d2.png,,,2 +direct,3,train/3/meter_01122026_s1_d3__direct_r00.png,train/3/meter_01122026_s1_d3.png,,,3 +direct,3,train/3/meter_01122026_s1_d3__direct_r01.png,train/3/meter_01122026_s1_d3.png,,,3 +direct,3,train/3/meter_01122026_s1_d3__direct_r02.png,train/3/meter_01122026_s1_d3.png,,,3 +direct,3,train/3/meter_01122026_s1_d3__direct_r03.png,train/3/meter_01122026_s1_d3.png,,,3 +direct,3,train/3/meter_01122026_s1_d3__direct_r04.png,train/3/meter_01122026_s1_d3.png,,,3 +direct,3,train/3/meter_01122026_s1_d3__direct_r05.png,train/3/meter_01122026_s1_d3.png,,,3 +direct,3,train/3/meter_01132026_s1_d3__direct_r00.png,train/3/meter_01132026_s1_d3.png,,,3 +direct,3,train/3/meter_01132026_s1_d3__direct_r01.png,train/3/meter_01132026_s1_d3.png,,,3 +direct,3,train/3/meter_01132026_s1_d3__direct_r02.png,train/3/meter_01132026_s1_d3.png,,,3 +direct,3,train/3/meter_01132026_s1_d3__direct_r03.png,train/3/meter_01132026_s1_d3.png,,,3 +direct,3,train/3/meter_01132026_s1_d3__direct_r04.png,train/3/meter_01132026_s1_d3.png,,,3 +direct,3,train/3/meter_01132026_s1_d3__direct_r05.png,train/3/meter_01132026_s1_d3.png,,,3 +direct,3,train/3/meter_01302026_s1_d3__direct_r00.png,train/3/meter_01302026_s1_d3.png,,,3 +direct,3,train/3/meter_01302026_s1_d3__direct_r01.png,train/3/meter_01302026_s1_d3.png,,,3 +direct,3,train/3/meter_01302026_s1_d3__direct_r02.png,train/3/meter_01302026_s1_d3.png,,,3 +direct,3,train/3/meter_01302026_s1_d3__direct_r03.png,train/3/meter_01302026_s1_d3.png,,,3 +direct,3,train/3/meter_01302026_s1_d3__direct_r04.png,train/3/meter_01302026_s1_d3.png,,,3 +direct,3,train/3/meter_01302026_s1_d3__direct_r05.png,train/3/meter_01302026_s1_d3.png,,,3 +direct,3,train/3/meter_02162026_s1_d3__direct_r00.png,train/3/meter_02162026_s1_d3.png,,,3 +direct,3,train/3/meter_02162026_s1_d3__direct_r01.png,train/3/meter_02162026_s1_d3.png,,,3 +direct,3,train/3/meter_02162026_s1_d3__direct_r02.png,train/3/meter_02162026_s1_d3.png,,,3 +direct,3,train/3/meter_02162026_s1_d3__direct_r03.png,train/3/meter_02162026_s1_d3.png,,,3 +direct,3,train/3/meter_02162026_s1_d3__direct_r04.png,train/3/meter_02162026_s1_d3.png,,,3 +direct,3,train/3/meter_02162026_s1_d3__direct_r05.png,train/3/meter_02162026_s1_d3.png,,,3 +direct,3,train/3/meter_02192026_s1_d3__direct_r00.png,train/3/meter_02192026_s1_d3.png,,,3 +direct,3,train/3/meter_02192026_s1_d3__direct_r01.png,train/3/meter_02192026_s1_d3.png,,,3 +direct,3,train/3/meter_02192026_s1_d3__direct_r02.png,train/3/meter_02192026_s1_d3.png,,,3 +direct,3,train/3/meter_02192026_s1_d3__direct_r03.png,train/3/meter_02192026_s1_d3.png,,,3 +direct,3,train/3/meter_02192026_s1_d3__direct_r04.png,train/3/meter_02192026_s1_d3.png,,,3 +direct,3,train/3/meter_02192026_s1_d3__direct_r05.png,train/3/meter_02192026_s1_d3.png,,,3 +direct,3,train/3/meter_02202026_s1_d3__direct_r00.png,train/3/meter_02202026_s1_d3.png,,,3 +direct,3,train/3/meter_02202026_s1_d3__direct_r01.png,train/3/meter_02202026_s1_d3.png,,,3 +direct,3,train/3/meter_02202026_s1_d3__direct_r02.png,train/3/meter_02202026_s1_d3.png,,,3 +direct,3,train/3/meter_02202026_s1_d3__direct_r03.png,train/3/meter_02202026_s1_d3.png,,,3 +direct,3,train/3/meter_02202026_s1_d3__direct_r04.png,train/3/meter_02202026_s1_d3.png,,,3 +direct,3,train/3/meter_02202026_s1_d3__direct_r05.png,train/3/meter_02202026_s1_d3.png,,,3 +direct,3,train/3/meter_02202026_s3_d3__direct_r00.png,train/3/meter_02202026_s3_d3.png,,,3 +direct,3,train/3/meter_02202026_s3_d3__direct_r01.png,train/3/meter_02202026_s3_d3.png,,,3 +direct,3,train/3/meter_02202026_s3_d3__direct_r02.png,train/3/meter_02202026_s3_d3.png,,,3 +direct,3,train/3/meter_02202026_s3_d3__direct_r03.png,train/3/meter_02202026_s3_d3.png,,,3 +direct,3,train/3/meter_02202026_s3_d3__direct_r04.png,train/3/meter_02202026_s3_d3.png,,,3 +direct,3,train/3/meter_02202026_s3_d3__direct_r05.png,train/3/meter_02202026_s3_d3.png,,,3 +direct,3,train/3/meter_02242026_s1_d3__direct_r00.png,train/3/meter_02242026_s1_d3.png,,,3 +direct,3,train/3/meter_02242026_s1_d3__direct_r01.png,train/3/meter_02242026_s1_d3.png,,,3 +direct,3,train/3/meter_02242026_s1_d3__direct_r02.png,train/3/meter_02242026_s1_d3.png,,,3 +direct,3,train/3/meter_02242026_s1_d3__direct_r03.png,train/3/meter_02242026_s1_d3.png,,,3 +direct,3,train/3/meter_02242026_s1_d3__direct_r04.png,train/3/meter_02242026_s1_d3.png,,,3 +direct,3,train/3/meter_02242026_s1_d3__direct_r05.png,train/3/meter_02242026_s1_d3.png,,,3 +direct,3,train/3/meter_02272026_s1_d3__direct_r00.png,train/3/meter_02272026_s1_d3.png,,,3 +direct,3,train/3/meter_02272026_s1_d3__direct_r01.png,train/3/meter_02272026_s1_d3.png,,,3 +direct,3,train/3/meter_02272026_s1_d3__direct_r02.png,train/3/meter_02272026_s1_d3.png,,,3 +direct,3,train/3/meter_02272026_s1_d3__direct_r03.png,train/3/meter_02272026_s1_d3.png,,,3 +direct,3,train/3/meter_02272026_s1_d3__direct_r04.png,train/3/meter_02272026_s1_d3.png,,,3 +direct,3,train/3/meter_02272026_s1_d3__direct_r05.png,train/3/meter_02272026_s1_d3.png,,,3 +direct,3,train/3/meter_03032026_s1_d3__direct_r00.png,train/3/meter_03032026_s1_d3.png,,,3 +direct,3,train/3/meter_03032026_s1_d3__direct_r01.png,train/3/meter_03032026_s1_d3.png,,,3 +direct,3,train/3/meter_03032026_s1_d3__direct_r02.png,train/3/meter_03032026_s1_d3.png,,,3 +direct,3,train/3/meter_03032026_s1_d3__direct_r03.png,train/3/meter_03032026_s1_d3.png,,,3 +direct,3,train/3/meter_03032026_s1_d3__direct_r04.png,train/3/meter_03032026_s1_d3.png,,,3 +direct,3,train/3/meter_03032026_s1_d3__direct_r05.png,train/3/meter_03032026_s1_d3.png,,,3 +direct,4,train/4/meter_02242026_s3_d4__direct_r00.png,train/4/meter_02242026_s3_d4.png,,,4 +direct,4,train/4/meter_02242026_s3_d4__direct_r01.png,train/4/meter_02242026_s3_d4.png,,,4 +direct,4,train/4/meter_02242026_s3_d4__direct_r02.png,train/4/meter_02242026_s3_d4.png,,,4 +direct,4,train/4/meter_02242026_s3_d4__direct_r03.png,train/4/meter_02242026_s3_d4.png,,,4 +direct,4,train/4/meter_02242026_s3_d4__direct_r04.png,train/4/meter_02242026_s3_d4.png,,,4 +direct,4,train/4/meter_02242026_s3_d4__direct_r05.png,train/4/meter_02242026_s3_d4.png,,,4 +direct,4,train/4/meter_07012020_s3_d4__direct_r00.png,train/4/meter_07012020_s3_d4.png,,,4 +direct,4,train/4/meter_07012020_s3_d4__direct_r01.png,train/4/meter_07012020_s3_d4.png,,,4 +direct,4,train/4/meter_07012020_s3_d4__direct_r02.png,train/4/meter_07012020_s3_d4.png,,,4 +direct,4,train/4/meter_07012020_s3_d4__direct_r03.png,train/4/meter_07012020_s3_d4.png,,,4 +direct,4,train/4/meter_07012020_s3_d4__direct_r04.png,train/4/meter_07012020_s3_d4.png,,,4 +direct,4,train/4/meter_07012020_s3_d4__direct_r05.png,train/4/meter_07012020_s3_d4.png,,,4 +direct,5,train/5/meter_02272026_s3_d5__direct_r00.png,train/5/meter_02272026_s3_d5.png,,,5 +direct,5,train/5/meter_02272026_s3_d5__direct_r01.png,train/5/meter_02272026_s3_d5.png,,,5 +direct,5,train/5/meter_02272026_s3_d5__direct_r02.png,train/5/meter_02272026_s3_d5.png,,,5 +direct,5,train/5/meter_02272026_s3_d5__direct_r03.png,train/5/meter_02272026_s3_d5.png,,,5 +direct,5,train/5/meter_02272026_s3_d5__direct_r04.png,train/5/meter_02272026_s3_d5.png,,,5 +direct,5,train/5/meter_02272026_s3_d5__direct_r05.png,train/5/meter_02272026_s3_d5.png,,,5 +direct,6,train/6/meter_03032026_s3_d6__direct_r00.png,train/6/meter_03032026_s3_d6.png,,,6 +direct,6,train/6/meter_03032026_s3_d6__direct_r01.png,train/6/meter_03032026_s3_d6.png,,,6 +direct,6,train/6/meter_03032026_s3_d6__direct_r02.png,train/6/meter_03032026_s3_d6.png,,,6 +direct,6,train/6/meter_03032026_s3_d6__direct_r03.png,train/6/meter_03032026_s3_d6.png,,,6 +direct,6,train/6/meter_03032026_s3_d6__direct_r04.png,train/6/meter_03032026_s3_d6.png,,,6 +direct,6,train/6/meter_03032026_s3_d6__direct_r05.png,train/6/meter_03032026_s3_d6.png,,,6 +direct,7,train/7/meter_01302026_s3_d7__direct_r00.png,train/7/meter_01302026_s3_d7.png,,,7 +direct,7,train/7/meter_01302026_s3_d7__direct_r01.png,train/7/meter_01302026_s3_d7.png,,,7 +direct,7,train/7/meter_01302026_s3_d7__direct_r02.png,train/7/meter_01302026_s3_d7.png,,,7 +direct,7,train/7/meter_01302026_s3_d7__direct_r03.png,train/7/meter_01302026_s3_d7.png,,,7 +direct,7,train/7/meter_01302026_s3_d7__direct_r04.png,train/7/meter_01302026_s3_d7.png,,,7 +direct,7,train/7/meter_01302026_s3_d7__direct_r05.png,train/7/meter_01302026_s3_d7.png,,,7 +direct,7,train/7/meter_07012020_s1_d7__direct_r00.png,train/7/meter_07012020_s1_d7.png,,,7 +direct,7,train/7/meter_07012020_s1_d7__direct_r01.png,train/7/meter_07012020_s1_d7.png,,,7 +direct,7,train/7/meter_07012020_s1_d7__direct_r02.png,train/7/meter_07012020_s1_d7.png,,,7 +direct,7,train/7/meter_07012020_s1_d7__direct_r03.png,train/7/meter_07012020_s1_d7.png,,,7 +direct,7,train/7/meter_07012020_s1_d7__direct_r04.png,train/7/meter_07012020_s1_d7.png,,,7 +direct,7,train/7/meter_07012020_s1_d7__direct_r05.png,train/7/meter_07012020_s1_d7.png,,,7 +direct,7,train/7/meter_10092025_s2_d7__direct_r00.png,train/7/meter_10092025_s2_d7.png,,,7 +direct,7,train/7/meter_10092025_s2_d7__direct_r01.png,train/7/meter_10092025_s2_d7.png,,,7 +direct,7,train/7/meter_10092025_s2_d7__direct_r02.png,train/7/meter_10092025_s2_d7.png,,,7 +direct,7,train/7/meter_10092025_s2_d7__direct_r03.png,train/7/meter_10092025_s2_d7.png,,,7 +direct,7,train/7/meter_10092025_s2_d7__direct_r04.png,train/7/meter_10092025_s2_d7.png,,,7 +direct,7,train/7/meter_10092025_s2_d7__direct_r05.png,train/7/meter_10092025_s2_d7.png,,,7 +direct,8,train/8/meter_07012020_s2_d8__direct_r00.png,train/8/meter_07012020_s2_d8.png,,,8 +direct,8,train/8/meter_07012020_s2_d8__direct_r01.png,train/8/meter_07012020_s2_d8.png,,,8 +direct,8,train/8/meter_07012020_s2_d8__direct_r02.png,train/8/meter_07012020_s2_d8.png,,,8 +direct,8,train/8/meter_07012020_s2_d8__direct_r03.png,train/8/meter_07012020_s2_d8.png,,,8 +direct,8,train/8/meter_07012020_s2_d8__direct_r04.png,train/8/meter_07012020_s2_d8.png,,,8 +direct,8,train/8/meter_07012020_s2_d8__direct_r05.png,train/8/meter_07012020_s2_d8.png,,,8 +direct,8,train/8/meter_11112020_s1_d8__direct_r00.png,train/8/meter_11112020_s1_d8.png,,,8 +direct,8,train/8/meter_11112020_s1_d8__direct_r01.png,train/8/meter_11112020_s1_d8.png,,,8 +direct,8,train/8/meter_11112020_s1_d8__direct_r02.png,train/8/meter_11112020_s1_d8.png,,,8 +direct,8,train/8/meter_11112020_s1_d8__direct_r03.png,train/8/meter_11112020_s1_d8.png,,,8 +direct,8,train/8/meter_11112020_s1_d8__direct_r04.png,train/8/meter_11112020_s1_d8.png,,,8 +direct,8,train/8/meter_11112020_s1_d8__direct_r05.png,train/8/meter_11112020_s1_d8.png,,,8 +direct,9,train/9/meter_10092025_s3_d9__direct_r00.png,train/9/meter_10092025_s3_d9.png,,,9 +direct,9,train/9/meter_10092025_s3_d9__direct_r01.png,train/9/meter_10092025_s3_d9.png,,,9 +direct,9,train/9/meter_10092025_s3_d9__direct_r02.png,train/9/meter_10092025_s3_d9.png,,,9 +direct,9,train/9/meter_10092025_s3_d9__direct_r03.png,train/9/meter_10092025_s3_d9.png,,,9 +direct,9,train/9/meter_10092025_s3_d9__direct_r04.png,train/9/meter_10092025_s3_d9.png,,,9 +direct,9,train/9/meter_10092025_s3_d9__direct_r05.png,train/9/meter_10092025_s3_d9.png,,,9 +direct,9,train/9/meter_11112020_s3_d9__direct_r00.png,train/9/meter_11112020_s3_d9.png,,,9 +direct,9,train/9/meter_11112020_s3_d9__direct_r01.png,train/9/meter_11112020_s3_d9.png,,,9 +direct,9,train/9/meter_11112020_s3_d9__direct_r02.png,train/9/meter_11112020_s3_d9.png,,,9 +direct,9,train/9/meter_11112020_s3_d9__direct_r03.png,train/9/meter_11112020_s3_d9.png,,,9 +direct,9,train/9/meter_11112020_s3_d9__direct_r04.png,train/9/meter_11112020_s3_d9.png,,,9 +direct,9,train/9/meter_11112020_s3_d9__direct_r05.png,train/9/meter_11112020_s3_d9.png,,,9 +composed,8,train/8/synthetic_window_00000_s0_d8.png,train/8/meter_07012020_s2_d8.png|train/9/meter_11112020_s3_d9.png|train/4/meter_02242026_s3_d4.png|train/0/meter_01122026_s2_d0.png,0,0,8940 +composed,9,train/9/synthetic_window_00000_s1_d9.png,train/8/meter_07012020_s2_d8.png|train/9/meter_11112020_s3_d9.png|train/4/meter_02242026_s3_d4.png|train/0/meter_01122026_s2_d0.png,0,1,8940 +composed,4,train/4/synthetic_window_00000_s2_d4.png,train/8/meter_07012020_s2_d8.png|train/9/meter_11112020_s3_d9.png|train/4/meter_02242026_s3_d4.png|train/0/meter_01122026_s2_d0.png,0,2,8940 +composed,0,train/0/synthetic_window_00000_s3_d0.png,train/8/meter_07012020_s2_d8.png|train/9/meter_11112020_s3_d9.png|train/4/meter_02242026_s3_d4.png|train/0/meter_01122026_s2_d0.png,0,3,8940 +composed,7,train/7/synthetic_window_00001_s0_d7.png,train/7/meter_07012020_s1_d7.png|train/8/meter_11112020_s1_d8.png|train/6/meter_03032026_s3_d6.png|train/7/meter_07012020_s1_d7.png,1,0,7867 +composed,8,train/8/synthetic_window_00001_s1_d8.png,train/7/meter_07012020_s1_d7.png|train/8/meter_11112020_s1_d8.png|train/6/meter_03032026_s3_d6.png|train/7/meter_07012020_s1_d7.png,1,1,7867 +composed,6,train/6/synthetic_window_00001_s2_d6.png,train/7/meter_07012020_s1_d7.png|train/8/meter_11112020_s1_d8.png|train/6/meter_03032026_s3_d6.png|train/7/meter_07012020_s1_d7.png,1,2,7867 +composed,7,train/7/synthetic_window_00001_s3_d7.png,train/7/meter_07012020_s1_d7.png|train/8/meter_11112020_s1_d8.png|train/6/meter_03032026_s3_d6.png|train/7/meter_07012020_s1_d7.png,1,3,7867 +composed,6,train/6/synthetic_window_00002_s0_d6.png,train/6/meter_03032026_s3_d6.png|train/4/meter_02242026_s3_d4.png|train/1/meter_11112020_s0_d1.png|train/0/meter_01302026_s2_d0.png,2,0,6410 +composed,4,train/4/synthetic_window_00002_s1_d4.png,train/6/meter_03032026_s3_d6.png|train/4/meter_02242026_s3_d4.png|train/1/meter_11112020_s0_d1.png|train/0/meter_01302026_s2_d0.png,2,1,6410 +composed,1,train/1/synthetic_window_00002_s2_d1.png,train/6/meter_03032026_s3_d6.png|train/4/meter_02242026_s3_d4.png|train/1/meter_11112020_s0_d1.png|train/0/meter_01302026_s2_d0.png,2,2,6410 +composed,0,train/0/synthetic_window_00002_s3_d0.png,train/6/meter_03032026_s3_d6.png|train/4/meter_02242026_s3_d4.png|train/1/meter_11112020_s0_d1.png|train/0/meter_01302026_s2_d0.png,2,3,6410 +composed,1,train/1/synthetic_window_00003_s0_d1.png,train/1/meter_11112020_s2_d1.png|train/1/meter_11112020_s0_d1.png|train/9/meter_11112020_s3_d9.png|train/8/meter_07012020_s2_d8.png,3,0,1198 +composed,1,train/1/synthetic_window_00003_s1_d1.png,train/1/meter_11112020_s2_d1.png|train/1/meter_11112020_s0_d1.png|train/9/meter_11112020_s3_d9.png|train/8/meter_07012020_s2_d8.png,3,1,1198 +composed,9,train/9/synthetic_window_00003_s2_d9.png,train/1/meter_11112020_s2_d1.png|train/1/meter_11112020_s0_d1.png|train/9/meter_11112020_s3_d9.png|train/8/meter_07012020_s2_d8.png,3,2,1198 +composed,8,train/8/synthetic_window_00003_s3_d8.png,train/1/meter_11112020_s2_d1.png|train/1/meter_11112020_s0_d1.png|train/9/meter_11112020_s3_d9.png|train/8/meter_07012020_s2_d8.png,3,3,1198 +composed,6,train/6/synthetic_window_00004_s0_d6.png,train/6/meter_03032026_s3_d6.png|train/8/meter_07012020_s2_d8.png|train/6/meter_03032026_s3_d6.png|train/1/meter_02272026_s2_d1.png,4,0,6861 +composed,8,train/8/synthetic_window_00004_s1_d8.png,train/6/meter_03032026_s3_d6.png|train/8/meter_07012020_s2_d8.png|train/6/meter_03032026_s3_d6.png|train/1/meter_02272026_s2_d1.png,4,1,6861 +composed,6,train/6/synthetic_window_00004_s2_d6.png,train/6/meter_03032026_s3_d6.png|train/8/meter_07012020_s2_d8.png|train/6/meter_03032026_s3_d6.png|train/1/meter_02272026_s2_d1.png,4,2,6861 +composed,1,train/1/synthetic_window_00004_s3_d1.png,train/6/meter_03032026_s3_d6.png|train/8/meter_07012020_s2_d8.png|train/6/meter_03032026_s3_d6.png|train/1/meter_02272026_s2_d1.png,4,3,6861 +composed,0,train/0/synthetic_window_00005_s0_d0.png,train/0/meter_01302026_s2_d0.png|train/6/meter_03032026_s3_d6.png|train/7/meter_01302026_s3_d7.png|train/6/meter_03032026_s3_d6.png,5,0,0676 +composed,6,train/6/synthetic_window_00005_s1_d6.png,train/0/meter_01302026_s2_d0.png|train/6/meter_03032026_s3_d6.png|train/7/meter_01302026_s3_d7.png|train/6/meter_03032026_s3_d6.png,5,1,0676 +composed,7,train/7/synthetic_window_00005_s2_d7.png,train/0/meter_01302026_s2_d0.png|train/6/meter_03032026_s3_d6.png|train/7/meter_01302026_s3_d7.png|train/6/meter_03032026_s3_d6.png,5,2,0676 +composed,6,train/6/synthetic_window_00005_s3_d6.png,train/0/meter_01302026_s2_d0.png|train/6/meter_03032026_s3_d6.png|train/7/meter_01302026_s3_d7.png|train/6/meter_03032026_s3_d6.png,5,3,0676 +composed,4,train/4/synthetic_window_00006_s0_d4.png,train/4/meter_02242026_s3_d4.png|train/7/meter_10092025_s2_d7.png|train/1/meter_11112020_s2_d1.png|train/5/meter_02272026_s3_d5.png,6,0,4715 +composed,7,train/7/synthetic_window_00006_s1_d7.png,train/4/meter_02242026_s3_d4.png|train/7/meter_10092025_s2_d7.png|train/1/meter_11112020_s2_d1.png|train/5/meter_02272026_s3_d5.png,6,1,4715 +composed,1,train/1/synthetic_window_00006_s2_d1.png,train/4/meter_02242026_s3_d4.png|train/7/meter_10092025_s2_d7.png|train/1/meter_11112020_s2_d1.png|train/5/meter_02272026_s3_d5.png,6,2,4715 +composed,5,train/5/synthetic_window_00006_s3_d5.png,train/4/meter_02242026_s3_d4.png|train/7/meter_10092025_s2_d7.png|train/1/meter_11112020_s2_d1.png|train/5/meter_02272026_s3_d5.png,6,3,4715 +composed,8,train/8/synthetic_window_00007_s0_d8.png,train/8/meter_07012020_s2_d8.png|train/5/meter_02272026_s3_d5.png|train/7/meter_01302026_s3_d7.png|train/1/meter_11112020_s0_d1.png,7,0,8571 +composed,5,train/5/synthetic_window_00007_s1_d5.png,train/8/meter_07012020_s2_d8.png|train/5/meter_02272026_s3_d5.png|train/7/meter_01302026_s3_d7.png|train/1/meter_11112020_s0_d1.png,7,1,8571 +composed,7,train/7/synthetic_window_00007_s2_d7.png,train/8/meter_07012020_s2_d8.png|train/5/meter_02272026_s3_d5.png|train/7/meter_01302026_s3_d7.png|train/1/meter_11112020_s0_d1.png,7,2,8571 +composed,1,train/1/synthetic_window_00007_s3_d1.png,train/8/meter_07012020_s2_d8.png|train/5/meter_02272026_s3_d5.png|train/7/meter_01302026_s3_d7.png|train/1/meter_11112020_s0_d1.png,7,3,8571 +composed,6,train/6/synthetic_window_00008_s0_d6.png,train/6/meter_03032026_s3_d6.png|train/6/meter_03032026_s3_d6.png|train/8/meter_07012020_s2_d8.png|train/0/meter_01132026_s2_d0.png,8,0,6680 +composed,6,train/6/synthetic_window_00008_s1_d6.png,train/6/meter_03032026_s3_d6.png|train/6/meter_03032026_s3_d6.png|train/8/meter_07012020_s2_d8.png|train/0/meter_01132026_s2_d0.png,8,1,6680 +composed,8,train/8/synthetic_window_00008_s2_d8.png,train/6/meter_03032026_s3_d6.png|train/6/meter_03032026_s3_d6.png|train/8/meter_07012020_s2_d8.png|train/0/meter_01132026_s2_d0.png,8,2,6680 +composed,0,train/0/synthetic_window_00008_s3_d0.png,train/6/meter_03032026_s3_d6.png|train/6/meter_03032026_s3_d6.png|train/8/meter_07012020_s2_d8.png|train/0/meter_01132026_s2_d0.png,8,3,6680 +composed,2,train/2/synthetic_window_00009_s0_d2.png,train/2/meter_02272026_s0_d2.png|train/0/meter_01132026_s2_d0.png|train/1/meter_02202026_s2_d1.png|train/4/meter_02242026_s3_d4.png,9,0,2014 +composed,0,train/0/synthetic_window_00009_s1_d0.png,train/2/meter_02272026_s0_d2.png|train/0/meter_01132026_s2_d0.png|train/1/meter_02202026_s2_d1.png|train/4/meter_02242026_s3_d4.png,9,1,2014 +composed,1,train/1/synthetic_window_00009_s2_d1.png,train/2/meter_02272026_s0_d2.png|train/0/meter_01132026_s2_d0.png|train/1/meter_02202026_s2_d1.png|train/4/meter_02242026_s3_d4.png,9,2,2014 +composed,4,train/4/synthetic_window_00009_s3_d4.png,train/2/meter_02272026_s0_d2.png|train/0/meter_01132026_s2_d0.png|train/1/meter_02202026_s2_d1.png|train/4/meter_02242026_s3_d4.png,9,3,2014 +composed,5,train/5/synthetic_window_00010_s0_d5.png,train/5/meter_02272026_s3_d5.png|train/1/meter_03032026_s2_d1.png|train/0/meter_01132026_s2_d0.png|train/4/meter_02242026_s3_d4.png,10,0,5104 +composed,1,train/1/synthetic_window_00010_s1_d1.png,train/5/meter_02272026_s3_d5.png|train/1/meter_03032026_s2_d1.png|train/0/meter_01132026_s2_d0.png|train/4/meter_02242026_s3_d4.png,10,1,5104 +composed,0,train/0/synthetic_window_00010_s2_d0.png,train/5/meter_02272026_s3_d5.png|train/1/meter_03032026_s2_d1.png|train/0/meter_01132026_s2_d0.png|train/4/meter_02242026_s3_d4.png,10,2,5104 +composed,4,train/4/synthetic_window_00010_s3_d4.png,train/5/meter_02272026_s3_d5.png|train/1/meter_03032026_s2_d1.png|train/0/meter_01132026_s2_d0.png|train/4/meter_02242026_s3_d4.png,10,3,5104 +composed,0,train/0/synthetic_window_00011_s0_d0.png,train/0/meter_01132026_s2_d0.png|train/0/meter_01132026_s2_d0.png|train/5/meter_02272026_s3_d5.png|train/1/meter_07012020_s0_d1.png,11,0,0051 +composed,0,train/0/synthetic_window_00011_s1_d0.png,train/0/meter_01132026_s2_d0.png|train/0/meter_01132026_s2_d0.png|train/5/meter_02272026_s3_d5.png|train/1/meter_07012020_s0_d1.png,11,1,0051 +composed,5,train/5/synthetic_window_00011_s2_d5.png,train/0/meter_01132026_s2_d0.png|train/0/meter_01132026_s2_d0.png|train/5/meter_02272026_s3_d5.png|train/1/meter_07012020_s0_d1.png,11,2,0051 +composed,1,train/1/synthetic_window_00011_s3_d1.png,train/0/meter_01132026_s2_d0.png|train/0/meter_01132026_s2_d0.png|train/5/meter_02272026_s3_d5.png|train/1/meter_07012020_s0_d1.png,11,3,0051 +composed,4,train/4/synthetic_window_00012_s0_d4.png,train/4/meter_02242026_s3_d4.png|train/0/meter_01122026_s2_d0.png|train/6/meter_03032026_s3_d6.png|train/5/meter_02272026_s3_d5.png,12,0,4065 +composed,0,train/0/synthetic_window_00012_s1_d0.png,train/4/meter_02242026_s3_d4.png|train/0/meter_01122026_s2_d0.png|train/6/meter_03032026_s3_d6.png|train/5/meter_02272026_s3_d5.png,12,1,4065 +composed,6,train/6/synthetic_window_00012_s2_d6.png,train/4/meter_02242026_s3_d4.png|train/0/meter_01122026_s2_d0.png|train/6/meter_03032026_s3_d6.png|train/5/meter_02272026_s3_d5.png,12,2,4065 +composed,5,train/5/synthetic_window_00012_s3_d5.png,train/4/meter_02242026_s3_d4.png|train/0/meter_01122026_s2_d0.png|train/6/meter_03032026_s3_d6.png|train/5/meter_02272026_s3_d5.png,12,3,4065 +composed,1,train/1/synthetic_window_00013_s0_d1.png,train/1/meter_02242026_s2_d1.png|train/1/meter_07012020_s0_d1.png|train/5/meter_02272026_s3_d5.png|train/7/meter_10092025_s2_d7.png,13,0,1157 +composed,1,train/1/synthetic_window_00013_s1_d1.png,train/1/meter_02242026_s2_d1.png|train/1/meter_07012020_s0_d1.png|train/5/meter_02272026_s3_d5.png|train/7/meter_10092025_s2_d7.png,13,1,1157 +composed,5,train/5/synthetic_window_00013_s2_d5.png,train/1/meter_02242026_s2_d1.png|train/1/meter_07012020_s0_d1.png|train/5/meter_02272026_s3_d5.png|train/7/meter_10092025_s2_d7.png,13,2,1157 +composed,7,train/7/synthetic_window_00013_s3_d7.png,train/1/meter_02242026_s2_d1.png|train/1/meter_07012020_s0_d1.png|train/5/meter_02272026_s3_d5.png|train/7/meter_10092025_s2_d7.png,13,3,1157 +composed,1,train/1/synthetic_window_00014_s0_d1.png,train/1/meter_02272026_s2_d1.png|train/7/meter_01302026_s3_d7.png|train/9/meter_10092025_s3_d9.png|train/3/meter_02202026_s3_d3.png,14,0,1793 +composed,7,train/7/synthetic_window_00014_s1_d7.png,train/1/meter_02272026_s2_d1.png|train/7/meter_01302026_s3_d7.png|train/9/meter_10092025_s3_d9.png|train/3/meter_02202026_s3_d3.png,14,1,1793 +composed,9,train/9/synthetic_window_00014_s2_d9.png,train/1/meter_02272026_s2_d1.png|train/7/meter_01302026_s3_d7.png|train/9/meter_10092025_s3_d9.png|train/3/meter_02202026_s3_d3.png,14,2,1793 +composed,3,train/3/synthetic_window_00014_s3_d3.png,train/1/meter_02272026_s2_d1.png|train/7/meter_01302026_s3_d7.png|train/9/meter_10092025_s3_d9.png|train/3/meter_02202026_s3_d3.png,14,3,1793 +composed,7,train/7/synthetic_window_00015_s0_d7.png,train/7/meter_01302026_s3_d7.png|train/7/meter_07012020_s1_d7.png|train/0/meter_01302026_s2_d0.png|train/4/meter_07012020_s3_d4.png,15,0,7704 +composed,7,train/7/synthetic_window_00015_s1_d7.png,train/7/meter_01302026_s3_d7.png|train/7/meter_07012020_s1_d7.png|train/0/meter_01302026_s2_d0.png|train/4/meter_07012020_s3_d4.png,15,1,7704 +composed,0,train/0/synthetic_window_00015_s2_d0.png,train/7/meter_01302026_s3_d7.png|train/7/meter_07012020_s1_d7.png|train/0/meter_01302026_s2_d0.png|train/4/meter_07012020_s3_d4.png,15,2,7704 +composed,4,train/4/synthetic_window_00015_s3_d4.png,train/7/meter_01302026_s3_d7.png|train/7/meter_07012020_s1_d7.png|train/0/meter_01302026_s2_d0.png|train/4/meter_07012020_s3_d4.png,15,3,7704 +composed,5,train/5/synthetic_window_00016_s0_d5.png,train/5/meter_02272026_s3_d5.png|train/3/meter_01302026_s1_d3.png|train/6/meter_03032026_s3_d6.png|train/9/meter_10092025_s3_d9.png,16,0,5369 +composed,3,train/3/synthetic_window_00016_s1_d3.png,train/5/meter_02272026_s3_d5.png|train/3/meter_01302026_s1_d3.png|train/6/meter_03032026_s3_d6.png|train/9/meter_10092025_s3_d9.png,16,1,5369 +composed,6,train/6/synthetic_window_00016_s2_d6.png,train/5/meter_02272026_s3_d5.png|train/3/meter_01302026_s1_d3.png|train/6/meter_03032026_s3_d6.png|train/9/meter_10092025_s3_d9.png,16,2,5369 +composed,9,train/9/synthetic_window_00016_s3_d9.png,train/5/meter_02272026_s3_d5.png|train/3/meter_01302026_s1_d3.png|train/6/meter_03032026_s3_d6.png|train/9/meter_10092025_s3_d9.png,16,3,5369 +composed,9,train/9/synthetic_window_00017_s0_d9.png,train/9/meter_11112020_s3_d9.png|train/0/meter_01132026_s2_d0.png|train/3/meter_03032026_s1_d3.png|train/9/meter_10092025_s3_d9.png,17,0,9039 +composed,0,train/0/synthetic_window_00017_s1_d0.png,train/9/meter_11112020_s3_d9.png|train/0/meter_01132026_s2_d0.png|train/3/meter_03032026_s1_d3.png|train/9/meter_10092025_s3_d9.png,17,1,9039 +composed,3,train/3/synthetic_window_00017_s2_d3.png,train/9/meter_11112020_s3_d9.png|train/0/meter_01132026_s2_d0.png|train/3/meter_03032026_s1_d3.png|train/9/meter_10092025_s3_d9.png,17,2,9039 +composed,9,train/9/synthetic_window_00017_s3_d9.png,train/9/meter_11112020_s3_d9.png|train/0/meter_01132026_s2_d0.png|train/3/meter_03032026_s1_d3.png|train/9/meter_10092025_s3_d9.png,17,3,9039 +composed,6,train/6/synthetic_window_00018_s0_d6.png,train/6/meter_03032026_s3_d6.png|train/0/meter_01302026_s2_d0.png|train/2/meter_03032026_s0_d2.png|train/0/meter_01132026_s2_d0.png,18,0,6020 +composed,0,train/0/synthetic_window_00018_s1_d0.png,train/6/meter_03032026_s3_d6.png|train/0/meter_01302026_s2_d0.png|train/2/meter_03032026_s0_d2.png|train/0/meter_01132026_s2_d0.png,18,1,6020 +composed,2,train/2/synthetic_window_00018_s2_d2.png,train/6/meter_03032026_s3_d6.png|train/0/meter_01302026_s2_d0.png|train/2/meter_03032026_s0_d2.png|train/0/meter_01132026_s2_d0.png,18,2,6020 +composed,0,train/0/synthetic_window_00018_s3_d0.png,train/6/meter_03032026_s3_d6.png|train/0/meter_01302026_s2_d0.png|train/2/meter_03032026_s0_d2.png|train/0/meter_01132026_s2_d0.png,18,3,6020 +composed,7,train/7/synthetic_window_00019_s0_d7.png,train/7/meter_01302026_s3_d7.png|train/7/meter_01302026_s3_d7.png|train/8/meter_07012020_s2_d8.png|train/1/meter_07012020_s0_d1.png,19,0,7781 +composed,7,train/7/synthetic_window_00019_s1_d7.png,train/7/meter_01302026_s3_d7.png|train/7/meter_01302026_s3_d7.png|train/8/meter_07012020_s2_d8.png|train/1/meter_07012020_s0_d1.png,19,1,7781 +composed,8,train/8/synthetic_window_00019_s2_d8.png,train/7/meter_01302026_s3_d7.png|train/7/meter_01302026_s3_d7.png|train/8/meter_07012020_s2_d8.png|train/1/meter_07012020_s0_d1.png,19,2,7781 +composed,1,train/1/synthetic_window_00019_s3_d1.png,train/7/meter_01302026_s3_d7.png|train/7/meter_01302026_s3_d7.png|train/8/meter_07012020_s2_d8.png|train/1/meter_07012020_s0_d1.png,19,3,7781 +composed,7,train/7/synthetic_window_00020_s0_d7.png,train/7/meter_01302026_s3_d7.png|train/3/meter_02192026_s1_d3.png|train/6/meter_03032026_s3_d6.png|train/8/meter_07012020_s2_d8.png,20,0,7368 +composed,3,train/3/synthetic_window_00020_s1_d3.png,train/7/meter_01302026_s3_d7.png|train/3/meter_02192026_s1_d3.png|train/6/meter_03032026_s3_d6.png|train/8/meter_07012020_s2_d8.png,20,1,7368 +composed,6,train/6/synthetic_window_00020_s2_d6.png,train/7/meter_01302026_s3_d7.png|train/3/meter_02192026_s1_d3.png|train/6/meter_03032026_s3_d6.png|train/8/meter_07012020_s2_d8.png,20,2,7368 +composed,8,train/8/synthetic_window_00020_s3_d8.png,train/7/meter_01302026_s3_d7.png|train/3/meter_02192026_s1_d3.png|train/6/meter_03032026_s3_d6.png|train/8/meter_07012020_s2_d8.png,20,3,7368 +composed,7,train/7/synthetic_window_00021_s0_d7.png,train/7/meter_07012020_s1_d7.png|train/0/meter_01132026_s2_d0.png|train/7/meter_10092025_s2_d7.png|train/5/meter_02272026_s3_d5.png,21,0,7075 +composed,0,train/0/synthetic_window_00021_s1_d0.png,train/7/meter_07012020_s1_d7.png|train/0/meter_01132026_s2_d0.png|train/7/meter_10092025_s2_d7.png|train/5/meter_02272026_s3_d5.png,21,1,7075 +composed,7,train/7/synthetic_window_00021_s2_d7.png,train/7/meter_07012020_s1_d7.png|train/0/meter_01132026_s2_d0.png|train/7/meter_10092025_s2_d7.png|train/5/meter_02272026_s3_d5.png,21,2,7075 +composed,5,train/5/synthetic_window_00021_s3_d5.png,train/7/meter_07012020_s1_d7.png|train/0/meter_01132026_s2_d0.png|train/7/meter_10092025_s2_d7.png|train/5/meter_02272026_s3_d5.png,21,3,7075 +composed,9,train/9/synthetic_window_00022_s0_d9.png,train/9/meter_10092025_s3_d9.png|train/4/meter_07012020_s3_d4.png|train/2/meter_02192026_s3_d2.png|train/2/meter_02162026_s3_d2.png,22,0,9422 +composed,4,train/4/synthetic_window_00022_s1_d4.png,train/9/meter_10092025_s3_d9.png|train/4/meter_07012020_s3_d4.png|train/2/meter_02192026_s3_d2.png|train/2/meter_02162026_s3_d2.png,22,1,9422 +composed,2,train/2/synthetic_window_00022_s2_d2.png,train/9/meter_10092025_s3_d9.png|train/4/meter_07012020_s3_d4.png|train/2/meter_02192026_s3_d2.png|train/2/meter_02162026_s3_d2.png,22,2,9422 +composed,2,train/2/synthetic_window_00022_s3_d2.png,train/9/meter_10092025_s3_d9.png|train/4/meter_07012020_s3_d4.png|train/2/meter_02192026_s3_d2.png|train/2/meter_02162026_s3_d2.png,22,3,9422 +composed,4,train/4/synthetic_window_00023_s0_d4.png,train/4/meter_02242026_s3_d4.png|train/8/meter_11112020_s1_d8.png|train/0/meter_01132026_s2_d0.png|train/7/meter_10092025_s2_d7.png,23,0,4807 +composed,8,train/8/synthetic_window_00023_s1_d8.png,train/4/meter_02242026_s3_d4.png|train/8/meter_11112020_s1_d8.png|train/0/meter_01132026_s2_d0.png|train/7/meter_10092025_s2_d7.png,23,1,4807 +composed,0,train/0/synthetic_window_00023_s2_d0.png,train/4/meter_02242026_s3_d4.png|train/8/meter_11112020_s1_d8.png|train/0/meter_01132026_s2_d0.png|train/7/meter_10092025_s2_d7.png,23,2,4807 +composed,7,train/7/synthetic_window_00023_s3_d7.png,train/4/meter_02242026_s3_d4.png|train/8/meter_11112020_s1_d8.png|train/0/meter_01132026_s2_d0.png|train/7/meter_10092025_s2_d7.png,23,3,4807 +composed,7,train/7/synthetic_window_00024_s0_d7.png,train/7/meter_07012020_s1_d7.png|train/8/meter_07012020_s2_d8.png|train/5/meter_02272026_s3_d5.png|train/6/meter_03032026_s3_d6.png,24,0,7856 +composed,8,train/8/synthetic_window_00024_s1_d8.png,train/7/meter_07012020_s1_d7.png|train/8/meter_07012020_s2_d8.png|train/5/meter_02272026_s3_d5.png|train/6/meter_03032026_s3_d6.png,24,1,7856 +composed,5,train/5/synthetic_window_00024_s2_d5.png,train/7/meter_07012020_s1_d7.png|train/8/meter_07012020_s2_d8.png|train/5/meter_02272026_s3_d5.png|train/6/meter_03032026_s3_d6.png,24,2,7856 +composed,6,train/6/synthetic_window_00024_s3_d6.png,train/7/meter_07012020_s1_d7.png|train/8/meter_07012020_s2_d8.png|train/5/meter_02272026_s3_d5.png|train/6/meter_03032026_s3_d6.png,24,3,7856 +composed,9,train/9/synthetic_window_00025_s0_d9.png,train/9/meter_11112020_s3_d9.png|train/7/meter_10092025_s2_d7.png|train/5/meter_02272026_s3_d5.png|train/2/meter_03032026_s0_d2.png,25,0,9752 +composed,7,train/7/synthetic_window_00025_s1_d7.png,train/9/meter_11112020_s3_d9.png|train/7/meter_10092025_s2_d7.png|train/5/meter_02272026_s3_d5.png|train/2/meter_03032026_s0_d2.png,25,1,9752 +composed,5,train/5/synthetic_window_00025_s2_d5.png,train/9/meter_11112020_s3_d9.png|train/7/meter_10092025_s2_d7.png|train/5/meter_02272026_s3_d5.png|train/2/meter_03032026_s0_d2.png,25,2,9752 +composed,2,train/2/synthetic_window_00025_s3_d2.png,train/9/meter_11112020_s3_d9.png|train/7/meter_10092025_s2_d7.png|train/5/meter_02272026_s3_d5.png|train/2/meter_03032026_s0_d2.png,25,3,9752 +composed,1,train/1/synthetic_window_00026_s0_d1.png,train/1/meter_02272026_s2_d1.png|train/0/meter_01122026_s2_d0.png|train/6/meter_03032026_s3_d6.png|train/1/meter_02192026_s2_d1.png,26,0,1061 +composed,0,train/0/synthetic_window_00026_s1_d0.png,train/1/meter_02272026_s2_d1.png|train/0/meter_01122026_s2_d0.png|train/6/meter_03032026_s3_d6.png|train/1/meter_02192026_s2_d1.png,26,1,1061 +composed,6,train/6/synthetic_window_00026_s2_d6.png,train/1/meter_02272026_s2_d1.png|train/0/meter_01122026_s2_d0.png|train/6/meter_03032026_s3_d6.png|train/1/meter_02192026_s2_d1.png,26,2,1061 +composed,1,train/1/synthetic_window_00026_s3_d1.png,train/1/meter_02272026_s2_d1.png|train/0/meter_01122026_s2_d0.png|train/6/meter_03032026_s3_d6.png|train/1/meter_02192026_s2_d1.png,26,3,1061 +composed,1,train/1/synthetic_window_00027_s0_d1.png,train/1/meter_03032026_s2_d1.png|train/2/meter_01132026_s0_d2.png|train/8/meter_07012020_s2_d8.png|train/1/meter_02202026_s2_d1.png,27,0,1281 +composed,2,train/2/synthetic_window_00027_s1_d2.png,train/1/meter_03032026_s2_d1.png|train/2/meter_01132026_s0_d2.png|train/8/meter_07012020_s2_d8.png|train/1/meter_02202026_s2_d1.png,27,1,1281 +composed,8,train/8/synthetic_window_00027_s2_d8.png,train/1/meter_03032026_s2_d1.png|train/2/meter_01132026_s0_d2.png|train/8/meter_07012020_s2_d8.png|train/1/meter_02202026_s2_d1.png,27,2,1281 +composed,1,train/1/synthetic_window_00027_s3_d1.png,train/1/meter_03032026_s2_d1.png|train/2/meter_01132026_s0_d2.png|train/8/meter_07012020_s2_d8.png|train/1/meter_02202026_s2_d1.png,27,3,1281 +composed,0,train/0/synthetic_window_00028_s0_d0.png,train/0/meter_01302026_s2_d0.png|train/1/meter_02242026_s2_d1.png|train/2/meter_01122026_s3_d2.png|train/6/meter_03032026_s3_d6.png,28,0,0126 +composed,1,train/1/synthetic_window_00028_s1_d1.png,train/0/meter_01302026_s2_d0.png|train/1/meter_02242026_s2_d1.png|train/2/meter_01122026_s3_d2.png|train/6/meter_03032026_s3_d6.png,28,1,0126 +composed,2,train/2/synthetic_window_00028_s2_d2.png,train/0/meter_01302026_s2_d0.png|train/1/meter_02242026_s2_d1.png|train/2/meter_01122026_s3_d2.png|train/6/meter_03032026_s3_d6.png,28,2,0126 +composed,6,train/6/synthetic_window_00028_s3_d6.png,train/0/meter_01302026_s2_d0.png|train/1/meter_02242026_s2_d1.png|train/2/meter_01122026_s3_d2.png|train/6/meter_03032026_s3_d6.png,28,3,0126 +composed,5,train/5/synthetic_window_00029_s0_d5.png,train/5/meter_02272026_s3_d5.png|train/1/meter_02242026_s2_d1.png|train/4/meter_02242026_s3_d4.png|train/0/meter_01132026_s2_d0.png,29,0,5140 +composed,1,train/1/synthetic_window_00029_s1_d1.png,train/5/meter_02272026_s3_d5.png|train/1/meter_02242026_s2_d1.png|train/4/meter_02242026_s3_d4.png|train/0/meter_01132026_s2_d0.png,29,1,5140 +composed,4,train/4/synthetic_window_00029_s2_d4.png,train/5/meter_02272026_s3_d5.png|train/1/meter_02242026_s2_d1.png|train/4/meter_02242026_s3_d4.png|train/0/meter_01132026_s2_d0.png,29,2,5140 +composed,0,train/0/synthetic_window_00029_s3_d0.png,train/5/meter_02272026_s3_d5.png|train/1/meter_02242026_s2_d1.png|train/4/meter_02242026_s3_d4.png|train/0/meter_01132026_s2_d0.png,29,3,5140 +composed,6,train/6/synthetic_window_00030_s0_d6.png,train/6/meter_03032026_s3_d6.png|train/2/meter_01132026_s0_d2.png|train/0/meter_01302026_s2_d0.png|train/1/meter_03032026_s2_d1.png,30,0,6201 +composed,2,train/2/synthetic_window_00030_s1_d2.png,train/6/meter_03032026_s3_d6.png|train/2/meter_01132026_s0_d2.png|train/0/meter_01302026_s2_d0.png|train/1/meter_03032026_s2_d1.png,30,1,6201 +composed,0,train/0/synthetic_window_00030_s2_d0.png,train/6/meter_03032026_s3_d6.png|train/2/meter_01132026_s0_d2.png|train/0/meter_01302026_s2_d0.png|train/1/meter_03032026_s2_d1.png,30,2,6201 +composed,1,train/1/synthetic_window_00030_s3_d1.png,train/6/meter_03032026_s3_d6.png|train/2/meter_01132026_s0_d2.png|train/0/meter_01302026_s2_d0.png|train/1/meter_03032026_s2_d1.png,30,3,6201 +composed,4,train/4/synthetic_window_00031_s0_d4.png,train/4/meter_07012020_s3_d4.png|train/6/meter_03032026_s3_d6.png|train/2/meter_02162026_s3_d2.png|train/7/meter_07012020_s1_d7.png,31,0,4627 +composed,6,train/6/synthetic_window_00031_s1_d6.png,train/4/meter_07012020_s3_d4.png|train/6/meter_03032026_s3_d6.png|train/2/meter_02162026_s3_d2.png|train/7/meter_07012020_s1_d7.png,31,1,4627 +composed,2,train/2/synthetic_window_00031_s2_d2.png,train/4/meter_07012020_s3_d4.png|train/6/meter_03032026_s3_d6.png|train/2/meter_02162026_s3_d2.png|train/7/meter_07012020_s1_d7.png,31,2,4627 +composed,7,train/7/synthetic_window_00031_s3_d7.png,train/4/meter_07012020_s3_d4.png|train/6/meter_03032026_s3_d6.png|train/2/meter_02162026_s3_d2.png|train/7/meter_07012020_s1_d7.png,31,3,4627 +composed,1,train/1/synthetic_window_00032_s0_d1.png,train/1/meter_02162026_s2_d1.png|train/4/meter_02242026_s3_d4.png|train/0/meter_01122026_s2_d0.png|train/2/meter_01122026_s3_d2.png,32,0,1402 +composed,4,train/4/synthetic_window_00032_s1_d4.png,train/1/meter_02162026_s2_d1.png|train/4/meter_02242026_s3_d4.png|train/0/meter_01122026_s2_d0.png|train/2/meter_01122026_s3_d2.png,32,1,1402 +composed,0,train/0/synthetic_window_00032_s2_d0.png,train/1/meter_02162026_s2_d1.png|train/4/meter_02242026_s3_d4.png|train/0/meter_01122026_s2_d0.png|train/2/meter_01122026_s3_d2.png,32,2,1402 +composed,2,train/2/synthetic_window_00032_s3_d2.png,train/1/meter_02162026_s2_d1.png|train/4/meter_02242026_s3_d4.png|train/0/meter_01122026_s2_d0.png|train/2/meter_01122026_s3_d2.png,32,3,1402 +composed,6,train/6/synthetic_window_00033_s0_d6.png,train/6/meter_03032026_s3_d6.png|train/7/meter_01302026_s3_d7.png|train/8/meter_11112020_s1_d8.png|train/6/meter_03032026_s3_d6.png,33,0,6786 +composed,7,train/7/synthetic_window_00033_s1_d7.png,train/6/meter_03032026_s3_d6.png|train/7/meter_01302026_s3_d7.png|train/8/meter_11112020_s1_d8.png|train/6/meter_03032026_s3_d6.png,33,1,6786 +composed,8,train/8/synthetic_window_00033_s2_d8.png,train/6/meter_03032026_s3_d6.png|train/7/meter_01302026_s3_d7.png|train/8/meter_11112020_s1_d8.png|train/6/meter_03032026_s3_d6.png,33,2,6786 +composed,6,train/6/synthetic_window_00033_s3_d6.png,train/6/meter_03032026_s3_d6.png|train/7/meter_01302026_s3_d7.png|train/8/meter_11112020_s1_d8.png|train/6/meter_03032026_s3_d6.png,33,3,6786 +composed,4,train/4/synthetic_window_00034_s0_d4.png,train/4/meter_07012020_s3_d4.png|train/5/meter_02272026_s3_d5.png|train/1/meter_02202026_s2_d1.png|train/2/meter_10092025_s1_d2.png,34,0,4512 +composed,5,train/5/synthetic_window_00034_s1_d5.png,train/4/meter_07012020_s3_d4.png|train/5/meter_02272026_s3_d5.png|train/1/meter_02202026_s2_d1.png|train/2/meter_10092025_s1_d2.png,34,1,4512 +composed,1,train/1/synthetic_window_00034_s2_d1.png,train/4/meter_07012020_s3_d4.png|train/5/meter_02272026_s3_d5.png|train/1/meter_02202026_s2_d1.png|train/2/meter_10092025_s1_d2.png,34,2,4512 +composed,2,train/2/synthetic_window_00034_s3_d2.png,train/4/meter_07012020_s3_d4.png|train/5/meter_02272026_s3_d5.png|train/1/meter_02202026_s2_d1.png|train/2/meter_10092025_s1_d2.png,34,3,4512 +composed,8,train/8/synthetic_window_00035_s0_d8.png,train/8/meter_07012020_s2_d8.png|train/7/meter_01302026_s3_d7.png|train/2/meter_02162026_s3_d2.png|train/0/meter_01132026_s2_d0.png,35,0,8720 +composed,7,train/7/synthetic_window_00035_s1_d7.png,train/8/meter_07012020_s2_d8.png|train/7/meter_01302026_s3_d7.png|train/2/meter_02162026_s3_d2.png|train/0/meter_01132026_s2_d0.png,35,1,8720 +composed,2,train/2/synthetic_window_00035_s2_d2.png,train/8/meter_07012020_s2_d8.png|train/7/meter_01302026_s3_d7.png|train/2/meter_02162026_s3_d2.png|train/0/meter_01132026_s2_d0.png,35,2,8720 +composed,0,train/0/synthetic_window_00035_s3_d0.png,train/8/meter_07012020_s2_d8.png|train/7/meter_01302026_s3_d7.png|train/2/meter_02162026_s3_d2.png|train/0/meter_01132026_s2_d0.png,35,3,8720 +composed,5,train/5/synthetic_window_00036_s0_d5.png,train/5/meter_02272026_s3_d5.png|train/6/meter_03032026_s3_d6.png|train/5/meter_02272026_s3_d5.png|train/3/meter_02202026_s1_d3.png,36,0,5653 +composed,6,train/6/synthetic_window_00036_s1_d6.png,train/5/meter_02272026_s3_d5.png|train/6/meter_03032026_s3_d6.png|train/5/meter_02272026_s3_d5.png|train/3/meter_02202026_s1_d3.png,36,1,5653 +composed,5,train/5/synthetic_window_00036_s2_d5.png,train/5/meter_02272026_s3_d5.png|train/6/meter_03032026_s3_d6.png|train/5/meter_02272026_s3_d5.png|train/3/meter_02202026_s1_d3.png,36,2,5653 +composed,3,train/3/synthetic_window_00036_s3_d3.png,train/5/meter_02272026_s3_d5.png|train/6/meter_03032026_s3_d6.png|train/5/meter_02272026_s3_d5.png|train/3/meter_02202026_s1_d3.png,36,3,5653 +composed,1,train/1/synthetic_window_00037_s0_d1.png,train/1/meter_07012020_s0_d1.png|train/1/meter_03032026_s2_d1.png|train/8/meter_07012020_s2_d8.png|train/1/meter_02272026_s2_d1.png,37,0,1181 +composed,1,train/1/synthetic_window_00037_s1_d1.png,train/1/meter_07012020_s0_d1.png|train/1/meter_03032026_s2_d1.png|train/8/meter_07012020_s2_d8.png|train/1/meter_02272026_s2_d1.png,37,1,1181 +composed,8,train/8/synthetic_window_00037_s2_d8.png,train/1/meter_07012020_s0_d1.png|train/1/meter_03032026_s2_d1.png|train/8/meter_07012020_s2_d8.png|train/1/meter_02272026_s2_d1.png,37,2,1181 +composed,1,train/1/synthetic_window_00037_s3_d1.png,train/1/meter_07012020_s0_d1.png|train/1/meter_03032026_s2_d1.png|train/8/meter_07012020_s2_d8.png|train/1/meter_02272026_s2_d1.png,37,3,1181 +composed,6,train/6/synthetic_window_00038_s0_d6.png,train/6/meter_03032026_s3_d6.png|train/8/meter_11112020_s1_d8.png|train/9/meter_11112020_s3_d9.png|train/1/meter_02162026_s2_d1.png,38,0,6891 +composed,8,train/8/synthetic_window_00038_s1_d8.png,train/6/meter_03032026_s3_d6.png|train/8/meter_11112020_s1_d8.png|train/9/meter_11112020_s3_d9.png|train/1/meter_02162026_s2_d1.png,38,1,6891 +composed,9,train/9/synthetic_window_00038_s2_d9.png,train/6/meter_03032026_s3_d6.png|train/8/meter_11112020_s1_d8.png|train/9/meter_11112020_s3_d9.png|train/1/meter_02162026_s2_d1.png,38,2,6891 +composed,1,train/1/synthetic_window_00038_s3_d1.png,train/6/meter_03032026_s3_d6.png|train/8/meter_11112020_s1_d8.png|train/9/meter_11112020_s3_d9.png|train/1/meter_02162026_s2_d1.png,38,3,6891 +composed,1,train/1/synthetic_window_00039_s0_d1.png,train/1/meter_11112020_s0_d1.png|train/2/meter_02192026_s3_d2.png|train/6/meter_03032026_s3_d6.png|train/2/meter_10092025_s1_d2.png,39,0,1262 +composed,2,train/2/synthetic_window_00039_s1_d2.png,train/1/meter_11112020_s0_d1.png|train/2/meter_02192026_s3_d2.png|train/6/meter_03032026_s3_d6.png|train/2/meter_10092025_s1_d2.png,39,1,1262 +composed,6,train/6/synthetic_window_00039_s2_d6.png,train/1/meter_11112020_s0_d1.png|train/2/meter_02192026_s3_d2.png|train/6/meter_03032026_s3_d6.png|train/2/meter_10092025_s1_d2.png,39,2,1262 +composed,2,train/2/synthetic_window_00039_s3_d2.png,train/1/meter_11112020_s0_d1.png|train/2/meter_02192026_s3_d2.png|train/6/meter_03032026_s3_d6.png|train/2/meter_10092025_s1_d2.png,39,3,1262 +composed,8,train/8/synthetic_window_00040_s0_d8.png,train/8/meter_07012020_s2_d8.png|train/7/meter_07012020_s1_d7.png|train/4/meter_02242026_s3_d4.png|train/3/meter_01132026_s1_d3.png,40,0,8743 +composed,7,train/7/synthetic_window_00040_s1_d7.png,train/8/meter_07012020_s2_d8.png|train/7/meter_07012020_s1_d7.png|train/4/meter_02242026_s3_d4.png|train/3/meter_01132026_s1_d3.png,40,1,8743 +composed,4,train/4/synthetic_window_00040_s2_d4.png,train/8/meter_07012020_s2_d8.png|train/7/meter_07012020_s1_d7.png|train/4/meter_02242026_s3_d4.png|train/3/meter_01132026_s1_d3.png,40,2,8743 +composed,3,train/3/synthetic_window_00040_s3_d3.png,train/8/meter_07012020_s2_d8.png|train/7/meter_07012020_s1_d7.png|train/4/meter_02242026_s3_d4.png|train/3/meter_01132026_s1_d3.png,40,3,8743 +composed,0,train/0/synthetic_window_00041_s0_d0.png,train/0/meter_01302026_s2_d0.png|train/3/meter_02192026_s1_d3.png|train/0/meter_01302026_s2_d0.png|train/6/meter_03032026_s3_d6.png,41,0,0306 +composed,3,train/3/synthetic_window_00041_s1_d3.png,train/0/meter_01302026_s2_d0.png|train/3/meter_02192026_s1_d3.png|train/0/meter_01302026_s2_d0.png|train/6/meter_03032026_s3_d6.png,41,1,0306 +composed,0,train/0/synthetic_window_00041_s2_d0.png,train/0/meter_01302026_s2_d0.png|train/3/meter_02192026_s1_d3.png|train/0/meter_01302026_s2_d0.png|train/6/meter_03032026_s3_d6.png,41,2,0306 +composed,6,train/6/synthetic_window_00041_s3_d6.png,train/0/meter_01302026_s2_d0.png|train/3/meter_02192026_s1_d3.png|train/0/meter_01302026_s2_d0.png|train/6/meter_03032026_s3_d6.png,41,3,0306 +composed,2,train/2/synthetic_window_00042_s0_d2.png,train/2/meter_02162026_s3_d2.png|train/9/meter_10092025_s3_d9.png|train/1/meter_02242026_s2_d1.png|train/8/meter_11112020_s1_d8.png,42,0,2918 +composed,9,train/9/synthetic_window_00042_s1_d9.png,train/2/meter_02162026_s3_d2.png|train/9/meter_10092025_s3_d9.png|train/1/meter_02242026_s2_d1.png|train/8/meter_11112020_s1_d8.png,42,1,2918 +composed,1,train/1/synthetic_window_00042_s2_d1.png,train/2/meter_02162026_s3_d2.png|train/9/meter_10092025_s3_d9.png|train/1/meter_02242026_s2_d1.png|train/8/meter_11112020_s1_d8.png,42,2,2918 +composed,8,train/8/synthetic_window_00042_s3_d8.png,train/2/meter_02162026_s3_d2.png|train/9/meter_10092025_s3_d9.png|train/1/meter_02242026_s2_d1.png|train/8/meter_11112020_s1_d8.png,42,3,2918 +composed,3,train/3/synthetic_window_00043_s0_d3.png,train/3/meter_02242026_s1_d3.png|train/7/meter_07012020_s1_d7.png|train/5/meter_02272026_s3_d5.png|train/3/meter_01302026_s1_d3.png,43,0,3753 +composed,7,train/7/synthetic_window_00043_s1_d7.png,train/3/meter_02242026_s1_d3.png|train/7/meter_07012020_s1_d7.png|train/5/meter_02272026_s3_d5.png|train/3/meter_01302026_s1_d3.png,43,1,3753 +composed,5,train/5/synthetic_window_00043_s2_d5.png,train/3/meter_02242026_s1_d3.png|train/7/meter_07012020_s1_d7.png|train/5/meter_02272026_s3_d5.png|train/3/meter_01302026_s1_d3.png,43,2,3753 +composed,3,train/3/synthetic_window_00043_s3_d3.png,train/3/meter_02242026_s1_d3.png|train/7/meter_07012020_s1_d7.png|train/5/meter_02272026_s3_d5.png|train/3/meter_01302026_s1_d3.png,43,3,3753 +composed,8,train/8/synthetic_window_00044_s0_d8.png,train/8/meter_11112020_s1_d8.png|train/2/meter_02192026_s3_d2.png|train/2/meter_01302026_s0_d2.png|train/5/meter_02272026_s3_d5.png,44,0,8225 +composed,2,train/2/synthetic_window_00044_s1_d2.png,train/8/meter_11112020_s1_d8.png|train/2/meter_02192026_s3_d2.png|train/2/meter_01302026_s0_d2.png|train/5/meter_02272026_s3_d5.png,44,1,8225 +composed,2,train/2/synthetic_window_00044_s2_d2.png,train/8/meter_11112020_s1_d8.png|train/2/meter_02192026_s3_d2.png|train/2/meter_01302026_s0_d2.png|train/5/meter_02272026_s3_d5.png,44,2,8225 +composed,5,train/5/synthetic_window_00044_s3_d5.png,train/8/meter_11112020_s1_d8.png|train/2/meter_02192026_s3_d2.png|train/2/meter_01302026_s0_d2.png|train/5/meter_02272026_s3_d5.png,44,3,8225 +composed,4,train/4/synthetic_window_00045_s0_d4.png,train/4/meter_07012020_s3_d4.png|train/3/meter_02242026_s1_d3.png|train/5/meter_02272026_s3_d5.png|train/6/meter_03032026_s3_d6.png,45,0,4356 +composed,3,train/3/synthetic_window_00045_s1_d3.png,train/4/meter_07012020_s3_d4.png|train/3/meter_02242026_s1_d3.png|train/5/meter_02272026_s3_d5.png|train/6/meter_03032026_s3_d6.png,45,1,4356 +composed,5,train/5/synthetic_window_00045_s2_d5.png,train/4/meter_07012020_s3_d4.png|train/3/meter_02242026_s1_d3.png|train/5/meter_02272026_s3_d5.png|train/6/meter_03032026_s3_d6.png,45,2,4356 +composed,6,train/6/synthetic_window_00045_s3_d6.png,train/4/meter_07012020_s3_d4.png|train/3/meter_02242026_s1_d3.png|train/5/meter_02272026_s3_d5.png|train/6/meter_03032026_s3_d6.png,45,3,4356 +composed,3,train/3/synthetic_window_00046_s0_d3.png,train/3/meter_01132026_s1_d3.png|train/2/meter_01122026_s3_d2.png|train/3/meter_02202026_s1_d3.png|train/8/meter_07012020_s2_d8.png,46,0,3238 +composed,2,train/2/synthetic_window_00046_s1_d2.png,train/3/meter_01132026_s1_d3.png|train/2/meter_01122026_s3_d2.png|train/3/meter_02202026_s1_d3.png|train/8/meter_07012020_s2_d8.png,46,1,3238 +composed,3,train/3/synthetic_window_00046_s2_d3.png,train/3/meter_01132026_s1_d3.png|train/2/meter_01122026_s3_d2.png|train/3/meter_02202026_s1_d3.png|train/8/meter_07012020_s2_d8.png,46,2,3238 +composed,8,train/8/synthetic_window_00046_s3_d8.png,train/3/meter_01132026_s1_d3.png|train/2/meter_01122026_s3_d2.png|train/3/meter_02202026_s1_d3.png|train/8/meter_07012020_s2_d8.png,46,3,3238 +composed,8,train/8/synthetic_window_00047_s0_d8.png,train/8/meter_07012020_s2_d8.png|train/7/meter_07012020_s1_d7.png|train/1/meter_03032026_s2_d1.png|train/4/meter_02242026_s3_d4.png,47,0,8714 +composed,7,train/7/synthetic_window_00047_s1_d7.png,train/8/meter_07012020_s2_d8.png|train/7/meter_07012020_s1_d7.png|train/1/meter_03032026_s2_d1.png|train/4/meter_02242026_s3_d4.png,47,1,8714 +composed,1,train/1/synthetic_window_00047_s2_d1.png,train/8/meter_07012020_s2_d8.png|train/7/meter_07012020_s1_d7.png|train/1/meter_03032026_s2_d1.png|train/4/meter_02242026_s3_d4.png,47,2,8714 +composed,4,train/4/synthetic_window_00047_s3_d4.png,train/8/meter_07012020_s2_d8.png|train/7/meter_07012020_s1_d7.png|train/1/meter_03032026_s2_d1.png|train/4/meter_02242026_s3_d4.png,47,3,8714 +composed,3,train/3/synthetic_window_00048_s0_d3.png,train/3/meter_02162026_s1_d3.png|train/1/meter_11112020_s0_d1.png|train/4/meter_02242026_s3_d4.png|train/5/meter_02272026_s3_d5.png,48,0,3145 +composed,1,train/1/synthetic_window_00048_s1_d1.png,train/3/meter_02162026_s1_d3.png|train/1/meter_11112020_s0_d1.png|train/4/meter_02242026_s3_d4.png|train/5/meter_02272026_s3_d5.png,48,1,3145 +composed,4,train/4/synthetic_window_00048_s2_d4.png,train/3/meter_02162026_s1_d3.png|train/1/meter_11112020_s0_d1.png|train/4/meter_02242026_s3_d4.png|train/5/meter_02272026_s3_d5.png,48,2,3145 +composed,5,train/5/synthetic_window_00048_s3_d5.png,train/3/meter_02162026_s1_d3.png|train/1/meter_11112020_s0_d1.png|train/4/meter_02242026_s3_d4.png|train/5/meter_02272026_s3_d5.png,48,3,3145 +composed,6,train/6/synthetic_window_00049_s0_d6.png,train/6/meter_03032026_s3_d6.png|train/3/meter_01132026_s1_d3.png|train/9/meter_10092025_s3_d9.png|train/2/meter_02202026_s0_d2.png,49,0,6392 +composed,3,train/3/synthetic_window_00049_s1_d3.png,train/6/meter_03032026_s3_d6.png|train/3/meter_01132026_s1_d3.png|train/9/meter_10092025_s3_d9.png|train/2/meter_02202026_s0_d2.png,49,1,6392 +composed,9,train/9/synthetic_window_00049_s2_d9.png,train/6/meter_03032026_s3_d6.png|train/3/meter_01132026_s1_d3.png|train/9/meter_10092025_s3_d9.png|train/2/meter_02202026_s0_d2.png,49,2,6392 +composed,2,train/2/synthetic_window_00049_s3_d2.png,train/6/meter_03032026_s3_d6.png|train/3/meter_01132026_s1_d3.png|train/9/meter_10092025_s3_d9.png|train/2/meter_02202026_s0_d2.png,49,3,6392 +composed,0,train/0/synthetic_window_00050_s0_d0.png,train/0/meter_01302026_s2_d0.png|train/9/meter_11112020_s3_d9.png|train/9/meter_10092025_s3_d9.png|train/7/meter_10092025_s2_d7.png,50,0,0997 +composed,9,train/9/synthetic_window_00050_s1_d9.png,train/0/meter_01302026_s2_d0.png|train/9/meter_11112020_s3_d9.png|train/9/meter_10092025_s3_d9.png|train/7/meter_10092025_s2_d7.png,50,1,0997 +composed,9,train/9/synthetic_window_00050_s2_d9.png,train/0/meter_01302026_s2_d0.png|train/9/meter_11112020_s3_d9.png|train/9/meter_10092025_s3_d9.png|train/7/meter_10092025_s2_d7.png,50,2,0997 +composed,7,train/7/synthetic_window_00050_s3_d7.png,train/0/meter_01302026_s2_d0.png|train/9/meter_11112020_s3_d9.png|train/9/meter_10092025_s3_d9.png|train/7/meter_10092025_s2_d7.png,50,3,0997 +composed,1,train/1/synthetic_window_00051_s0_d1.png,train/1/meter_02162026_s2_d1.png|train/1/meter_11112020_s0_d1.png|train/8/meter_11112020_s1_d8.png|train/0/meter_01302026_s2_d0.png,51,0,1180 +composed,1,train/1/synthetic_window_00051_s1_d1.png,train/1/meter_02162026_s2_d1.png|train/1/meter_11112020_s0_d1.png|train/8/meter_11112020_s1_d8.png|train/0/meter_01302026_s2_d0.png,51,1,1180 +composed,8,train/8/synthetic_window_00051_s2_d8.png,train/1/meter_02162026_s2_d1.png|train/1/meter_11112020_s0_d1.png|train/8/meter_11112020_s1_d8.png|train/0/meter_01302026_s2_d0.png,51,2,1180 +composed,0,train/0/synthetic_window_00051_s3_d0.png,train/1/meter_02162026_s2_d1.png|train/1/meter_11112020_s0_d1.png|train/8/meter_11112020_s1_d8.png|train/0/meter_01302026_s2_d0.png,51,3,1180 +composed,2,train/2/synthetic_window_00052_s0_d2.png,train/2/meter_02272026_s0_d2.png|train/8/meter_11112020_s1_d8.png|train/7/meter_07012020_s1_d7.png|train/2/meter_02162026_s3_d2.png,52,0,2872 +composed,8,train/8/synthetic_window_00052_s1_d8.png,train/2/meter_02272026_s0_d2.png|train/8/meter_11112020_s1_d8.png|train/7/meter_07012020_s1_d7.png|train/2/meter_02162026_s3_d2.png,52,1,2872 +composed,7,train/7/synthetic_window_00052_s2_d7.png,train/2/meter_02272026_s0_d2.png|train/8/meter_11112020_s1_d8.png|train/7/meter_07012020_s1_d7.png|train/2/meter_02162026_s3_d2.png,52,2,2872 +composed,2,train/2/synthetic_window_00052_s3_d2.png,train/2/meter_02272026_s0_d2.png|train/8/meter_11112020_s1_d8.png|train/7/meter_07012020_s1_d7.png|train/2/meter_02162026_s3_d2.png,52,3,2872 +composed,9,train/9/synthetic_window_00053_s0_d9.png,train/9/meter_11112020_s3_d9.png|train/4/meter_07012020_s3_d4.png|train/9/meter_11112020_s3_d9.png|train/1/meter_02162026_s2_d1.png,53,0,9491 +composed,4,train/4/synthetic_window_00053_s1_d4.png,train/9/meter_11112020_s3_d9.png|train/4/meter_07012020_s3_d4.png|train/9/meter_11112020_s3_d9.png|train/1/meter_02162026_s2_d1.png,53,1,9491 +composed,9,train/9/synthetic_window_00053_s2_d9.png,train/9/meter_11112020_s3_d9.png|train/4/meter_07012020_s3_d4.png|train/9/meter_11112020_s3_d9.png|train/1/meter_02162026_s2_d1.png,53,2,9491 +composed,1,train/1/synthetic_window_00053_s3_d1.png,train/9/meter_11112020_s3_d9.png|train/4/meter_07012020_s3_d4.png|train/9/meter_11112020_s3_d9.png|train/1/meter_02162026_s2_d1.png,53,3,9491 +composed,6,train/6/synthetic_window_00054_s0_d6.png,train/6/meter_03032026_s3_d6.png|train/6/meter_03032026_s3_d6.png|train/2/meter_02202026_s0_d2.png|train/8/meter_07012020_s2_d8.png,54,0,6628 +composed,6,train/6/synthetic_window_00054_s1_d6.png,train/6/meter_03032026_s3_d6.png|train/6/meter_03032026_s3_d6.png|train/2/meter_02202026_s0_d2.png|train/8/meter_07012020_s2_d8.png,54,1,6628 +composed,2,train/2/synthetic_window_00054_s2_d2.png,train/6/meter_03032026_s3_d6.png|train/6/meter_03032026_s3_d6.png|train/2/meter_02202026_s0_d2.png|train/8/meter_07012020_s2_d8.png,54,2,6628 +composed,8,train/8/synthetic_window_00054_s3_d8.png,train/6/meter_03032026_s3_d6.png|train/6/meter_03032026_s3_d6.png|train/2/meter_02202026_s0_d2.png|train/8/meter_07012020_s2_d8.png,54,3,6628 +composed,7,train/7/synthetic_window_00055_s0_d7.png,train/7/meter_01302026_s3_d7.png|train/1/meter_02162026_s2_d1.png|train/1/meter_02272026_s2_d1.png|train/7/meter_07012020_s1_d7.png,55,0,7117 +composed,1,train/1/synthetic_window_00055_s1_d1.png,train/7/meter_01302026_s3_d7.png|train/1/meter_02162026_s2_d1.png|train/1/meter_02272026_s2_d1.png|train/7/meter_07012020_s1_d7.png,55,1,7117 +composed,1,train/1/synthetic_window_00055_s2_d1.png,train/7/meter_01302026_s3_d7.png|train/1/meter_02162026_s2_d1.png|train/1/meter_02272026_s2_d1.png|train/7/meter_07012020_s1_d7.png,55,2,7117 +composed,7,train/7/synthetic_window_00055_s3_d7.png,train/7/meter_01302026_s3_d7.png|train/1/meter_02162026_s2_d1.png|train/1/meter_02272026_s2_d1.png|train/7/meter_07012020_s1_d7.png,55,3,7117 +composed,4,train/4/synthetic_window_00056_s0_d4.png,train/4/meter_02242026_s3_d4.png|train/7/meter_10092025_s2_d7.png|train/4/meter_07012020_s3_d4.png|train/9/meter_11112020_s3_d9.png,56,0,4749 +composed,7,train/7/synthetic_window_00056_s1_d7.png,train/4/meter_02242026_s3_d4.png|train/7/meter_10092025_s2_d7.png|train/4/meter_07012020_s3_d4.png|train/9/meter_11112020_s3_d9.png,56,1,4749 +composed,4,train/4/synthetic_window_00056_s2_d4.png,train/4/meter_02242026_s3_d4.png|train/7/meter_10092025_s2_d7.png|train/4/meter_07012020_s3_d4.png|train/9/meter_11112020_s3_d9.png,56,2,4749 +composed,9,train/9/synthetic_window_00056_s3_d9.png,train/4/meter_02242026_s3_d4.png|train/7/meter_10092025_s2_d7.png|train/4/meter_07012020_s3_d4.png|train/9/meter_11112020_s3_d9.png,56,3,4749 +composed,4,train/4/synthetic_window_00057_s0_d4.png,train/4/meter_02242026_s3_d4.png|train/1/meter_02202026_s2_d1.png|train/1/meter_02162026_s2_d1.png|train/9/meter_11112020_s3_d9.png,57,0,4119 +composed,1,train/1/synthetic_window_00057_s1_d1.png,train/4/meter_02242026_s3_d4.png|train/1/meter_02202026_s2_d1.png|train/1/meter_02162026_s2_d1.png|train/9/meter_11112020_s3_d9.png,57,1,4119 +composed,1,train/1/synthetic_window_00057_s2_d1.png,train/4/meter_02242026_s3_d4.png|train/1/meter_02202026_s2_d1.png|train/1/meter_02162026_s2_d1.png|train/9/meter_11112020_s3_d9.png,57,2,4119 +composed,9,train/9/synthetic_window_00057_s3_d9.png,train/4/meter_02242026_s3_d4.png|train/1/meter_02202026_s2_d1.png|train/1/meter_02162026_s2_d1.png|train/9/meter_11112020_s3_d9.png,57,3,4119 +composed,3,train/3/synthetic_window_00058_s0_d3.png,train/3/meter_02192026_s1_d3.png|train/0/meter_01302026_s2_d0.png|train/6/meter_03032026_s3_d6.png|train/5/meter_02272026_s3_d5.png,58,0,3065 +composed,0,train/0/synthetic_window_00058_s1_d0.png,train/3/meter_02192026_s1_d3.png|train/0/meter_01302026_s2_d0.png|train/6/meter_03032026_s3_d6.png|train/5/meter_02272026_s3_d5.png,58,1,3065 +composed,6,train/6/synthetic_window_00058_s2_d6.png,train/3/meter_02192026_s1_d3.png|train/0/meter_01302026_s2_d0.png|train/6/meter_03032026_s3_d6.png|train/5/meter_02272026_s3_d5.png,58,2,3065 +composed,5,train/5/synthetic_window_00058_s3_d5.png,train/3/meter_02192026_s1_d3.png|train/0/meter_01302026_s2_d0.png|train/6/meter_03032026_s3_d6.png|train/5/meter_02272026_s3_d5.png,58,3,3065 +composed,3,train/3/synthetic_window_00059_s0_d3.png,train/3/meter_01302026_s1_d3.png|train/4/meter_07012020_s3_d4.png|train/7/meter_01302026_s3_d7.png|train/8/meter_11112020_s1_d8.png,59,0,3478 +composed,4,train/4/synthetic_window_00059_s1_d4.png,train/3/meter_01302026_s1_d3.png|train/4/meter_07012020_s3_d4.png|train/7/meter_01302026_s3_d7.png|train/8/meter_11112020_s1_d8.png,59,1,3478 +composed,7,train/7/synthetic_window_00059_s2_d7.png,train/3/meter_01302026_s1_d3.png|train/4/meter_07012020_s3_d4.png|train/7/meter_01302026_s3_d7.png|train/8/meter_11112020_s1_d8.png,59,2,3478 +composed,8,train/8/synthetic_window_00059_s3_d8.png,train/3/meter_01302026_s1_d3.png|train/4/meter_07012020_s3_d4.png|train/7/meter_01302026_s3_d7.png|train/8/meter_11112020_s1_d8.png,59,3,3478 +composed,9,train/9/synthetic_window_00060_s0_d9.png,train/9/meter_11112020_s3_d9.png|train/6/meter_03032026_s3_d6.png|train/0/meter_01132026_s2_d0.png|train/5/meter_02272026_s3_d5.png,60,0,9605 +composed,6,train/6/synthetic_window_00060_s1_d6.png,train/9/meter_11112020_s3_d9.png|train/6/meter_03032026_s3_d6.png|train/0/meter_01132026_s2_d0.png|train/5/meter_02272026_s3_d5.png,60,1,9605 +composed,0,train/0/synthetic_window_00060_s2_d0.png,train/9/meter_11112020_s3_d9.png|train/6/meter_03032026_s3_d6.png|train/0/meter_01132026_s2_d0.png|train/5/meter_02272026_s3_d5.png,60,2,9605 +composed,5,train/5/synthetic_window_00060_s3_d5.png,train/9/meter_11112020_s3_d9.png|train/6/meter_03032026_s3_d6.png|train/0/meter_01132026_s2_d0.png|train/5/meter_02272026_s3_d5.png,60,3,9605 +composed,9,train/9/synthetic_window_00061_s0_d9.png,train/9/meter_11112020_s3_d9.png|train/4/meter_07012020_s3_d4.png|train/9/meter_10092025_s3_d9.png|train/5/meter_02272026_s3_d5.png,61,0,9495 +composed,4,train/4/synthetic_window_00061_s1_d4.png,train/9/meter_11112020_s3_d9.png|train/4/meter_07012020_s3_d4.png|train/9/meter_10092025_s3_d9.png|train/5/meter_02272026_s3_d5.png,61,1,9495 +composed,9,train/9/synthetic_window_00061_s2_d9.png,train/9/meter_11112020_s3_d9.png|train/4/meter_07012020_s3_d4.png|train/9/meter_10092025_s3_d9.png|train/5/meter_02272026_s3_d5.png,61,2,9495 +composed,5,train/5/synthetic_window_00061_s3_d5.png,train/9/meter_11112020_s3_d9.png|train/4/meter_07012020_s3_d4.png|train/9/meter_10092025_s3_d9.png|train/5/meter_02272026_s3_d5.png,61,3,9495 +composed,0,train/0/synthetic_window_00062_s0_d0.png,train/0/meter_01132026_s2_d0.png|train/3/meter_01132026_s1_d3.png|train/6/meter_03032026_s3_d6.png|train/2/meter_02162026_s0_d2.png,62,0,0362 +composed,3,train/3/synthetic_window_00062_s1_d3.png,train/0/meter_01132026_s2_d0.png|train/3/meter_01132026_s1_d3.png|train/6/meter_03032026_s3_d6.png|train/2/meter_02162026_s0_d2.png,62,1,0362 +composed,6,train/6/synthetic_window_00062_s2_d6.png,train/0/meter_01132026_s2_d0.png|train/3/meter_01132026_s1_d3.png|train/6/meter_03032026_s3_d6.png|train/2/meter_02162026_s0_d2.png,62,2,0362 +composed,2,train/2/synthetic_window_00062_s3_d2.png,train/0/meter_01132026_s2_d0.png|train/3/meter_01132026_s1_d3.png|train/6/meter_03032026_s3_d6.png|train/2/meter_02162026_s0_d2.png,62,3,0362 +composed,5,train/5/synthetic_window_00063_s0_d5.png,train/5/meter_02272026_s3_d5.png|train/2/meter_02192026_s3_d2.png|train/0/meter_01132026_s2_d0.png|train/0/meter_01302026_s2_d0.png,63,0,5200 +composed,2,train/2/synthetic_window_00063_s1_d2.png,train/5/meter_02272026_s3_d5.png|train/2/meter_02192026_s3_d2.png|train/0/meter_01132026_s2_d0.png|train/0/meter_01302026_s2_d0.png,63,1,5200 +composed,0,train/0/synthetic_window_00063_s2_d0.png,train/5/meter_02272026_s3_d5.png|train/2/meter_02192026_s3_d2.png|train/0/meter_01132026_s2_d0.png|train/0/meter_01302026_s2_d0.png,63,2,5200 +composed,0,train/0/synthetic_window_00063_s3_d0.png,train/5/meter_02272026_s3_d5.png|train/2/meter_02192026_s3_d2.png|train/0/meter_01132026_s2_d0.png|train/0/meter_01302026_s2_d0.png,63,3,5200 +composed,3,train/3/synthetic_window_00064_s0_d3.png,train/3/meter_02202026_s1_d3.png|train/4/meter_07012020_s3_d4.png|train/3/meter_02162026_s1_d3.png|train/6/meter_03032026_s3_d6.png,64,0,3436 +composed,4,train/4/synthetic_window_00064_s1_d4.png,train/3/meter_02202026_s1_d3.png|train/4/meter_07012020_s3_d4.png|train/3/meter_02162026_s1_d3.png|train/6/meter_03032026_s3_d6.png,64,1,3436 +composed,3,train/3/synthetic_window_00064_s2_d3.png,train/3/meter_02202026_s1_d3.png|train/4/meter_07012020_s3_d4.png|train/3/meter_02162026_s1_d3.png|train/6/meter_03032026_s3_d6.png,64,2,3436 +composed,6,train/6/synthetic_window_00064_s3_d6.png,train/3/meter_02202026_s1_d3.png|train/4/meter_07012020_s3_d4.png|train/3/meter_02162026_s1_d3.png|train/6/meter_03032026_s3_d6.png,64,3,3436 +composed,3,train/3/synthetic_window_00065_s0_d3.png,train/3/meter_02202026_s3_d3.png|train/3/meter_03032026_s1_d3.png|train/4/meter_07012020_s3_d4.png|train/9/meter_11112020_s3_d9.png,65,0,3349 +composed,3,train/3/synthetic_window_00065_s1_d3.png,train/3/meter_02202026_s3_d3.png|train/3/meter_03032026_s1_d3.png|train/4/meter_07012020_s3_d4.png|train/9/meter_11112020_s3_d9.png,65,1,3349 +composed,4,train/4/synthetic_window_00065_s2_d4.png,train/3/meter_02202026_s3_d3.png|train/3/meter_03032026_s1_d3.png|train/4/meter_07012020_s3_d4.png|train/9/meter_11112020_s3_d9.png,65,2,3349 +composed,9,train/9/synthetic_window_00065_s3_d9.png,train/3/meter_02202026_s3_d3.png|train/3/meter_03032026_s1_d3.png|train/4/meter_07012020_s3_d4.png|train/9/meter_11112020_s3_d9.png,65,3,3349 +composed,0,train/0/synthetic_window_00066_s0_d0.png,train/0/meter_01132026_s2_d0.png|train/1/meter_07012020_s0_d1.png|train/5/meter_02272026_s3_d5.png|train/4/meter_07012020_s3_d4.png,66,0,0154 +composed,1,train/1/synthetic_window_00066_s1_d1.png,train/0/meter_01132026_s2_d0.png|train/1/meter_07012020_s0_d1.png|train/5/meter_02272026_s3_d5.png|train/4/meter_07012020_s3_d4.png,66,1,0154 +composed,5,train/5/synthetic_window_00066_s2_d5.png,train/0/meter_01132026_s2_d0.png|train/1/meter_07012020_s0_d1.png|train/5/meter_02272026_s3_d5.png|train/4/meter_07012020_s3_d4.png,66,2,0154 +composed,4,train/4/synthetic_window_00066_s3_d4.png,train/0/meter_01132026_s2_d0.png|train/1/meter_07012020_s0_d1.png|train/5/meter_02272026_s3_d5.png|train/4/meter_07012020_s3_d4.png,66,3,0154 +composed,3,train/3/synthetic_window_00067_s0_d3.png,train/3/meter_03032026_s1_d3.png|train/1/meter_02242026_s2_d1.png|train/9/meter_10092025_s3_d9.png|train/7/meter_07012020_s1_d7.png,67,0,3197 +composed,1,train/1/synthetic_window_00067_s1_d1.png,train/3/meter_03032026_s1_d3.png|train/1/meter_02242026_s2_d1.png|train/9/meter_10092025_s3_d9.png|train/7/meter_07012020_s1_d7.png,67,1,3197 +composed,9,train/9/synthetic_window_00067_s2_d9.png,train/3/meter_03032026_s1_d3.png|train/1/meter_02242026_s2_d1.png|train/9/meter_10092025_s3_d9.png|train/7/meter_07012020_s1_d7.png,67,2,3197 +composed,7,train/7/synthetic_window_00067_s3_d7.png,train/3/meter_03032026_s1_d3.png|train/1/meter_02242026_s2_d1.png|train/9/meter_10092025_s3_d9.png|train/7/meter_07012020_s1_d7.png,67,3,3197 +composed,1,train/1/synthetic_window_00068_s0_d1.png,train/1/meter_11112020_s0_d1.png|train/0/meter_01122026_s2_d0.png|train/9/meter_10092025_s3_d9.png|train/0/meter_01122026_s2_d0.png,68,0,1090 +composed,0,train/0/synthetic_window_00068_s1_d0.png,train/1/meter_11112020_s0_d1.png|train/0/meter_01122026_s2_d0.png|train/9/meter_10092025_s3_d9.png|train/0/meter_01122026_s2_d0.png,68,1,1090 +composed,9,train/9/synthetic_window_00068_s2_d9.png,train/1/meter_11112020_s0_d1.png|train/0/meter_01122026_s2_d0.png|train/9/meter_10092025_s3_d9.png|train/0/meter_01122026_s2_d0.png,68,2,1090 +composed,0,train/0/synthetic_window_00068_s3_d0.png,train/1/meter_11112020_s0_d1.png|train/0/meter_01122026_s2_d0.png|train/9/meter_10092025_s3_d9.png|train/0/meter_01122026_s2_d0.png,68,3,1090 +composed,2,train/2/synthetic_window_00069_s0_d2.png,train/2/meter_03032026_s0_d2.png|train/4/meter_07012020_s3_d4.png|train/4/meter_07012020_s3_d4.png|train/1/meter_02162026_s2_d1.png,69,0,2441 +composed,4,train/4/synthetic_window_00069_s1_d4.png,train/2/meter_03032026_s0_d2.png|train/4/meter_07012020_s3_d4.png|train/4/meter_07012020_s3_d4.png|train/1/meter_02162026_s2_d1.png,69,1,2441 +composed,4,train/4/synthetic_window_00069_s2_d4.png,train/2/meter_03032026_s0_d2.png|train/4/meter_07012020_s3_d4.png|train/4/meter_07012020_s3_d4.png|train/1/meter_02162026_s2_d1.png,69,2,2441 +composed,1,train/1/synthetic_window_00069_s3_d1.png,train/2/meter_03032026_s0_d2.png|train/4/meter_07012020_s3_d4.png|train/4/meter_07012020_s3_d4.png|train/1/meter_02162026_s2_d1.png,69,3,2441 +composed,4,train/4/synthetic_window_00070_s0_d4.png,train/4/meter_02242026_s3_d4.png|train/6/meter_03032026_s3_d6.png|train/0/meter_01302026_s2_d0.png|train/0/meter_01122026_s2_d0.png,70,0,4600 +composed,6,train/6/synthetic_window_00070_s1_d6.png,train/4/meter_02242026_s3_d4.png|train/6/meter_03032026_s3_d6.png|train/0/meter_01302026_s2_d0.png|train/0/meter_01122026_s2_d0.png,70,1,4600 +composed,0,train/0/synthetic_window_00070_s2_d0.png,train/4/meter_02242026_s3_d4.png|train/6/meter_03032026_s3_d6.png|train/0/meter_01302026_s2_d0.png|train/0/meter_01122026_s2_d0.png,70,2,4600 +composed,0,train/0/synthetic_window_00070_s3_d0.png,train/4/meter_02242026_s3_d4.png|train/6/meter_03032026_s3_d6.png|train/0/meter_01302026_s2_d0.png|train/0/meter_01122026_s2_d0.png,70,3,4600 +composed,3,train/3/synthetic_window_00071_s0_d3.png,train/3/meter_01122026_s1_d3.png|train/0/meter_01302026_s2_d0.png|train/0/meter_01302026_s2_d0.png|train/0/meter_01122026_s2_d0.png,71,0,3000 +composed,0,train/0/synthetic_window_00071_s1_d0.png,train/3/meter_01122026_s1_d3.png|train/0/meter_01302026_s2_d0.png|train/0/meter_01302026_s2_d0.png|train/0/meter_01122026_s2_d0.png,71,1,3000 +composed,0,train/0/synthetic_window_00071_s2_d0.png,train/3/meter_01122026_s1_d3.png|train/0/meter_01302026_s2_d0.png|train/0/meter_01302026_s2_d0.png|train/0/meter_01122026_s2_d0.png,71,2,3000 +composed,0,train/0/synthetic_window_00071_s3_d0.png,train/3/meter_01122026_s1_d3.png|train/0/meter_01302026_s2_d0.png|train/0/meter_01302026_s2_d0.png|train/0/meter_01122026_s2_d0.png,71,3,3000 +composed,2,train/2/synthetic_window_00072_s0_d2.png,train/2/meter_02272026_s0_d2.png|train/1/meter_02162026_s2_d1.png|train/5/meter_02272026_s3_d5.png|train/6/meter_03032026_s3_d6.png,72,0,2156 +composed,1,train/1/synthetic_window_00072_s1_d1.png,train/2/meter_02272026_s0_d2.png|train/1/meter_02162026_s2_d1.png|train/5/meter_02272026_s3_d5.png|train/6/meter_03032026_s3_d6.png,72,1,2156 +composed,5,train/5/synthetic_window_00072_s2_d5.png,train/2/meter_02272026_s0_d2.png|train/1/meter_02162026_s2_d1.png|train/5/meter_02272026_s3_d5.png|train/6/meter_03032026_s3_d6.png,72,2,2156 +composed,6,train/6/synthetic_window_00072_s3_d6.png,train/2/meter_02272026_s0_d2.png|train/1/meter_02162026_s2_d1.png|train/5/meter_02272026_s3_d5.png|train/6/meter_03032026_s3_d6.png,72,3,2156 +composed,1,train/1/synthetic_window_00073_s0_d1.png,train/1/meter_02242026_s2_d1.png|train/1/meter_02272026_s2_d1.png|train/5/meter_02272026_s3_d5.png|train/9/meter_10092025_s3_d9.png,73,0,1159 +composed,1,train/1/synthetic_window_00073_s1_d1.png,train/1/meter_02242026_s2_d1.png|train/1/meter_02272026_s2_d1.png|train/5/meter_02272026_s3_d5.png|train/9/meter_10092025_s3_d9.png,73,1,1159 +composed,5,train/5/synthetic_window_00073_s2_d5.png,train/1/meter_02242026_s2_d1.png|train/1/meter_02272026_s2_d1.png|train/5/meter_02272026_s3_d5.png|train/9/meter_10092025_s3_d9.png,73,2,1159 +composed,9,train/9/synthetic_window_00073_s3_d9.png,train/1/meter_02242026_s2_d1.png|train/1/meter_02272026_s2_d1.png|train/5/meter_02272026_s3_d5.png|train/9/meter_10092025_s3_d9.png,73,3,1159 +composed,4,train/4/synthetic_window_00074_s0_d4.png,train/4/meter_07012020_s3_d4.png|train/6/meter_03032026_s3_d6.png|train/6/meter_03032026_s3_d6.png|train/0/meter_01122026_s2_d0.png,74,0,4660 +composed,6,train/6/synthetic_window_00074_s1_d6.png,train/4/meter_07012020_s3_d4.png|train/6/meter_03032026_s3_d6.png|train/6/meter_03032026_s3_d6.png|train/0/meter_01122026_s2_d0.png,74,1,4660 +composed,6,train/6/synthetic_window_00074_s2_d6.png,train/4/meter_07012020_s3_d4.png|train/6/meter_03032026_s3_d6.png|train/6/meter_03032026_s3_d6.png|train/0/meter_01122026_s2_d0.png,74,2,4660 +composed,0,train/0/synthetic_window_00074_s3_d0.png,train/4/meter_07012020_s3_d4.png|train/6/meter_03032026_s3_d6.png|train/6/meter_03032026_s3_d6.png|train/0/meter_01122026_s2_d0.png,74,3,4660 +composed,8,train/8/synthetic_window_00075_s0_d8.png,train/8/meter_07012020_s2_d8.png|train/3/meter_01132026_s1_d3.png|train/3/meter_01302026_s1_d3.png|train/9/meter_10092025_s3_d9.png,75,0,8339 +composed,3,train/3/synthetic_window_00075_s1_d3.png,train/8/meter_07012020_s2_d8.png|train/3/meter_01132026_s1_d3.png|train/3/meter_01302026_s1_d3.png|train/9/meter_10092025_s3_d9.png,75,1,8339 +composed,3,train/3/synthetic_window_00075_s2_d3.png,train/8/meter_07012020_s2_d8.png|train/3/meter_01132026_s1_d3.png|train/3/meter_01302026_s1_d3.png|train/9/meter_10092025_s3_d9.png,75,2,8339 +composed,9,train/9/synthetic_window_00075_s3_d9.png,train/8/meter_07012020_s2_d8.png|train/3/meter_01132026_s1_d3.png|train/3/meter_01302026_s1_d3.png|train/9/meter_10092025_s3_d9.png,75,3,8339 +composed,2,train/2/synthetic_window_00076_s0_d2.png,train/2/meter_02162026_s0_d2.png|train/0/meter_01302026_s2_d0.png|train/9/meter_10092025_s3_d9.png|train/8/meter_07012020_s2_d8.png,76,0,2098 +composed,0,train/0/synthetic_window_00076_s1_d0.png,train/2/meter_02162026_s0_d2.png|train/0/meter_01302026_s2_d0.png|train/9/meter_10092025_s3_d9.png|train/8/meter_07012020_s2_d8.png,76,1,2098 +composed,9,train/9/synthetic_window_00076_s2_d9.png,train/2/meter_02162026_s0_d2.png|train/0/meter_01302026_s2_d0.png|train/9/meter_10092025_s3_d9.png|train/8/meter_07012020_s2_d8.png,76,2,2098 +composed,8,train/8/synthetic_window_00076_s3_d8.png,train/2/meter_02162026_s0_d2.png|train/0/meter_01302026_s2_d0.png|train/9/meter_10092025_s3_d9.png|train/8/meter_07012020_s2_d8.png,76,3,2098 +composed,8,train/8/synthetic_window_00077_s0_d8.png,train/8/meter_11112020_s1_d8.png|train/4/meter_07012020_s3_d4.png|train/7/meter_07012020_s1_d7.png|train/7/meter_07012020_s1_d7.png,77,0,8477 +composed,4,train/4/synthetic_window_00077_s1_d4.png,train/8/meter_11112020_s1_d8.png|train/4/meter_07012020_s3_d4.png|train/7/meter_07012020_s1_d7.png|train/7/meter_07012020_s1_d7.png,77,1,8477 +composed,7,train/7/synthetic_window_00077_s2_d7.png,train/8/meter_11112020_s1_d8.png|train/4/meter_07012020_s3_d4.png|train/7/meter_07012020_s1_d7.png|train/7/meter_07012020_s1_d7.png,77,2,8477 +composed,7,train/7/synthetic_window_00077_s3_d7.png,train/8/meter_11112020_s1_d8.png|train/4/meter_07012020_s3_d4.png|train/7/meter_07012020_s1_d7.png|train/7/meter_07012020_s1_d7.png,77,3,8477 +composed,9,train/9/synthetic_window_00078_s0_d9.png,train/9/meter_10092025_s3_d9.png|train/1/meter_02272026_s2_d1.png|train/9/meter_11112020_s3_d9.png|train/2/meter_01122026_s3_d2.png,78,0,9192 +composed,1,train/1/synthetic_window_00078_s1_d1.png,train/9/meter_10092025_s3_d9.png|train/1/meter_02272026_s2_d1.png|train/9/meter_11112020_s3_d9.png|train/2/meter_01122026_s3_d2.png,78,1,9192 +composed,9,train/9/synthetic_window_00078_s2_d9.png,train/9/meter_10092025_s3_d9.png|train/1/meter_02272026_s2_d1.png|train/9/meter_11112020_s3_d9.png|train/2/meter_01122026_s3_d2.png,78,2,9192 +composed,2,train/2/synthetic_window_00078_s3_d2.png,train/9/meter_10092025_s3_d9.png|train/1/meter_02272026_s2_d1.png|train/9/meter_11112020_s3_d9.png|train/2/meter_01122026_s3_d2.png,78,3,9192 +composed,3,train/3/synthetic_window_00079_s0_d3.png,train/3/meter_01302026_s1_d3.png|train/7/meter_01302026_s3_d7.png|train/6/meter_03032026_s3_d6.png|train/1/meter_02202026_s2_d1.png,79,0,3761 +composed,7,train/7/synthetic_window_00079_s1_d7.png,train/3/meter_01302026_s1_d3.png|train/7/meter_01302026_s3_d7.png|train/6/meter_03032026_s3_d6.png|train/1/meter_02202026_s2_d1.png,79,1,3761 +composed,6,train/6/synthetic_window_00079_s2_d6.png,train/3/meter_01302026_s1_d3.png|train/7/meter_01302026_s3_d7.png|train/6/meter_03032026_s3_d6.png|train/1/meter_02202026_s2_d1.png,79,2,3761 +composed,1,train/1/synthetic_window_00079_s3_d1.png,train/3/meter_01302026_s1_d3.png|train/7/meter_01302026_s3_d7.png|train/6/meter_03032026_s3_d6.png|train/1/meter_02202026_s2_d1.png,79,3,3761 +composed,9,train/9/synthetic_window_00080_s0_d9.png,train/9/meter_10092025_s3_d9.png|train/9/meter_10092025_s3_d9.png|train/9/meter_11112020_s3_d9.png|train/3/meter_02202026_s3_d3.png,80,0,9993 +composed,9,train/9/synthetic_window_00080_s1_d9.png,train/9/meter_10092025_s3_d9.png|train/9/meter_10092025_s3_d9.png|train/9/meter_11112020_s3_d9.png|train/3/meter_02202026_s3_d3.png,80,1,9993 +composed,9,train/9/synthetic_window_00080_s2_d9.png,train/9/meter_10092025_s3_d9.png|train/9/meter_10092025_s3_d9.png|train/9/meter_11112020_s3_d9.png|train/3/meter_02202026_s3_d3.png,80,2,9993 +composed,3,train/3/synthetic_window_00080_s3_d3.png,train/9/meter_10092025_s3_d9.png|train/9/meter_10092025_s3_d9.png|train/9/meter_11112020_s3_d9.png|train/3/meter_02202026_s3_d3.png,80,3,9993 +composed,1,train/1/synthetic_window_00081_s0_d1.png,train/1/meter_02162026_s2_d1.png|train/9/meter_10092025_s3_d9.png|train/9/meter_11112020_s3_d9.png|train/7/meter_10092025_s2_d7.png,81,0,1997 +composed,9,train/9/synthetic_window_00081_s1_d9.png,train/1/meter_02162026_s2_d1.png|train/9/meter_10092025_s3_d9.png|train/9/meter_11112020_s3_d9.png|train/7/meter_10092025_s2_d7.png,81,1,1997 +composed,9,train/9/synthetic_window_00081_s2_d9.png,train/1/meter_02162026_s2_d1.png|train/9/meter_10092025_s3_d9.png|train/9/meter_11112020_s3_d9.png|train/7/meter_10092025_s2_d7.png,81,2,1997 +composed,7,train/7/synthetic_window_00081_s3_d7.png,train/1/meter_02162026_s2_d1.png|train/9/meter_10092025_s3_d9.png|train/9/meter_11112020_s3_d9.png|train/7/meter_10092025_s2_d7.png,81,3,1997 +composed,7,train/7/synthetic_window_00082_s0_d7.png,train/7/meter_07012020_s1_d7.png|train/0/meter_01132026_s2_d0.png|train/4/meter_02242026_s3_d4.png|train/5/meter_02272026_s3_d5.png,82,0,7045 +composed,0,train/0/synthetic_window_00082_s1_d0.png,train/7/meter_07012020_s1_d7.png|train/0/meter_01132026_s2_d0.png|train/4/meter_02242026_s3_d4.png|train/5/meter_02272026_s3_d5.png,82,1,7045 +composed,4,train/4/synthetic_window_00082_s2_d4.png,train/7/meter_07012020_s1_d7.png|train/0/meter_01132026_s2_d0.png|train/4/meter_02242026_s3_d4.png|train/5/meter_02272026_s3_d5.png,82,2,7045 +composed,5,train/5/synthetic_window_00082_s3_d5.png,train/7/meter_07012020_s1_d7.png|train/0/meter_01132026_s2_d0.png|train/4/meter_02242026_s3_d4.png|train/5/meter_02272026_s3_d5.png,82,3,7045 +composed,0,train/0/synthetic_window_00083_s0_d0.png,train/0/meter_01132026_s2_d0.png|train/9/meter_11112020_s3_d9.png|train/4/meter_02242026_s3_d4.png|train/4/meter_07012020_s3_d4.png,83,0,0944 +composed,9,train/9/synthetic_window_00083_s1_d9.png,train/0/meter_01132026_s2_d0.png|train/9/meter_11112020_s3_d9.png|train/4/meter_02242026_s3_d4.png|train/4/meter_07012020_s3_d4.png,83,1,0944 +composed,4,train/4/synthetic_window_00083_s2_d4.png,train/0/meter_01132026_s2_d0.png|train/9/meter_11112020_s3_d9.png|train/4/meter_02242026_s3_d4.png|train/4/meter_07012020_s3_d4.png,83,2,0944 +composed,4,train/4/synthetic_window_00083_s3_d4.png,train/0/meter_01132026_s2_d0.png|train/9/meter_11112020_s3_d9.png|train/4/meter_02242026_s3_d4.png|train/4/meter_07012020_s3_d4.png,83,3,0944 +composed,7,train/7/synthetic_window_00084_s0_d7.png,train/7/meter_07012020_s1_d7.png|train/2/meter_10092025_s0_d2.png|train/9/meter_10092025_s3_d9.png|train/9/meter_11112020_s3_d9.png,84,0,7299 +composed,2,train/2/synthetic_window_00084_s1_d2.png,train/7/meter_07012020_s1_d7.png|train/2/meter_10092025_s0_d2.png|train/9/meter_10092025_s3_d9.png|train/9/meter_11112020_s3_d9.png,84,1,7299 +composed,9,train/9/synthetic_window_00084_s2_d9.png,train/7/meter_07012020_s1_d7.png|train/2/meter_10092025_s0_d2.png|train/9/meter_10092025_s3_d9.png|train/9/meter_11112020_s3_d9.png,84,2,7299 +composed,9,train/9/synthetic_window_00084_s3_d9.png,train/7/meter_07012020_s1_d7.png|train/2/meter_10092025_s0_d2.png|train/9/meter_10092025_s3_d9.png|train/9/meter_11112020_s3_d9.png,84,3,7299 +composed,1,train/1/synthetic_window_00085_s0_d1.png,train/1/meter_02242026_s2_d1.png|train/8/meter_07012020_s2_d8.png|train/2/meter_10092025_s1_d2.png|train/4/meter_07012020_s3_d4.png,85,0,1824 +composed,8,train/8/synthetic_window_00085_s1_d8.png,train/1/meter_02242026_s2_d1.png|train/8/meter_07012020_s2_d8.png|train/2/meter_10092025_s1_d2.png|train/4/meter_07012020_s3_d4.png,85,1,1824 +composed,2,train/2/synthetic_window_00085_s2_d2.png,train/1/meter_02242026_s2_d1.png|train/8/meter_07012020_s2_d8.png|train/2/meter_10092025_s1_d2.png|train/4/meter_07012020_s3_d4.png,85,2,1824 +composed,4,train/4/synthetic_window_00085_s3_d4.png,train/1/meter_02242026_s2_d1.png|train/8/meter_07012020_s2_d8.png|train/2/meter_10092025_s1_d2.png|train/4/meter_07012020_s3_d4.png,85,3,1824 +composed,2,train/2/synthetic_window_00086_s0_d2.png,train/2/meter_02162026_s3_d2.png|train/9/meter_10092025_s3_d9.png|train/8/meter_11112020_s1_d8.png|train/6/meter_03032026_s3_d6.png,86,0,2986 +composed,9,train/9/synthetic_window_00086_s1_d9.png,train/2/meter_02162026_s3_d2.png|train/9/meter_10092025_s3_d9.png|train/8/meter_11112020_s1_d8.png|train/6/meter_03032026_s3_d6.png,86,1,2986 +composed,8,train/8/synthetic_window_00086_s2_d8.png,train/2/meter_02162026_s3_d2.png|train/9/meter_10092025_s3_d9.png|train/8/meter_11112020_s1_d8.png|train/6/meter_03032026_s3_d6.png,86,2,2986 +composed,6,train/6/synthetic_window_00086_s3_d6.png,train/2/meter_02162026_s3_d2.png|train/9/meter_10092025_s3_d9.png|train/8/meter_11112020_s1_d8.png|train/6/meter_03032026_s3_d6.png,86,3,2986 +composed,3,train/3/synthetic_window_00087_s0_d3.png,train/3/meter_02192026_s1_d3.png|train/2/meter_02192026_s0_d2.png|train/7/meter_07012020_s1_d7.png|train/9/meter_11112020_s3_d9.png,87,0,3279 +composed,2,train/2/synthetic_window_00087_s1_d2.png,train/3/meter_02192026_s1_d3.png|train/2/meter_02192026_s0_d2.png|train/7/meter_07012020_s1_d7.png|train/9/meter_11112020_s3_d9.png,87,1,3279 +composed,7,train/7/synthetic_window_00087_s2_d7.png,train/3/meter_02192026_s1_d3.png|train/2/meter_02192026_s0_d2.png|train/7/meter_07012020_s1_d7.png|train/9/meter_11112020_s3_d9.png,87,2,3279 +composed,9,train/9/synthetic_window_00087_s3_d9.png,train/3/meter_02192026_s1_d3.png|train/2/meter_02192026_s0_d2.png|train/7/meter_07012020_s1_d7.png|train/9/meter_11112020_s3_d9.png,87,3,3279 +composed,7,train/7/synthetic_window_00088_s0_d7.png,train/7/meter_07012020_s1_d7.png|train/8/meter_07012020_s2_d8.png|train/7/meter_10092025_s2_d7.png|train/5/meter_02272026_s3_d5.png,88,0,7875 +composed,8,train/8/synthetic_window_00088_s1_d8.png,train/7/meter_07012020_s1_d7.png|train/8/meter_07012020_s2_d8.png|train/7/meter_10092025_s2_d7.png|train/5/meter_02272026_s3_d5.png,88,1,7875 +composed,7,train/7/synthetic_window_00088_s2_d7.png,train/7/meter_07012020_s1_d7.png|train/8/meter_07012020_s2_d8.png|train/7/meter_10092025_s2_d7.png|train/5/meter_02272026_s3_d5.png,88,2,7875 +composed,5,train/5/synthetic_window_00088_s3_d5.png,train/7/meter_07012020_s1_d7.png|train/8/meter_07012020_s2_d8.png|train/7/meter_10092025_s2_d7.png|train/5/meter_02272026_s3_d5.png,88,3,7875 +composed,2,train/2/synthetic_window_00089_s0_d2.png,train/2/meter_01132026_s3_d2.png|train/0/meter_01302026_s2_d0.png|train/0/meter_01132026_s2_d0.png|train/8/meter_07012020_s2_d8.png,89,0,2008 +composed,0,train/0/synthetic_window_00089_s1_d0.png,train/2/meter_01132026_s3_d2.png|train/0/meter_01302026_s2_d0.png|train/0/meter_01132026_s2_d0.png|train/8/meter_07012020_s2_d8.png,89,1,2008 +composed,0,train/0/synthetic_window_00089_s2_d0.png,train/2/meter_01132026_s3_d2.png|train/0/meter_01302026_s2_d0.png|train/0/meter_01132026_s2_d0.png|train/8/meter_07012020_s2_d8.png,89,2,2008 +composed,8,train/8/synthetic_window_00089_s3_d8.png,train/2/meter_01132026_s3_d2.png|train/0/meter_01302026_s2_d0.png|train/0/meter_01132026_s2_d0.png|train/8/meter_07012020_s2_d8.png,89,3,2008 +composed,9,train/9/synthetic_window_00090_s0_d9.png,train/9/meter_11112020_s3_d9.png|train/0/meter_01122026_s2_d0.png|train/6/meter_03032026_s3_d6.png|train/2/meter_01122026_s0_d2.png,90,0,9062 +composed,0,train/0/synthetic_window_00090_s1_d0.png,train/9/meter_11112020_s3_d9.png|train/0/meter_01122026_s2_d0.png|train/6/meter_03032026_s3_d6.png|train/2/meter_01122026_s0_d2.png,90,1,9062 +composed,6,train/6/synthetic_window_00090_s2_d6.png,train/9/meter_11112020_s3_d9.png|train/0/meter_01122026_s2_d0.png|train/6/meter_03032026_s3_d6.png|train/2/meter_01122026_s0_d2.png,90,2,9062 +composed,2,train/2/synthetic_window_00090_s3_d2.png,train/9/meter_11112020_s3_d9.png|train/0/meter_01122026_s2_d0.png|train/6/meter_03032026_s3_d6.png|train/2/meter_01122026_s0_d2.png,90,3,9062 +composed,9,train/9/synthetic_window_00091_s0_d9.png,train/9/meter_10092025_s3_d9.png|train/4/meter_07012020_s3_d4.png|train/7/meter_01302026_s3_d7.png|train/4/meter_07012020_s3_d4.png,91,0,9474 +composed,4,train/4/synthetic_window_00091_s1_d4.png,train/9/meter_10092025_s3_d9.png|train/4/meter_07012020_s3_d4.png|train/7/meter_01302026_s3_d7.png|train/4/meter_07012020_s3_d4.png,91,1,9474 +composed,7,train/7/synthetic_window_00091_s2_d7.png,train/9/meter_10092025_s3_d9.png|train/4/meter_07012020_s3_d4.png|train/7/meter_01302026_s3_d7.png|train/4/meter_07012020_s3_d4.png,91,2,9474 +composed,4,train/4/synthetic_window_00091_s3_d4.png,train/9/meter_10092025_s3_d9.png|train/4/meter_07012020_s3_d4.png|train/7/meter_01302026_s3_d7.png|train/4/meter_07012020_s3_d4.png,91,3,9474 +composed,7,train/7/synthetic_window_00092_s0_d7.png,train/7/meter_01302026_s3_d7.png|train/0/meter_01132026_s2_d0.png|train/6/meter_03032026_s3_d6.png|train/5/meter_02272026_s3_d5.png,92,0,7065 +composed,0,train/0/synthetic_window_00092_s1_d0.png,train/7/meter_01302026_s3_d7.png|train/0/meter_01132026_s2_d0.png|train/6/meter_03032026_s3_d6.png|train/5/meter_02272026_s3_d5.png,92,1,7065 +composed,6,train/6/synthetic_window_00092_s2_d6.png,train/7/meter_01302026_s3_d7.png|train/0/meter_01132026_s2_d0.png|train/6/meter_03032026_s3_d6.png|train/5/meter_02272026_s3_d5.png,92,2,7065 +composed,5,train/5/synthetic_window_00092_s3_d5.png,train/7/meter_01302026_s3_d7.png|train/0/meter_01132026_s2_d0.png|train/6/meter_03032026_s3_d6.png|train/5/meter_02272026_s3_d5.png,92,3,7065 +composed,3,train/3/synthetic_window_00093_s0_d3.png,train/3/meter_01302026_s1_d3.png|train/2/meter_01122026_s3_d2.png|train/8/meter_11112020_s1_d8.png|train/4/meter_02242026_s3_d4.png,93,0,3284 +composed,2,train/2/synthetic_window_00093_s1_d2.png,train/3/meter_01302026_s1_d3.png|train/2/meter_01122026_s3_d2.png|train/8/meter_11112020_s1_d8.png|train/4/meter_02242026_s3_d4.png,93,1,3284 +composed,8,train/8/synthetic_window_00093_s2_d8.png,train/3/meter_01302026_s1_d3.png|train/2/meter_01122026_s3_d2.png|train/8/meter_11112020_s1_d8.png|train/4/meter_02242026_s3_d4.png,93,2,3284 +composed,4,train/4/synthetic_window_00093_s3_d4.png,train/3/meter_01302026_s1_d3.png|train/2/meter_01122026_s3_d2.png|train/8/meter_11112020_s1_d8.png|train/4/meter_02242026_s3_d4.png,93,3,3284 +composed,0,train/0/synthetic_window_00094_s0_d0.png,train/0/meter_01302026_s2_d0.png|train/9/meter_10092025_s3_d9.png|train/0/meter_01132026_s2_d0.png|train/7/meter_10092025_s2_d7.png,94,0,0907 +composed,9,train/9/synthetic_window_00094_s1_d9.png,train/0/meter_01302026_s2_d0.png|train/9/meter_10092025_s3_d9.png|train/0/meter_01132026_s2_d0.png|train/7/meter_10092025_s2_d7.png,94,1,0907 +composed,0,train/0/synthetic_window_00094_s2_d0.png,train/0/meter_01302026_s2_d0.png|train/9/meter_10092025_s3_d9.png|train/0/meter_01132026_s2_d0.png|train/7/meter_10092025_s2_d7.png,94,2,0907 +composed,7,train/7/synthetic_window_00094_s3_d7.png,train/0/meter_01302026_s2_d0.png|train/9/meter_10092025_s3_d9.png|train/0/meter_01132026_s2_d0.png|train/7/meter_10092025_s2_d7.png,94,3,0907 +composed,4,train/4/synthetic_window_00095_s0_d4.png,train/4/meter_07012020_s3_d4.png|train/9/meter_11112020_s3_d9.png|train/8/meter_11112020_s1_d8.png|train/6/meter_03032026_s3_d6.png,95,0,4986 +composed,9,train/9/synthetic_window_00095_s1_d9.png,train/4/meter_07012020_s3_d4.png|train/9/meter_11112020_s3_d9.png|train/8/meter_11112020_s1_d8.png|train/6/meter_03032026_s3_d6.png,95,1,4986 +composed,8,train/8/synthetic_window_00095_s2_d8.png,train/4/meter_07012020_s3_d4.png|train/9/meter_11112020_s3_d9.png|train/8/meter_11112020_s1_d8.png|train/6/meter_03032026_s3_d6.png,95,2,4986 +composed,6,train/6/synthetic_window_00095_s3_d6.png,train/4/meter_07012020_s3_d4.png|train/9/meter_11112020_s3_d9.png|train/8/meter_11112020_s1_d8.png|train/6/meter_03032026_s3_d6.png,95,3,4986 +composed,7,train/7/synthetic_window_00096_s0_d7.png,train/7/meter_07012020_s1_d7.png|train/1/meter_07012020_s0_d1.png|train/8/meter_07012020_s2_d8.png|train/5/meter_02272026_s3_d5.png,96,0,7185 +composed,1,train/1/synthetic_window_00096_s1_d1.png,train/7/meter_07012020_s1_d7.png|train/1/meter_07012020_s0_d1.png|train/8/meter_07012020_s2_d8.png|train/5/meter_02272026_s3_d5.png,96,1,7185 +composed,8,train/8/synthetic_window_00096_s2_d8.png,train/7/meter_07012020_s1_d7.png|train/1/meter_07012020_s0_d1.png|train/8/meter_07012020_s2_d8.png|train/5/meter_02272026_s3_d5.png,96,2,7185 +composed,5,train/5/synthetic_window_00096_s3_d5.png,train/7/meter_07012020_s1_d7.png|train/1/meter_07012020_s0_d1.png|train/8/meter_07012020_s2_d8.png|train/5/meter_02272026_s3_d5.png,96,3,7185 +composed,3,train/3/synthetic_window_00097_s0_d3.png,train/3/meter_01132026_s1_d3.png|train/0/meter_01132026_s2_d0.png|train/4/meter_07012020_s3_d4.png|train/1/meter_02242026_s2_d1.png,97,0,3041 +composed,0,train/0/synthetic_window_00097_s1_d0.png,train/3/meter_01132026_s1_d3.png|train/0/meter_01132026_s2_d0.png|train/4/meter_07012020_s3_d4.png|train/1/meter_02242026_s2_d1.png,97,1,3041 +composed,4,train/4/synthetic_window_00097_s2_d4.png,train/3/meter_01132026_s1_d3.png|train/0/meter_01132026_s2_d0.png|train/4/meter_07012020_s3_d4.png|train/1/meter_02242026_s2_d1.png,97,2,3041 +composed,1,train/1/synthetic_window_00097_s3_d1.png,train/3/meter_01132026_s1_d3.png|train/0/meter_01132026_s2_d0.png|train/4/meter_07012020_s3_d4.png|train/1/meter_02242026_s2_d1.png,97,3,3041 +composed,1,train/1/synthetic_window_00098_s0_d1.png,train/1/meter_02272026_s2_d1.png|train/4/meter_02242026_s3_d4.png|train/0/meter_01132026_s2_d0.png|train/1/meter_02192026_s2_d1.png,98,0,1401 +composed,4,train/4/synthetic_window_00098_s1_d4.png,train/1/meter_02272026_s2_d1.png|train/4/meter_02242026_s3_d4.png|train/0/meter_01132026_s2_d0.png|train/1/meter_02192026_s2_d1.png,98,1,1401 +composed,0,train/0/synthetic_window_00098_s2_d0.png,train/1/meter_02272026_s2_d1.png|train/4/meter_02242026_s3_d4.png|train/0/meter_01132026_s2_d0.png|train/1/meter_02192026_s2_d1.png,98,2,1401 +composed,1,train/1/synthetic_window_00098_s3_d1.png,train/1/meter_02272026_s2_d1.png|train/4/meter_02242026_s3_d4.png|train/0/meter_01132026_s2_d0.png|train/1/meter_02192026_s2_d1.png,98,3,1401 +composed,4,train/4/synthetic_window_00099_s0_d4.png,train/4/meter_02242026_s3_d4.png|train/0/meter_01132026_s2_d0.png|train/3/meter_01122026_s1_d3.png|train/1/meter_02192026_s2_d1.png,99,0,4031 +composed,0,train/0/synthetic_window_00099_s1_d0.png,train/4/meter_02242026_s3_d4.png|train/0/meter_01132026_s2_d0.png|train/3/meter_01122026_s1_d3.png|train/1/meter_02192026_s2_d1.png,99,1,4031 +composed,3,train/3/synthetic_window_00099_s2_d3.png,train/4/meter_02242026_s3_d4.png|train/0/meter_01132026_s2_d0.png|train/3/meter_01122026_s1_d3.png|train/1/meter_02192026_s2_d1.png,99,2,4031 +composed,1,train/1/synthetic_window_00099_s3_d1.png,train/4/meter_02242026_s3_d4.png|train/0/meter_01132026_s2_d0.png|train/3/meter_01122026_s1_d3.png|train/1/meter_02192026_s2_d1.png,99,3,4031 +composed,2,train/2/synthetic_window_00100_s0_d2.png,train/2/meter_01122026_s3_d2.png|train/2/meter_02272026_s0_d2.png|train/3/meter_02242026_s1_d3.png|train/3/meter_02242026_s1_d3.png,100,0,2233 +composed,2,train/2/synthetic_window_00100_s1_d2.png,train/2/meter_01122026_s3_d2.png|train/2/meter_02272026_s0_d2.png|train/3/meter_02242026_s1_d3.png|train/3/meter_02242026_s1_d3.png,100,1,2233 +composed,3,train/3/synthetic_window_00100_s2_d3.png,train/2/meter_01122026_s3_d2.png|train/2/meter_02272026_s0_d2.png|train/3/meter_02242026_s1_d3.png|train/3/meter_02242026_s1_d3.png,100,2,2233 +composed,3,train/3/synthetic_window_00100_s3_d3.png,train/2/meter_01122026_s3_d2.png|train/2/meter_02272026_s0_d2.png|train/3/meter_02242026_s1_d3.png|train/3/meter_02242026_s1_d3.png,100,3,2233 +composed,7,train/7/synthetic_window_00101_s0_d7.png,train/7/meter_10092025_s2_d7.png|train/9/meter_11112020_s3_d9.png|train/7/meter_07012020_s1_d7.png|train/0/meter_01302026_s2_d0.png,101,0,7970 +composed,9,train/9/synthetic_window_00101_s1_d9.png,train/7/meter_10092025_s2_d7.png|train/9/meter_11112020_s3_d9.png|train/7/meter_07012020_s1_d7.png|train/0/meter_01302026_s2_d0.png,101,1,7970 +composed,7,train/7/synthetic_window_00101_s2_d7.png,train/7/meter_10092025_s2_d7.png|train/9/meter_11112020_s3_d9.png|train/7/meter_07012020_s1_d7.png|train/0/meter_01302026_s2_d0.png,101,2,7970 +composed,0,train/0/synthetic_window_00101_s3_d0.png,train/7/meter_10092025_s2_d7.png|train/9/meter_11112020_s3_d9.png|train/7/meter_07012020_s1_d7.png|train/0/meter_01302026_s2_d0.png,101,3,7970 +composed,3,train/3/synthetic_window_00102_s0_d3.png,train/3/meter_02202026_s1_d3.png|train/8/meter_07012020_s2_d8.png|train/4/meter_07012020_s3_d4.png|train/5/meter_02272026_s3_d5.png,102,0,3845 +composed,8,train/8/synthetic_window_00102_s1_d8.png,train/3/meter_02202026_s1_d3.png|train/8/meter_07012020_s2_d8.png|train/4/meter_07012020_s3_d4.png|train/5/meter_02272026_s3_d5.png,102,1,3845 +composed,4,train/4/synthetic_window_00102_s2_d4.png,train/3/meter_02202026_s1_d3.png|train/8/meter_07012020_s2_d8.png|train/4/meter_07012020_s3_d4.png|train/5/meter_02272026_s3_d5.png,102,2,3845 +composed,5,train/5/synthetic_window_00102_s3_d5.png,train/3/meter_02202026_s1_d3.png|train/8/meter_07012020_s2_d8.png|train/4/meter_07012020_s3_d4.png|train/5/meter_02272026_s3_d5.png,102,3,3845 +composed,2,train/2/synthetic_window_00103_s0_d2.png,train/2/meter_01122026_s3_d2.png|train/8/meter_07012020_s2_d8.png|train/7/meter_01302026_s3_d7.png|train/7/meter_07012020_s1_d7.png,103,0,2877 +composed,8,train/8/synthetic_window_00103_s1_d8.png,train/2/meter_01122026_s3_d2.png|train/8/meter_07012020_s2_d8.png|train/7/meter_01302026_s3_d7.png|train/7/meter_07012020_s1_d7.png,103,1,2877 +composed,7,train/7/synthetic_window_00103_s2_d7.png,train/2/meter_01122026_s3_d2.png|train/8/meter_07012020_s2_d8.png|train/7/meter_01302026_s3_d7.png|train/7/meter_07012020_s1_d7.png,103,2,2877 +composed,7,train/7/synthetic_window_00103_s3_d7.png,train/2/meter_01122026_s3_d2.png|train/8/meter_07012020_s2_d8.png|train/7/meter_01302026_s3_d7.png|train/7/meter_07012020_s1_d7.png,103,3,2877 +composed,9,train/9/synthetic_window_00104_s0_d9.png,train/9/meter_11112020_s3_d9.png|train/7/meter_10092025_s2_d7.png|train/7/meter_01302026_s3_d7.png|train/4/meter_07012020_s3_d4.png,104,0,9774 +composed,7,train/7/synthetic_window_00104_s1_d7.png,train/9/meter_11112020_s3_d9.png|train/7/meter_10092025_s2_d7.png|train/7/meter_01302026_s3_d7.png|train/4/meter_07012020_s3_d4.png,104,1,9774 +composed,7,train/7/synthetic_window_00104_s2_d7.png,train/9/meter_11112020_s3_d9.png|train/7/meter_10092025_s2_d7.png|train/7/meter_01302026_s3_d7.png|train/4/meter_07012020_s3_d4.png,104,2,9774 +composed,4,train/4/synthetic_window_00104_s3_d4.png,train/9/meter_11112020_s3_d9.png|train/7/meter_10092025_s2_d7.png|train/7/meter_01302026_s3_d7.png|train/4/meter_07012020_s3_d4.png,104,3,9774 +composed,3,train/3/synthetic_window_00105_s0_d3.png,train/3/meter_01302026_s1_d3.png|train/1/meter_11112020_s2_d1.png|train/8/meter_11112020_s1_d8.png|train/9/meter_11112020_s3_d9.png,105,0,3189 +composed,1,train/1/synthetic_window_00105_s1_d1.png,train/3/meter_01302026_s1_d3.png|train/1/meter_11112020_s2_d1.png|train/8/meter_11112020_s1_d8.png|train/9/meter_11112020_s3_d9.png,105,1,3189 +composed,8,train/8/synthetic_window_00105_s2_d8.png,train/3/meter_01302026_s1_d3.png|train/1/meter_11112020_s2_d1.png|train/8/meter_11112020_s1_d8.png|train/9/meter_11112020_s3_d9.png,105,2,3189 +composed,9,train/9/synthetic_window_00105_s3_d9.png,train/3/meter_01302026_s1_d3.png|train/1/meter_11112020_s2_d1.png|train/8/meter_11112020_s1_d8.png|train/9/meter_11112020_s3_d9.png,105,3,3189 +composed,1,train/1/synthetic_window_00106_s0_d1.png,train/1/meter_03032026_s2_d1.png|train/8/meter_07012020_s2_d8.png|train/8/meter_07012020_s2_d8.png|train/5/meter_02272026_s3_d5.png,106,0,1885 +composed,8,train/8/synthetic_window_00106_s1_d8.png,train/1/meter_03032026_s2_d1.png|train/8/meter_07012020_s2_d8.png|train/8/meter_07012020_s2_d8.png|train/5/meter_02272026_s3_d5.png,106,1,1885 +composed,8,train/8/synthetic_window_00106_s2_d8.png,train/1/meter_03032026_s2_d1.png|train/8/meter_07012020_s2_d8.png|train/8/meter_07012020_s2_d8.png|train/5/meter_02272026_s3_d5.png,106,2,1885 +composed,5,train/5/synthetic_window_00106_s3_d5.png,train/1/meter_03032026_s2_d1.png|train/8/meter_07012020_s2_d8.png|train/8/meter_07012020_s2_d8.png|train/5/meter_02272026_s3_d5.png,106,3,1885 +composed,1,train/1/synthetic_window_00107_s0_d1.png,train/1/meter_07012020_s0_d1.png|train/4/meter_02242026_s3_d4.png|train/4/meter_07012020_s3_d4.png|train/2/meter_02272026_s0_d2.png,107,0,1442 +composed,4,train/4/synthetic_window_00107_s1_d4.png,train/1/meter_07012020_s0_d1.png|train/4/meter_02242026_s3_d4.png|train/4/meter_07012020_s3_d4.png|train/2/meter_02272026_s0_d2.png,107,1,1442 +composed,4,train/4/synthetic_window_00107_s2_d4.png,train/1/meter_07012020_s0_d1.png|train/4/meter_02242026_s3_d4.png|train/4/meter_07012020_s3_d4.png|train/2/meter_02272026_s0_d2.png,107,2,1442 +composed,2,train/2/synthetic_window_00107_s3_d2.png,train/1/meter_07012020_s0_d1.png|train/4/meter_02242026_s3_d4.png|train/4/meter_07012020_s3_d4.png|train/2/meter_02272026_s0_d2.png,107,3,1442 +composed,7,train/7/synthetic_window_00108_s0_d7.png,train/7/meter_07012020_s1_d7.png|train/6/meter_03032026_s3_d6.png|train/1/meter_02192026_s2_d1.png|train/1/meter_02242026_s2_d1.png,108,0,7611 +composed,6,train/6/synthetic_window_00108_s1_d6.png,train/7/meter_07012020_s1_d7.png|train/6/meter_03032026_s3_d6.png|train/1/meter_02192026_s2_d1.png|train/1/meter_02242026_s2_d1.png,108,1,7611 +composed,1,train/1/synthetic_window_00108_s2_d1.png,train/7/meter_07012020_s1_d7.png|train/6/meter_03032026_s3_d6.png|train/1/meter_02192026_s2_d1.png|train/1/meter_02242026_s2_d1.png,108,2,7611 +composed,1,train/1/synthetic_window_00108_s3_d1.png,train/7/meter_07012020_s1_d7.png|train/6/meter_03032026_s3_d6.png|train/1/meter_02192026_s2_d1.png|train/1/meter_02242026_s2_d1.png,108,3,7611 +composed,9,train/9/synthetic_window_00109_s0_d9.png,train/9/meter_11112020_s3_d9.png|train/3/meter_02272026_s1_d3.png|train/8/meter_11112020_s1_d8.png|train/3/meter_01132026_s1_d3.png,109,0,9383 +composed,3,train/3/synthetic_window_00109_s1_d3.png,train/9/meter_11112020_s3_d9.png|train/3/meter_02272026_s1_d3.png|train/8/meter_11112020_s1_d8.png|train/3/meter_01132026_s1_d3.png,109,1,9383 +composed,8,train/8/synthetic_window_00109_s2_d8.png,train/9/meter_11112020_s3_d9.png|train/3/meter_02272026_s1_d3.png|train/8/meter_11112020_s1_d8.png|train/3/meter_01132026_s1_d3.png,109,2,9383 +composed,3,train/3/synthetic_window_00109_s3_d3.png,train/9/meter_11112020_s3_d9.png|train/3/meter_02272026_s1_d3.png|train/8/meter_11112020_s1_d8.png|train/3/meter_01132026_s1_d3.png,109,3,9383 +composed,3,train/3/synthetic_window_00110_s0_d3.png,train/3/meter_01132026_s1_d3.png|train/4/meter_07012020_s3_d4.png|train/0/meter_01302026_s2_d0.png|train/7/meter_01302026_s3_d7.png,110,0,3407 +composed,4,train/4/synthetic_window_00110_s1_d4.png,train/3/meter_01132026_s1_d3.png|train/4/meter_07012020_s3_d4.png|train/0/meter_01302026_s2_d0.png|train/7/meter_01302026_s3_d7.png,110,1,3407 +composed,0,train/0/synthetic_window_00110_s2_d0.png,train/3/meter_01132026_s1_d3.png|train/4/meter_07012020_s3_d4.png|train/0/meter_01302026_s2_d0.png|train/7/meter_01302026_s3_d7.png,110,2,3407 +composed,7,train/7/synthetic_window_00110_s3_d7.png,train/3/meter_01132026_s1_d3.png|train/4/meter_07012020_s3_d4.png|train/0/meter_01302026_s2_d0.png|train/7/meter_01302026_s3_d7.png,110,3,3407 +composed,1,train/1/synthetic_window_00111_s0_d1.png,train/1/meter_02202026_s2_d1.png|train/3/meter_02242026_s1_d3.png|train/0/meter_01302026_s2_d0.png|train/3/meter_02272026_s1_d3.png,111,0,1303 +composed,3,train/3/synthetic_window_00111_s1_d3.png,train/1/meter_02202026_s2_d1.png|train/3/meter_02242026_s1_d3.png|train/0/meter_01302026_s2_d0.png|train/3/meter_02272026_s1_d3.png,111,1,1303 +composed,0,train/0/synthetic_window_00111_s2_d0.png,train/1/meter_02202026_s2_d1.png|train/3/meter_02242026_s1_d3.png|train/0/meter_01302026_s2_d0.png|train/3/meter_02272026_s1_d3.png,111,2,1303 +composed,3,train/3/synthetic_window_00111_s3_d3.png,train/1/meter_02202026_s2_d1.png|train/3/meter_02242026_s1_d3.png|train/0/meter_01302026_s2_d0.png|train/3/meter_02272026_s1_d3.png,111,3,1303 +composed,5,train/5/synthetic_window_00112_s0_d5.png,train/5/meter_02272026_s3_d5.png|train/2/meter_02202026_s0_d2.png|train/2/meter_02202026_s0_d2.png|train/7/meter_01302026_s3_d7.png,112,0,5227 +composed,2,train/2/synthetic_window_00112_s1_d2.png,train/5/meter_02272026_s3_d5.png|train/2/meter_02202026_s0_d2.png|train/2/meter_02202026_s0_d2.png|train/7/meter_01302026_s3_d7.png,112,1,5227 +composed,2,train/2/synthetic_window_00112_s2_d2.png,train/5/meter_02272026_s3_d5.png|train/2/meter_02202026_s0_d2.png|train/2/meter_02202026_s0_d2.png|train/7/meter_01302026_s3_d7.png,112,2,5227 +composed,7,train/7/synthetic_window_00112_s3_d7.png,train/5/meter_02272026_s3_d5.png|train/2/meter_02202026_s0_d2.png|train/2/meter_02202026_s0_d2.png|train/7/meter_01302026_s3_d7.png,112,3,5227 +composed,9,train/9/synthetic_window_00113_s0_d9.png,train/9/meter_10092025_s3_d9.png|train/3/meter_02162026_s1_d3.png|train/4/meter_02242026_s3_d4.png|train/0/meter_01132026_s2_d0.png,113,0,9340 +composed,3,train/3/synthetic_window_00113_s1_d3.png,train/9/meter_10092025_s3_d9.png|train/3/meter_02162026_s1_d3.png|train/4/meter_02242026_s3_d4.png|train/0/meter_01132026_s2_d0.png,113,1,9340 +composed,4,train/4/synthetic_window_00113_s2_d4.png,train/9/meter_10092025_s3_d9.png|train/3/meter_02162026_s1_d3.png|train/4/meter_02242026_s3_d4.png|train/0/meter_01132026_s2_d0.png,113,2,9340 +composed,0,train/0/synthetic_window_00113_s3_d0.png,train/9/meter_10092025_s3_d9.png|train/3/meter_02162026_s1_d3.png|train/4/meter_02242026_s3_d4.png|train/0/meter_01132026_s2_d0.png,113,3,9340 +composed,4,train/4/synthetic_window_00114_s0_d4.png,train/4/meter_02242026_s3_d4.png|train/0/meter_01122026_s2_d0.png|train/1/meter_02162026_s2_d1.png|train/0/meter_01122026_s2_d0.png,114,0,4010 +composed,0,train/0/synthetic_window_00114_s1_d0.png,train/4/meter_02242026_s3_d4.png|train/0/meter_01122026_s2_d0.png|train/1/meter_02162026_s2_d1.png|train/0/meter_01122026_s2_d0.png,114,1,4010 +composed,1,train/1/synthetic_window_00114_s2_d1.png,train/4/meter_02242026_s3_d4.png|train/0/meter_01122026_s2_d0.png|train/1/meter_02162026_s2_d1.png|train/0/meter_01122026_s2_d0.png,114,2,4010 +composed,0,train/0/synthetic_window_00114_s3_d0.png,train/4/meter_02242026_s3_d4.png|train/0/meter_01122026_s2_d0.png|train/1/meter_02162026_s2_d1.png|train/0/meter_01122026_s2_d0.png,114,3,4010 +composed,7,train/7/synthetic_window_00115_s0_d7.png,train/7/meter_07012020_s1_d7.png|train/8/meter_11112020_s1_d8.png|train/1/meter_07012020_s0_d1.png|train/9/meter_10092025_s3_d9.png,115,0,7819 +composed,8,train/8/synthetic_window_00115_s1_d8.png,train/7/meter_07012020_s1_d7.png|train/8/meter_11112020_s1_d8.png|train/1/meter_07012020_s0_d1.png|train/9/meter_10092025_s3_d9.png,115,1,7819 +composed,1,train/1/synthetic_window_00115_s2_d1.png,train/7/meter_07012020_s1_d7.png|train/8/meter_11112020_s1_d8.png|train/1/meter_07012020_s0_d1.png|train/9/meter_10092025_s3_d9.png,115,2,7819 +composed,9,train/9/synthetic_window_00115_s3_d9.png,train/7/meter_07012020_s1_d7.png|train/8/meter_11112020_s1_d8.png|train/1/meter_07012020_s0_d1.png|train/9/meter_10092025_s3_d9.png,115,3,7819 +composed,7,train/7/synthetic_window_00116_s0_d7.png,train/7/meter_10092025_s2_d7.png|train/8/meter_11112020_s1_d8.png|train/1/meter_11112020_s2_d1.png|train/8/meter_07012020_s2_d8.png,116,0,7818 +composed,8,train/8/synthetic_window_00116_s1_d8.png,train/7/meter_10092025_s2_d7.png|train/8/meter_11112020_s1_d8.png|train/1/meter_11112020_s2_d1.png|train/8/meter_07012020_s2_d8.png,116,1,7818 +composed,1,train/1/synthetic_window_00116_s2_d1.png,train/7/meter_10092025_s2_d7.png|train/8/meter_11112020_s1_d8.png|train/1/meter_11112020_s2_d1.png|train/8/meter_07012020_s2_d8.png,116,2,7818 +composed,8,train/8/synthetic_window_00116_s3_d8.png,train/7/meter_10092025_s2_d7.png|train/8/meter_11112020_s1_d8.png|train/1/meter_11112020_s2_d1.png|train/8/meter_07012020_s2_d8.png,116,3,7818 +composed,8,train/8/synthetic_window_00117_s0_d8.png,train/8/meter_07012020_s2_d8.png|train/9/meter_10092025_s3_d9.png|train/4/meter_02242026_s3_d4.png|train/4/meter_07012020_s3_d4.png,117,0,8944 +composed,9,train/9/synthetic_window_00117_s1_d9.png,train/8/meter_07012020_s2_d8.png|train/9/meter_10092025_s3_d9.png|train/4/meter_02242026_s3_d4.png|train/4/meter_07012020_s3_d4.png,117,1,8944 +composed,4,train/4/synthetic_window_00117_s2_d4.png,train/8/meter_07012020_s2_d8.png|train/9/meter_10092025_s3_d9.png|train/4/meter_02242026_s3_d4.png|train/4/meter_07012020_s3_d4.png,117,2,8944 +composed,4,train/4/synthetic_window_00117_s3_d4.png,train/8/meter_07012020_s2_d8.png|train/9/meter_10092025_s3_d9.png|train/4/meter_02242026_s3_d4.png|train/4/meter_07012020_s3_d4.png,117,3,8944 +composed,9,train/9/synthetic_window_00118_s0_d9.png,train/9/meter_11112020_s3_d9.png|train/7/meter_01302026_s3_d7.png|train/0/meter_01122026_s2_d0.png|train/9/meter_11112020_s3_d9.png,118,0,9709 +composed,7,train/7/synthetic_window_00118_s1_d7.png,train/9/meter_11112020_s3_d9.png|train/7/meter_01302026_s3_d7.png|train/0/meter_01122026_s2_d0.png|train/9/meter_11112020_s3_d9.png,118,1,9709 +composed,0,train/0/synthetic_window_00118_s2_d0.png,train/9/meter_11112020_s3_d9.png|train/7/meter_01302026_s3_d7.png|train/0/meter_01122026_s2_d0.png|train/9/meter_11112020_s3_d9.png,118,2,9709 +composed,9,train/9/synthetic_window_00118_s3_d9.png,train/9/meter_11112020_s3_d9.png|train/7/meter_01302026_s3_d7.png|train/0/meter_01122026_s2_d0.png|train/9/meter_11112020_s3_d9.png,118,3,9709 +composed,1,train/1/synthetic_window_00119_s0_d1.png,train/1/meter_03032026_s2_d1.png|train/2/meter_03032026_s0_d2.png|train/0/meter_01122026_s2_d0.png|train/1/meter_02272026_s2_d1.png,119,0,1201 +composed,2,train/2/synthetic_window_00119_s1_d2.png,train/1/meter_03032026_s2_d1.png|train/2/meter_03032026_s0_d2.png|train/0/meter_01122026_s2_d0.png|train/1/meter_02272026_s2_d1.png,119,1,1201 +composed,0,train/0/synthetic_window_00119_s2_d0.png,train/1/meter_03032026_s2_d1.png|train/2/meter_03032026_s0_d2.png|train/0/meter_01122026_s2_d0.png|train/1/meter_02272026_s2_d1.png,119,2,1201 +composed,1,train/1/synthetic_window_00119_s3_d1.png,train/1/meter_03032026_s2_d1.png|train/2/meter_03032026_s0_d2.png|train/0/meter_01122026_s2_d0.png|train/1/meter_02272026_s2_d1.png,119,3,1201 +composed,8,train/8/synthetic_window_00120_s0_d8.png,train/8/meter_11112020_s1_d8.png|train/5/meter_02272026_s3_d5.png|train/4/meter_07012020_s3_d4.png|train/9/meter_11112020_s3_d9.png,120,0,8549 +composed,5,train/5/synthetic_window_00120_s1_d5.png,train/8/meter_11112020_s1_d8.png|train/5/meter_02272026_s3_d5.png|train/4/meter_07012020_s3_d4.png|train/9/meter_11112020_s3_d9.png,120,1,8549 +composed,4,train/4/synthetic_window_00120_s2_d4.png,train/8/meter_11112020_s1_d8.png|train/5/meter_02272026_s3_d5.png|train/4/meter_07012020_s3_d4.png|train/9/meter_11112020_s3_d9.png,120,2,8549 +composed,9,train/9/synthetic_window_00120_s3_d9.png,train/8/meter_11112020_s1_d8.png|train/5/meter_02272026_s3_d5.png|train/4/meter_07012020_s3_d4.png|train/9/meter_11112020_s3_d9.png,120,3,8549 +composed,0,train/0/synthetic_window_00121_s0_d0.png,train/0/meter_01122026_s2_d0.png|train/7/meter_01302026_s3_d7.png|train/5/meter_02272026_s3_d5.png|train/2/meter_01122026_s0_d2.png,121,0,0752 +composed,7,train/7/synthetic_window_00121_s1_d7.png,train/0/meter_01122026_s2_d0.png|train/7/meter_01302026_s3_d7.png|train/5/meter_02272026_s3_d5.png|train/2/meter_01122026_s0_d2.png,121,1,0752 +composed,5,train/5/synthetic_window_00121_s2_d5.png,train/0/meter_01122026_s2_d0.png|train/7/meter_01302026_s3_d7.png|train/5/meter_02272026_s3_d5.png|train/2/meter_01122026_s0_d2.png,121,2,0752 +composed,2,train/2/synthetic_window_00121_s3_d2.png,train/0/meter_01122026_s2_d0.png|train/7/meter_01302026_s3_d7.png|train/5/meter_02272026_s3_d5.png|train/2/meter_01122026_s0_d2.png,121,3,0752 +composed,4,train/4/synthetic_window_00122_s0_d4.png,train/4/meter_07012020_s3_d4.png|train/9/meter_11112020_s3_d9.png|train/1/meter_02202026_s2_d1.png|train/3/meter_02162026_s1_d3.png,122,0,4913 +composed,9,train/9/synthetic_window_00122_s1_d9.png,train/4/meter_07012020_s3_d4.png|train/9/meter_11112020_s3_d9.png|train/1/meter_02202026_s2_d1.png|train/3/meter_02162026_s1_d3.png,122,1,4913 +composed,1,train/1/synthetic_window_00122_s2_d1.png,train/4/meter_07012020_s3_d4.png|train/9/meter_11112020_s3_d9.png|train/1/meter_02202026_s2_d1.png|train/3/meter_02162026_s1_d3.png,122,2,4913 +composed,3,train/3/synthetic_window_00122_s3_d3.png,train/4/meter_07012020_s3_d4.png|train/9/meter_11112020_s3_d9.png|train/1/meter_02202026_s2_d1.png|train/3/meter_02162026_s1_d3.png,122,3,4913 +composed,5,train/5/synthetic_window_00123_s0_d5.png,train/5/meter_02272026_s3_d5.png|train/1/meter_02192026_s2_d1.png|train/1/meter_02242026_s2_d1.png|train/4/meter_02242026_s3_d4.png,123,0,5114 +composed,1,train/1/synthetic_window_00123_s1_d1.png,train/5/meter_02272026_s3_d5.png|train/1/meter_02192026_s2_d1.png|train/1/meter_02242026_s2_d1.png|train/4/meter_02242026_s3_d4.png,123,1,5114 +composed,1,train/1/synthetic_window_00123_s2_d1.png,train/5/meter_02272026_s3_d5.png|train/1/meter_02192026_s2_d1.png|train/1/meter_02242026_s2_d1.png|train/4/meter_02242026_s3_d4.png,123,2,5114 +composed,4,train/4/synthetic_window_00123_s3_d4.png,train/5/meter_02272026_s3_d5.png|train/1/meter_02192026_s2_d1.png|train/1/meter_02242026_s2_d1.png|train/4/meter_02242026_s3_d4.png,123,3,5114 +composed,4,train/4/synthetic_window_00124_s0_d4.png,train/4/meter_02242026_s3_d4.png|train/5/meter_02272026_s3_d5.png|train/6/meter_03032026_s3_d6.png|train/4/meter_02242026_s3_d4.png,124,0,4564 +composed,5,train/5/synthetic_window_00124_s1_d5.png,train/4/meter_02242026_s3_d4.png|train/5/meter_02272026_s3_d5.png|train/6/meter_03032026_s3_d6.png|train/4/meter_02242026_s3_d4.png,124,1,4564 +composed,6,train/6/synthetic_window_00124_s2_d6.png,train/4/meter_02242026_s3_d4.png|train/5/meter_02272026_s3_d5.png|train/6/meter_03032026_s3_d6.png|train/4/meter_02242026_s3_d4.png,124,2,4564 +composed,4,train/4/synthetic_window_00124_s3_d4.png,train/4/meter_02242026_s3_d4.png|train/5/meter_02272026_s3_d5.png|train/6/meter_03032026_s3_d6.png|train/4/meter_02242026_s3_d4.png,124,3,4564 +composed,6,train/6/synthetic_window_00125_s0_d6.png,train/6/meter_03032026_s3_d6.png|train/6/meter_03032026_s3_d6.png|train/5/meter_02272026_s3_d5.png|train/2/meter_01132026_s0_d2.png,125,0,6652 +composed,6,train/6/synthetic_window_00125_s1_d6.png,train/6/meter_03032026_s3_d6.png|train/6/meter_03032026_s3_d6.png|train/5/meter_02272026_s3_d5.png|train/2/meter_01132026_s0_d2.png,125,1,6652 +composed,5,train/5/synthetic_window_00125_s2_d5.png,train/6/meter_03032026_s3_d6.png|train/6/meter_03032026_s3_d6.png|train/5/meter_02272026_s3_d5.png|train/2/meter_01132026_s0_d2.png,125,2,6652 +composed,2,train/2/synthetic_window_00125_s3_d2.png,train/6/meter_03032026_s3_d6.png|train/6/meter_03032026_s3_d6.png|train/5/meter_02272026_s3_d5.png|train/2/meter_01132026_s0_d2.png,125,3,6652 +composed,9,train/9/synthetic_window_00126_s0_d9.png,train/9/meter_11112020_s3_d9.png|train/4/meter_07012020_s3_d4.png|train/9/meter_10092025_s3_d9.png|train/1/meter_02272026_s2_d1.png,126,0,9491 +composed,4,train/4/synthetic_window_00126_s1_d4.png,train/9/meter_11112020_s3_d9.png|train/4/meter_07012020_s3_d4.png|train/9/meter_10092025_s3_d9.png|train/1/meter_02272026_s2_d1.png,126,1,9491 +composed,9,train/9/synthetic_window_00126_s2_d9.png,train/9/meter_11112020_s3_d9.png|train/4/meter_07012020_s3_d4.png|train/9/meter_10092025_s3_d9.png|train/1/meter_02272026_s2_d1.png,126,2,9491 +composed,1,train/1/synthetic_window_00126_s3_d1.png,train/9/meter_11112020_s3_d9.png|train/4/meter_07012020_s3_d4.png|train/9/meter_10092025_s3_d9.png|train/1/meter_02272026_s2_d1.png,126,3,9491 +composed,6,train/6/synthetic_window_00127_s0_d6.png,train/6/meter_03032026_s3_d6.png|train/9/meter_11112020_s3_d9.png|train/0/meter_01132026_s2_d0.png|train/4/meter_07012020_s3_d4.png,127,0,6904 +composed,9,train/9/synthetic_window_00127_s1_d9.png,train/6/meter_03032026_s3_d6.png|train/9/meter_11112020_s3_d9.png|train/0/meter_01132026_s2_d0.png|train/4/meter_07012020_s3_d4.png,127,1,6904 +composed,0,train/0/synthetic_window_00127_s2_d0.png,train/6/meter_03032026_s3_d6.png|train/9/meter_11112020_s3_d9.png|train/0/meter_01132026_s2_d0.png|train/4/meter_07012020_s3_d4.png,127,2,6904 +composed,4,train/4/synthetic_window_00127_s3_d4.png,train/6/meter_03032026_s3_d6.png|train/9/meter_11112020_s3_d9.png|train/0/meter_01132026_s2_d0.png|train/4/meter_07012020_s3_d4.png,127,3,6904 +composed,1,train/1/synthetic_window_00128_s0_d1.png,train/1/meter_02192026_s2_d1.png|train/6/meter_03032026_s3_d6.png|train/1/meter_02162026_s2_d1.png|train/4/meter_07012020_s3_d4.png,128,0,1614 +composed,6,train/6/synthetic_window_00128_s1_d6.png,train/1/meter_02192026_s2_d1.png|train/6/meter_03032026_s3_d6.png|train/1/meter_02162026_s2_d1.png|train/4/meter_07012020_s3_d4.png,128,1,1614 +composed,1,train/1/synthetic_window_00128_s2_d1.png,train/1/meter_02192026_s2_d1.png|train/6/meter_03032026_s3_d6.png|train/1/meter_02162026_s2_d1.png|train/4/meter_07012020_s3_d4.png,128,2,1614 +composed,4,train/4/synthetic_window_00128_s3_d4.png,train/1/meter_02192026_s2_d1.png|train/6/meter_03032026_s3_d6.png|train/1/meter_02162026_s2_d1.png|train/4/meter_07012020_s3_d4.png,128,3,1614 +composed,0,train/0/synthetic_window_00129_s0_d0.png,train/0/meter_01132026_s2_d0.png|train/4/meter_02242026_s3_d4.png|train/6/meter_03032026_s3_d6.png|train/7/meter_01302026_s3_d7.png,129,0,0467 +composed,4,train/4/synthetic_window_00129_s1_d4.png,train/0/meter_01132026_s2_d0.png|train/4/meter_02242026_s3_d4.png|train/6/meter_03032026_s3_d6.png|train/7/meter_01302026_s3_d7.png,129,1,0467 +composed,6,train/6/synthetic_window_00129_s2_d6.png,train/0/meter_01132026_s2_d0.png|train/4/meter_02242026_s3_d4.png|train/6/meter_03032026_s3_d6.png|train/7/meter_01302026_s3_d7.png,129,2,0467 +composed,7,train/7/synthetic_window_00129_s3_d7.png,train/0/meter_01132026_s2_d0.png|train/4/meter_02242026_s3_d4.png|train/6/meter_03032026_s3_d6.png|train/7/meter_01302026_s3_d7.png,129,3,0467 +composed,2,train/2/synthetic_window_00130_s0_d2.png,train/2/meter_01132026_s3_d2.png|train/7/meter_10092025_s2_d7.png|train/5/meter_02272026_s3_d5.png|train/2/meter_01132026_s0_d2.png,130,0,2752 +composed,7,train/7/synthetic_window_00130_s1_d7.png,train/2/meter_01132026_s3_d2.png|train/7/meter_10092025_s2_d7.png|train/5/meter_02272026_s3_d5.png|train/2/meter_01132026_s0_d2.png,130,1,2752 +composed,5,train/5/synthetic_window_00130_s2_d5.png,train/2/meter_01132026_s3_d2.png|train/7/meter_10092025_s2_d7.png|train/5/meter_02272026_s3_d5.png|train/2/meter_01132026_s0_d2.png,130,2,2752 +composed,2,train/2/synthetic_window_00130_s3_d2.png,train/2/meter_01132026_s3_d2.png|train/7/meter_10092025_s2_d7.png|train/5/meter_02272026_s3_d5.png|train/2/meter_01132026_s0_d2.png,130,3,2752 +composed,1,train/1/synthetic_window_00131_s0_d1.png,train/1/meter_02202026_s2_d1.png|train/4/meter_02242026_s3_d4.png|train/8/meter_11112020_s1_d8.png|train/2/meter_02242026_s0_d2.png,131,0,1482 +composed,4,train/4/synthetic_window_00131_s1_d4.png,train/1/meter_02202026_s2_d1.png|train/4/meter_02242026_s3_d4.png|train/8/meter_11112020_s1_d8.png|train/2/meter_02242026_s0_d2.png,131,1,1482 +composed,8,train/8/synthetic_window_00131_s2_d8.png,train/1/meter_02202026_s2_d1.png|train/4/meter_02242026_s3_d4.png|train/8/meter_11112020_s1_d8.png|train/2/meter_02242026_s0_d2.png,131,2,1482 +composed,2,train/2/synthetic_window_00131_s3_d2.png,train/1/meter_02202026_s2_d1.png|train/4/meter_02242026_s3_d4.png|train/8/meter_11112020_s1_d8.png|train/2/meter_02242026_s0_d2.png,131,3,1482 +composed,3,train/3/synthetic_window_00132_s0_d3.png,train/3/meter_01302026_s1_d3.png|train/7/meter_01302026_s3_d7.png|train/4/meter_07012020_s3_d4.png|train/5/meter_02272026_s3_d5.png,132,0,3745 +composed,7,train/7/synthetic_window_00132_s1_d7.png,train/3/meter_01302026_s1_d3.png|train/7/meter_01302026_s3_d7.png|train/4/meter_07012020_s3_d4.png|train/5/meter_02272026_s3_d5.png,132,1,3745 +composed,4,train/4/synthetic_window_00132_s2_d4.png,train/3/meter_01302026_s1_d3.png|train/7/meter_01302026_s3_d7.png|train/4/meter_07012020_s3_d4.png|train/5/meter_02272026_s3_d5.png,132,2,3745 +composed,5,train/5/synthetic_window_00132_s3_d5.png,train/3/meter_01302026_s1_d3.png|train/7/meter_01302026_s3_d7.png|train/4/meter_07012020_s3_d4.png|train/5/meter_02272026_s3_d5.png,132,3,3745 +composed,5,train/5/synthetic_window_00133_s0_d5.png,train/5/meter_02272026_s3_d5.png|train/4/meter_02242026_s3_d4.png|train/6/meter_03032026_s3_d6.png|train/9/meter_11112020_s3_d9.png,133,0,5469 +composed,4,train/4/synthetic_window_00133_s1_d4.png,train/5/meter_02272026_s3_d5.png|train/4/meter_02242026_s3_d4.png|train/6/meter_03032026_s3_d6.png|train/9/meter_11112020_s3_d9.png,133,1,5469 +composed,6,train/6/synthetic_window_00133_s2_d6.png,train/5/meter_02272026_s3_d5.png|train/4/meter_02242026_s3_d4.png|train/6/meter_03032026_s3_d6.png|train/9/meter_11112020_s3_d9.png,133,2,5469 +composed,9,train/9/synthetic_window_00133_s3_d9.png,train/5/meter_02272026_s3_d5.png|train/4/meter_02242026_s3_d4.png|train/6/meter_03032026_s3_d6.png|train/9/meter_11112020_s3_d9.png,133,3,5469 +composed,5,train/5/synthetic_window_00134_s0_d5.png,train/5/meter_02272026_s3_d5.png|train/6/meter_03032026_s3_d6.png|train/7/meter_10092025_s2_d7.png|train/3/meter_02162026_s1_d3.png,134,0,5673 +composed,6,train/6/synthetic_window_00134_s1_d6.png,train/5/meter_02272026_s3_d5.png|train/6/meter_03032026_s3_d6.png|train/7/meter_10092025_s2_d7.png|train/3/meter_02162026_s1_d3.png,134,1,5673 +composed,7,train/7/synthetic_window_00134_s2_d7.png,train/5/meter_02272026_s3_d5.png|train/6/meter_03032026_s3_d6.png|train/7/meter_10092025_s2_d7.png|train/3/meter_02162026_s1_d3.png,134,2,5673 +composed,3,train/3/synthetic_window_00134_s3_d3.png,train/5/meter_02272026_s3_d5.png|train/6/meter_03032026_s3_d6.png|train/7/meter_10092025_s2_d7.png|train/3/meter_02162026_s1_d3.png,134,3,5673 +composed,7,train/7/synthetic_window_00135_s0_d7.png,train/7/meter_07012020_s1_d7.png|train/6/meter_03032026_s3_d6.png|train/7/meter_07012020_s1_d7.png|train/5/meter_02272026_s3_d5.png,135,0,7675 +composed,6,train/6/synthetic_window_00135_s1_d6.png,train/7/meter_07012020_s1_d7.png|train/6/meter_03032026_s3_d6.png|train/7/meter_07012020_s1_d7.png|train/5/meter_02272026_s3_d5.png,135,1,7675 +composed,7,train/7/synthetic_window_00135_s2_d7.png,train/7/meter_07012020_s1_d7.png|train/6/meter_03032026_s3_d6.png|train/7/meter_07012020_s1_d7.png|train/5/meter_02272026_s3_d5.png,135,2,7675 +composed,5,train/5/synthetic_window_00135_s3_d5.png,train/7/meter_07012020_s1_d7.png|train/6/meter_03032026_s3_d6.png|train/7/meter_07012020_s1_d7.png|train/5/meter_02272026_s3_d5.png,135,3,7675 +composed,2,train/2/synthetic_window_00136_s0_d2.png,train/2/meter_10092025_s0_d2.png|train/7/meter_01302026_s3_d7.png|train/1/meter_02162026_s2_d1.png|train/8/meter_11112020_s1_d8.png,136,0,2718 +composed,7,train/7/synthetic_window_00136_s1_d7.png,train/2/meter_10092025_s0_d2.png|train/7/meter_01302026_s3_d7.png|train/1/meter_02162026_s2_d1.png|train/8/meter_11112020_s1_d8.png,136,1,2718 +composed,1,train/1/synthetic_window_00136_s2_d1.png,train/2/meter_10092025_s0_d2.png|train/7/meter_01302026_s3_d7.png|train/1/meter_02162026_s2_d1.png|train/8/meter_11112020_s1_d8.png,136,2,2718 +composed,8,train/8/synthetic_window_00136_s3_d8.png,train/2/meter_10092025_s0_d2.png|train/7/meter_01302026_s3_d7.png|train/1/meter_02162026_s2_d1.png|train/8/meter_11112020_s1_d8.png,136,3,2718 +composed,1,train/1/synthetic_window_00137_s0_d1.png,train/1/meter_02272026_s2_d1.png|train/5/meter_02272026_s3_d5.png|train/5/meter_02272026_s3_d5.png|train/5/meter_02272026_s3_d5.png,137,0,1555 +composed,5,train/5/synthetic_window_00137_s1_d5.png,train/1/meter_02272026_s2_d1.png|train/5/meter_02272026_s3_d5.png|train/5/meter_02272026_s3_d5.png|train/5/meter_02272026_s3_d5.png,137,1,1555 +composed,5,train/5/synthetic_window_00137_s2_d5.png,train/1/meter_02272026_s2_d1.png|train/5/meter_02272026_s3_d5.png|train/5/meter_02272026_s3_d5.png|train/5/meter_02272026_s3_d5.png,137,2,1555 +composed,5,train/5/synthetic_window_00137_s3_d5.png,train/1/meter_02272026_s2_d1.png|train/5/meter_02272026_s3_d5.png|train/5/meter_02272026_s3_d5.png|train/5/meter_02272026_s3_d5.png,137,3,1555 +composed,6,train/6/synthetic_window_00138_s0_d6.png,train/6/meter_03032026_s3_d6.png|train/0/meter_01122026_s2_d0.png|train/6/meter_03032026_s3_d6.png|train/6/meter_03032026_s3_d6.png,138,0,6066 +composed,0,train/0/synthetic_window_00138_s1_d0.png,train/6/meter_03032026_s3_d6.png|train/0/meter_01122026_s2_d0.png|train/6/meter_03032026_s3_d6.png|train/6/meter_03032026_s3_d6.png,138,1,6066 +composed,6,train/6/synthetic_window_00138_s2_d6.png,train/6/meter_03032026_s3_d6.png|train/0/meter_01122026_s2_d0.png|train/6/meter_03032026_s3_d6.png|train/6/meter_03032026_s3_d6.png,138,2,6066 +composed,6,train/6/synthetic_window_00138_s3_d6.png,train/6/meter_03032026_s3_d6.png|train/0/meter_01122026_s2_d0.png|train/6/meter_03032026_s3_d6.png|train/6/meter_03032026_s3_d6.png,138,3,6066 +composed,7,train/7/synthetic_window_00139_s0_d7.png,train/7/meter_10092025_s2_d7.png|train/5/meter_02272026_s3_d5.png|train/2/meter_10092025_s1_d2.png|train/2/meter_02162026_s0_d2.png,139,0,7522 +composed,5,train/5/synthetic_window_00139_s1_d5.png,train/7/meter_10092025_s2_d7.png|train/5/meter_02272026_s3_d5.png|train/2/meter_10092025_s1_d2.png|train/2/meter_02162026_s0_d2.png,139,1,7522 +composed,2,train/2/synthetic_window_00139_s2_d2.png,train/7/meter_10092025_s2_d7.png|train/5/meter_02272026_s3_d5.png|train/2/meter_10092025_s1_d2.png|train/2/meter_02162026_s0_d2.png,139,2,7522 +composed,2,train/2/synthetic_window_00139_s3_d2.png,train/7/meter_10092025_s2_d7.png|train/5/meter_02272026_s3_d5.png|train/2/meter_10092025_s1_d2.png|train/2/meter_02162026_s0_d2.png,139,3,7522 +composed,1,train/1/synthetic_window_00140_s0_d1.png,train/1/meter_02272026_s2_d1.png|train/0/meter_01122026_s2_d0.png|train/5/meter_02272026_s3_d5.png|train/7/meter_01302026_s3_d7.png,140,0,1057 +composed,0,train/0/synthetic_window_00140_s1_d0.png,train/1/meter_02272026_s2_d1.png|train/0/meter_01122026_s2_d0.png|train/5/meter_02272026_s3_d5.png|train/7/meter_01302026_s3_d7.png,140,1,1057 +composed,5,train/5/synthetic_window_00140_s2_d5.png,train/1/meter_02272026_s2_d1.png|train/0/meter_01122026_s2_d0.png|train/5/meter_02272026_s3_d5.png|train/7/meter_01302026_s3_d7.png,140,2,1057 +composed,7,train/7/synthetic_window_00140_s3_d7.png,train/1/meter_02272026_s2_d1.png|train/0/meter_01122026_s2_d0.png|train/5/meter_02272026_s3_d5.png|train/7/meter_01302026_s3_d7.png,140,3,1057 +composed,1,train/1/synthetic_window_00141_s0_d1.png,train/1/meter_03032026_s2_d1.png|train/0/meter_01302026_s2_d0.png|train/5/meter_02272026_s3_d5.png|train/8/meter_07012020_s2_d8.png,141,0,1058 +composed,0,train/0/synthetic_window_00141_s1_d0.png,train/1/meter_03032026_s2_d1.png|train/0/meter_01302026_s2_d0.png|train/5/meter_02272026_s3_d5.png|train/8/meter_07012020_s2_d8.png,141,1,1058 +composed,5,train/5/synthetic_window_00141_s2_d5.png,train/1/meter_03032026_s2_d1.png|train/0/meter_01302026_s2_d0.png|train/5/meter_02272026_s3_d5.png|train/8/meter_07012020_s2_d8.png,141,2,1058 +composed,8,train/8/synthetic_window_00141_s3_d8.png,train/1/meter_03032026_s2_d1.png|train/0/meter_01302026_s2_d0.png|train/5/meter_02272026_s3_d5.png|train/8/meter_07012020_s2_d8.png,141,3,1058 +composed,3,train/3/synthetic_window_00142_s0_d3.png,train/3/meter_02162026_s1_d3.png|train/0/meter_01122026_s2_d0.png|train/8/meter_11112020_s1_d8.png|train/7/meter_01302026_s3_d7.png,142,0,3087 +composed,0,train/0/synthetic_window_00142_s1_d0.png,train/3/meter_02162026_s1_d3.png|train/0/meter_01122026_s2_d0.png|train/8/meter_11112020_s1_d8.png|train/7/meter_01302026_s3_d7.png,142,1,3087 +composed,8,train/8/synthetic_window_00142_s2_d8.png,train/3/meter_02162026_s1_d3.png|train/0/meter_01122026_s2_d0.png|train/8/meter_11112020_s1_d8.png|train/7/meter_01302026_s3_d7.png,142,2,3087 +composed,7,train/7/synthetic_window_00142_s3_d7.png,train/3/meter_02162026_s1_d3.png|train/0/meter_01122026_s2_d0.png|train/8/meter_11112020_s1_d8.png|train/7/meter_01302026_s3_d7.png,142,3,3087 +composed,7,train/7/synthetic_window_00143_s0_d7.png,train/7/meter_07012020_s1_d7.png|train/6/meter_03032026_s3_d6.png|train/1/meter_02192026_s2_d1.png|train/6/meter_03032026_s3_d6.png,143,0,7616 +composed,6,train/6/synthetic_window_00143_s1_d6.png,train/7/meter_07012020_s1_d7.png|train/6/meter_03032026_s3_d6.png|train/1/meter_02192026_s2_d1.png|train/6/meter_03032026_s3_d6.png,143,1,7616 +composed,1,train/1/synthetic_window_00143_s2_d1.png,train/7/meter_07012020_s1_d7.png|train/6/meter_03032026_s3_d6.png|train/1/meter_02192026_s2_d1.png|train/6/meter_03032026_s3_d6.png,143,2,7616 +composed,6,train/6/synthetic_window_00143_s3_d6.png,train/7/meter_07012020_s1_d7.png|train/6/meter_03032026_s3_d6.png|train/1/meter_02192026_s2_d1.png|train/6/meter_03032026_s3_d6.png,143,3,7616 +composed,3,train/3/synthetic_window_00144_s0_d3.png,train/3/meter_03032026_s1_d3.png|train/5/meter_02272026_s3_d5.png|train/0/meter_01122026_s2_d0.png|train/4/meter_07012020_s3_d4.png,144,0,3504 +composed,5,train/5/synthetic_window_00144_s1_d5.png,train/3/meter_03032026_s1_d3.png|train/5/meter_02272026_s3_d5.png|train/0/meter_01122026_s2_d0.png|train/4/meter_07012020_s3_d4.png,144,1,3504 +composed,0,train/0/synthetic_window_00144_s2_d0.png,train/3/meter_03032026_s1_d3.png|train/5/meter_02272026_s3_d5.png|train/0/meter_01122026_s2_d0.png|train/4/meter_07012020_s3_d4.png,144,2,3504 +composed,4,train/4/synthetic_window_00144_s3_d4.png,train/3/meter_03032026_s1_d3.png|train/5/meter_02272026_s3_d5.png|train/0/meter_01122026_s2_d0.png|train/4/meter_07012020_s3_d4.png,144,3,3504 +composed,9,train/9/synthetic_window_00145_s0_d9.png,train/9/meter_10092025_s3_d9.png|train/1/meter_07012020_s0_d1.png|train/0/meter_01132026_s2_d0.png|train/7/meter_10092025_s2_d7.png,145,0,9107 +composed,1,train/1/synthetic_window_00145_s1_d1.png,train/9/meter_10092025_s3_d9.png|train/1/meter_07012020_s0_d1.png|train/0/meter_01132026_s2_d0.png|train/7/meter_10092025_s2_d7.png,145,1,9107 +composed,0,train/0/synthetic_window_00145_s2_d0.png,train/9/meter_10092025_s3_d9.png|train/1/meter_07012020_s0_d1.png|train/0/meter_01132026_s2_d0.png|train/7/meter_10092025_s2_d7.png,145,2,9107 +composed,7,train/7/synthetic_window_00145_s3_d7.png,train/9/meter_10092025_s3_d9.png|train/1/meter_07012020_s0_d1.png|train/0/meter_01132026_s2_d0.png|train/7/meter_10092025_s2_d7.png,145,3,9107 +composed,9,train/9/synthetic_window_00146_s0_d9.png,train/9/meter_10092025_s3_d9.png|train/5/meter_02272026_s3_d5.png|train/2/meter_02202026_s0_d2.png|train/1/meter_02162026_s2_d1.png,146,0,9521 +composed,5,train/5/synthetic_window_00146_s1_d5.png,train/9/meter_10092025_s3_d9.png|train/5/meter_02272026_s3_d5.png|train/2/meter_02202026_s0_d2.png|train/1/meter_02162026_s2_d1.png,146,1,9521 +composed,2,train/2/synthetic_window_00146_s2_d2.png,train/9/meter_10092025_s3_d9.png|train/5/meter_02272026_s3_d5.png|train/2/meter_02202026_s0_d2.png|train/1/meter_02162026_s2_d1.png,146,2,9521 +composed,1,train/1/synthetic_window_00146_s3_d1.png,train/9/meter_10092025_s3_d9.png|train/5/meter_02272026_s3_d5.png|train/2/meter_02202026_s0_d2.png|train/1/meter_02162026_s2_d1.png,146,3,9521 +composed,1,train/1/synthetic_window_00147_s0_d1.png,train/1/meter_07012020_s0_d1.png|train/0/meter_01132026_s2_d0.png|train/3/meter_02272026_s1_d3.png|train/9/meter_10092025_s3_d9.png,147,0,1039 +composed,0,train/0/synthetic_window_00147_s1_d0.png,train/1/meter_07012020_s0_d1.png|train/0/meter_01132026_s2_d0.png|train/3/meter_02272026_s1_d3.png|train/9/meter_10092025_s3_d9.png,147,1,1039 +composed,3,train/3/synthetic_window_00147_s2_d3.png,train/1/meter_07012020_s0_d1.png|train/0/meter_01132026_s2_d0.png|train/3/meter_02272026_s1_d3.png|train/9/meter_10092025_s3_d9.png,147,2,1039 +composed,9,train/9/synthetic_window_00147_s3_d9.png,train/1/meter_07012020_s0_d1.png|train/0/meter_01132026_s2_d0.png|train/3/meter_02272026_s1_d3.png|train/9/meter_10092025_s3_d9.png,147,3,1039 +composed,6,train/6/synthetic_window_00148_s0_d6.png,train/6/meter_03032026_s3_d6.png|train/8/meter_07012020_s2_d8.png|train/6/meter_03032026_s3_d6.png|train/4/meter_07012020_s3_d4.png,148,0,6864 +composed,8,train/8/synthetic_window_00148_s1_d8.png,train/6/meter_03032026_s3_d6.png|train/8/meter_07012020_s2_d8.png|train/6/meter_03032026_s3_d6.png|train/4/meter_07012020_s3_d4.png,148,1,6864 +composed,6,train/6/synthetic_window_00148_s2_d6.png,train/6/meter_03032026_s3_d6.png|train/8/meter_07012020_s2_d8.png|train/6/meter_03032026_s3_d6.png|train/4/meter_07012020_s3_d4.png,148,2,6864 +composed,4,train/4/synthetic_window_00148_s3_d4.png,train/6/meter_03032026_s3_d6.png|train/8/meter_07012020_s2_d8.png|train/6/meter_03032026_s3_d6.png|train/4/meter_07012020_s3_d4.png,148,3,6864 +composed,6,train/6/synthetic_window_00149_s0_d6.png,train/6/meter_03032026_s3_d6.png|train/0/meter_01122026_s2_d0.png|train/3/meter_02162026_s1_d3.png|train/0/meter_01302026_s2_d0.png,149,0,6030 +composed,0,train/0/synthetic_window_00149_s1_d0.png,train/6/meter_03032026_s3_d6.png|train/0/meter_01122026_s2_d0.png|train/3/meter_02162026_s1_d3.png|train/0/meter_01302026_s2_d0.png,149,1,6030 +composed,3,train/3/synthetic_window_00149_s2_d3.png,train/6/meter_03032026_s3_d6.png|train/0/meter_01122026_s2_d0.png|train/3/meter_02162026_s1_d3.png|train/0/meter_01302026_s2_d0.png,149,2,6030 +composed,0,train/0/synthetic_window_00149_s3_d0.png,train/6/meter_03032026_s3_d6.png|train/0/meter_01122026_s2_d0.png|train/3/meter_02162026_s1_d3.png|train/0/meter_01302026_s2_d0.png,149,3,6030 +composed,5,train/5/synthetic_window_00150_s0_d5.png,train/5/meter_02272026_s3_d5.png|train/9/meter_10092025_s3_d9.png|train/6/meter_03032026_s3_d6.png|train/0/meter_01122026_s2_d0.png,150,0,5960 +composed,9,train/9/synthetic_window_00150_s1_d9.png,train/5/meter_02272026_s3_d5.png|train/9/meter_10092025_s3_d9.png|train/6/meter_03032026_s3_d6.png|train/0/meter_01122026_s2_d0.png,150,1,5960 +composed,6,train/6/synthetic_window_00150_s2_d6.png,train/5/meter_02272026_s3_d5.png|train/9/meter_10092025_s3_d9.png|train/6/meter_03032026_s3_d6.png|train/0/meter_01122026_s2_d0.png,150,2,5960 +composed,0,train/0/synthetic_window_00150_s3_d0.png,train/5/meter_02272026_s3_d5.png|train/9/meter_10092025_s3_d9.png|train/6/meter_03032026_s3_d6.png|train/0/meter_01122026_s2_d0.png,150,3,5960 +composed,1,train/1/synthetic_window_00151_s0_d1.png,train/1/meter_07012020_s0_d1.png|train/9/meter_11112020_s3_d9.png|train/8/meter_11112020_s1_d8.png|train/7/meter_07012020_s1_d7.png,151,0,1987 +composed,9,train/9/synthetic_window_00151_s1_d9.png,train/1/meter_07012020_s0_d1.png|train/9/meter_11112020_s3_d9.png|train/8/meter_11112020_s1_d8.png|train/7/meter_07012020_s1_d7.png,151,1,1987 +composed,8,train/8/synthetic_window_00151_s2_d8.png,train/1/meter_07012020_s0_d1.png|train/9/meter_11112020_s3_d9.png|train/8/meter_11112020_s1_d8.png|train/7/meter_07012020_s1_d7.png,151,2,1987 +composed,7,train/7/synthetic_window_00151_s3_d7.png,train/1/meter_07012020_s0_d1.png|train/9/meter_11112020_s3_d9.png|train/8/meter_11112020_s1_d8.png|train/7/meter_07012020_s1_d7.png,151,3,1987 +composed,6,train/6/synthetic_window_00152_s0_d6.png,train/6/meter_03032026_s3_d6.png|train/2/meter_01302026_s0_d2.png|train/3/meter_02202026_s1_d3.png|train/6/meter_03032026_s3_d6.png,152,0,6236 +composed,2,train/2/synthetic_window_00152_s1_d2.png,train/6/meter_03032026_s3_d6.png|train/2/meter_01302026_s0_d2.png|train/3/meter_02202026_s1_d3.png|train/6/meter_03032026_s3_d6.png,152,1,6236 +composed,3,train/3/synthetic_window_00152_s2_d3.png,train/6/meter_03032026_s3_d6.png|train/2/meter_01302026_s0_d2.png|train/3/meter_02202026_s1_d3.png|train/6/meter_03032026_s3_d6.png,152,2,6236 +composed,6,train/6/synthetic_window_00152_s3_d6.png,train/6/meter_03032026_s3_d6.png|train/2/meter_01302026_s0_d2.png|train/3/meter_02202026_s1_d3.png|train/6/meter_03032026_s3_d6.png,152,3,6236 +composed,7,train/7/synthetic_window_00153_s0_d7.png,train/7/meter_01302026_s3_d7.png|train/4/meter_02242026_s3_d4.png|train/2/meter_01132026_s3_d2.png|train/8/meter_11112020_s1_d8.png,153,0,7428 +composed,4,train/4/synthetic_window_00153_s1_d4.png,train/7/meter_01302026_s3_d7.png|train/4/meter_02242026_s3_d4.png|train/2/meter_01132026_s3_d2.png|train/8/meter_11112020_s1_d8.png,153,1,7428 +composed,2,train/2/synthetic_window_00153_s2_d2.png,train/7/meter_01302026_s3_d7.png|train/4/meter_02242026_s3_d4.png|train/2/meter_01132026_s3_d2.png|train/8/meter_11112020_s1_d8.png,153,2,7428 +composed,8,train/8/synthetic_window_00153_s3_d8.png,train/7/meter_01302026_s3_d7.png|train/4/meter_02242026_s3_d4.png|train/2/meter_01132026_s3_d2.png|train/8/meter_11112020_s1_d8.png,153,3,7428 +composed,5,train/5/synthetic_window_00154_s0_d5.png,train/5/meter_02272026_s3_d5.png|train/0/meter_01122026_s2_d0.png|train/2/meter_02272026_s0_d2.png|train/9/meter_10092025_s3_d9.png,154,0,5029 +composed,0,train/0/synthetic_window_00154_s1_d0.png,train/5/meter_02272026_s3_d5.png|train/0/meter_01122026_s2_d0.png|train/2/meter_02272026_s0_d2.png|train/9/meter_10092025_s3_d9.png,154,1,5029 +composed,2,train/2/synthetic_window_00154_s2_d2.png,train/5/meter_02272026_s3_d5.png|train/0/meter_01122026_s2_d0.png|train/2/meter_02272026_s0_d2.png|train/9/meter_10092025_s3_d9.png,154,2,5029 +composed,9,train/9/synthetic_window_00154_s3_d9.png,train/5/meter_02272026_s3_d5.png|train/0/meter_01122026_s2_d0.png|train/2/meter_02272026_s0_d2.png|train/9/meter_10092025_s3_d9.png,154,3,5029 +composed,6,train/6/synthetic_window_00155_s0_d6.png,train/6/meter_03032026_s3_d6.png|train/5/meter_02272026_s3_d5.png|train/7/meter_01302026_s3_d7.png|train/8/meter_07012020_s2_d8.png,155,0,6578 +composed,5,train/5/synthetic_window_00155_s1_d5.png,train/6/meter_03032026_s3_d6.png|train/5/meter_02272026_s3_d5.png|train/7/meter_01302026_s3_d7.png|train/8/meter_07012020_s2_d8.png,155,1,6578 +composed,7,train/7/synthetic_window_00155_s2_d7.png,train/6/meter_03032026_s3_d6.png|train/5/meter_02272026_s3_d5.png|train/7/meter_01302026_s3_d7.png|train/8/meter_07012020_s2_d8.png,155,2,6578 +composed,8,train/8/synthetic_window_00155_s3_d8.png,train/6/meter_03032026_s3_d6.png|train/5/meter_02272026_s3_d5.png|train/7/meter_01302026_s3_d7.png|train/8/meter_07012020_s2_d8.png,155,3,6578 +composed,7,train/7/synthetic_window_00156_s0_d7.png,train/7/meter_07012020_s1_d7.png|train/7/meter_01302026_s3_d7.png|train/6/meter_03032026_s3_d6.png|train/8/meter_11112020_s1_d8.png,156,0,7768 +composed,7,train/7/synthetic_window_00156_s1_d7.png,train/7/meter_07012020_s1_d7.png|train/7/meter_01302026_s3_d7.png|train/6/meter_03032026_s3_d6.png|train/8/meter_11112020_s1_d8.png,156,1,7768 +composed,6,train/6/synthetic_window_00156_s2_d6.png,train/7/meter_07012020_s1_d7.png|train/7/meter_01302026_s3_d7.png|train/6/meter_03032026_s3_d6.png|train/8/meter_11112020_s1_d8.png,156,2,7768 +composed,8,train/8/synthetic_window_00156_s3_d8.png,train/7/meter_07012020_s1_d7.png|train/7/meter_01302026_s3_d7.png|train/6/meter_03032026_s3_d6.png|train/8/meter_11112020_s1_d8.png,156,3,7768 +composed,8,train/8/synthetic_window_00157_s0_d8.png,train/8/meter_11112020_s1_d8.png|train/9/meter_10092025_s3_d9.png|train/8/meter_11112020_s1_d8.png|train/8/meter_07012020_s2_d8.png,157,0,8988 +composed,9,train/9/synthetic_window_00157_s1_d9.png,train/8/meter_11112020_s1_d8.png|train/9/meter_10092025_s3_d9.png|train/8/meter_11112020_s1_d8.png|train/8/meter_07012020_s2_d8.png,157,1,8988 +composed,8,train/8/synthetic_window_00157_s2_d8.png,train/8/meter_11112020_s1_d8.png|train/9/meter_10092025_s3_d9.png|train/8/meter_11112020_s1_d8.png|train/8/meter_07012020_s2_d8.png,157,2,8988 +composed,8,train/8/synthetic_window_00157_s3_d8.png,train/8/meter_11112020_s1_d8.png|train/9/meter_10092025_s3_d9.png|train/8/meter_11112020_s1_d8.png|train/8/meter_07012020_s2_d8.png,157,3,8988 +composed,4,train/4/synthetic_window_00158_s0_d4.png,train/4/meter_07012020_s3_d4.png|train/3/meter_02202026_s3_d3.png|train/8/meter_07012020_s2_d8.png|train/1/meter_02162026_s2_d1.png,158,0,4381 +composed,3,train/3/synthetic_window_00158_s1_d3.png,train/4/meter_07012020_s3_d4.png|train/3/meter_02202026_s3_d3.png|train/8/meter_07012020_s2_d8.png|train/1/meter_02162026_s2_d1.png,158,1,4381 +composed,8,train/8/synthetic_window_00158_s2_d8.png,train/4/meter_07012020_s3_d4.png|train/3/meter_02202026_s3_d3.png|train/8/meter_07012020_s2_d8.png|train/1/meter_02162026_s2_d1.png,158,2,4381 +composed,1,train/1/synthetic_window_00158_s3_d1.png,train/4/meter_07012020_s3_d4.png|train/3/meter_02202026_s3_d3.png|train/8/meter_07012020_s2_d8.png|train/1/meter_02162026_s2_d1.png,158,3,4381 +composed,1,train/1/synthetic_window_00159_s0_d1.png,train/1/meter_02272026_s2_d1.png|train/5/meter_02272026_s3_d5.png|train/4/meter_02242026_s3_d4.png|train/9/meter_11112020_s3_d9.png,159,0,1549 +composed,5,train/5/synthetic_window_00159_s1_d5.png,train/1/meter_02272026_s2_d1.png|train/5/meter_02272026_s3_d5.png|train/4/meter_02242026_s3_d4.png|train/9/meter_11112020_s3_d9.png,159,1,1549 +composed,4,train/4/synthetic_window_00159_s2_d4.png,train/1/meter_02272026_s2_d1.png|train/5/meter_02272026_s3_d5.png|train/4/meter_02242026_s3_d4.png|train/9/meter_11112020_s3_d9.png,159,2,1549 +composed,9,train/9/synthetic_window_00159_s3_d9.png,train/1/meter_02272026_s2_d1.png|train/5/meter_02272026_s3_d5.png|train/4/meter_02242026_s3_d4.png|train/9/meter_11112020_s3_d9.png,159,3,1549 +composed,2,train/2/synthetic_window_00160_s0_d2.png,train/2/meter_02162026_s0_d2.png|train/7/meter_01302026_s3_d7.png|train/6/meter_03032026_s3_d6.png|train/5/meter_02272026_s3_d5.png,160,0,2765 +composed,7,train/7/synthetic_window_00160_s1_d7.png,train/2/meter_02162026_s0_d2.png|train/7/meter_01302026_s3_d7.png|train/6/meter_03032026_s3_d6.png|train/5/meter_02272026_s3_d5.png,160,1,2765 +composed,6,train/6/synthetic_window_00160_s2_d6.png,train/2/meter_02162026_s0_d2.png|train/7/meter_01302026_s3_d7.png|train/6/meter_03032026_s3_d6.png|train/5/meter_02272026_s3_d5.png,160,2,2765 +composed,5,train/5/synthetic_window_00160_s3_d5.png,train/2/meter_02162026_s0_d2.png|train/7/meter_01302026_s3_d7.png|train/6/meter_03032026_s3_d6.png|train/5/meter_02272026_s3_d5.png,160,3,2765 +composed,5,train/5/synthetic_window_00161_s0_d5.png,train/5/meter_02272026_s3_d5.png|train/9/meter_11112020_s3_d9.png|train/3/meter_02192026_s1_d3.png|train/6/meter_03032026_s3_d6.png,161,0,5936 +composed,9,train/9/synthetic_window_00161_s1_d9.png,train/5/meter_02272026_s3_d5.png|train/9/meter_11112020_s3_d9.png|train/3/meter_02192026_s1_d3.png|train/6/meter_03032026_s3_d6.png,161,1,5936 +composed,3,train/3/synthetic_window_00161_s2_d3.png,train/5/meter_02272026_s3_d5.png|train/9/meter_11112020_s3_d9.png|train/3/meter_02192026_s1_d3.png|train/6/meter_03032026_s3_d6.png,161,2,5936 +composed,6,train/6/synthetic_window_00161_s3_d6.png,train/5/meter_02272026_s3_d5.png|train/9/meter_11112020_s3_d9.png|train/3/meter_02192026_s1_d3.png|train/6/meter_03032026_s3_d6.png,161,3,5936 +composed,6,train/6/synthetic_window_00162_s0_d6.png,train/6/meter_03032026_s3_d6.png|train/9/meter_10092025_s3_d9.png|train/2/meter_02162026_s3_d2.png|train/5/meter_02272026_s3_d5.png,162,0,6925 +composed,9,train/9/synthetic_window_00162_s1_d9.png,train/6/meter_03032026_s3_d6.png|train/9/meter_10092025_s3_d9.png|train/2/meter_02162026_s3_d2.png|train/5/meter_02272026_s3_d5.png,162,1,6925 +composed,2,train/2/synthetic_window_00162_s2_d2.png,train/6/meter_03032026_s3_d6.png|train/9/meter_10092025_s3_d9.png|train/2/meter_02162026_s3_d2.png|train/5/meter_02272026_s3_d5.png,162,2,6925 +composed,5,train/5/synthetic_window_00162_s3_d5.png,train/6/meter_03032026_s3_d6.png|train/9/meter_10092025_s3_d9.png|train/2/meter_02162026_s3_d2.png|train/5/meter_02272026_s3_d5.png,162,3,6925 +composed,8,train/8/synthetic_window_00163_s0_d8.png,train/8/meter_11112020_s1_d8.png|train/4/meter_07012020_s3_d4.png|train/7/meter_10092025_s2_d7.png|train/3/meter_02202026_s1_d3.png,163,0,8473 +composed,4,train/4/synthetic_window_00163_s1_d4.png,train/8/meter_11112020_s1_d8.png|train/4/meter_07012020_s3_d4.png|train/7/meter_10092025_s2_d7.png|train/3/meter_02202026_s1_d3.png,163,1,8473 +composed,7,train/7/synthetic_window_00163_s2_d7.png,train/8/meter_11112020_s1_d8.png|train/4/meter_07012020_s3_d4.png|train/7/meter_10092025_s2_d7.png|train/3/meter_02202026_s1_d3.png,163,2,8473 +composed,3,train/3/synthetic_window_00163_s3_d3.png,train/8/meter_11112020_s1_d8.png|train/4/meter_07012020_s3_d4.png|train/7/meter_10092025_s2_d7.png|train/3/meter_02202026_s1_d3.png,163,3,8473 +composed,3,train/3/synthetic_window_00164_s0_d3.png,train/3/meter_02202026_s3_d3.png|train/6/meter_03032026_s3_d6.png|train/2/meter_02272026_s0_d2.png|train/0/meter_01302026_s2_d0.png,164,0,3620 +composed,6,train/6/synthetic_window_00164_s1_d6.png,train/3/meter_02202026_s3_d3.png|train/6/meter_03032026_s3_d6.png|train/2/meter_02272026_s0_d2.png|train/0/meter_01302026_s2_d0.png,164,1,3620 +composed,2,train/2/synthetic_window_00164_s2_d2.png,train/3/meter_02202026_s3_d3.png|train/6/meter_03032026_s3_d6.png|train/2/meter_02272026_s0_d2.png|train/0/meter_01302026_s2_d0.png,164,2,3620 +composed,0,train/0/synthetic_window_00164_s3_d0.png,train/3/meter_02202026_s3_d3.png|train/6/meter_03032026_s3_d6.png|train/2/meter_02272026_s0_d2.png|train/0/meter_01302026_s2_d0.png,164,3,3620 +composed,4,train/4/synthetic_window_00165_s0_d4.png,train/4/meter_07012020_s3_d4.png|train/2/meter_01122026_s3_d2.png|train/2/meter_10092025_s1_d2.png|train/3/meter_01302026_s1_d3.png,165,0,4223 +composed,2,train/2/synthetic_window_00165_s1_d2.png,train/4/meter_07012020_s3_d4.png|train/2/meter_01122026_s3_d2.png|train/2/meter_10092025_s1_d2.png|train/3/meter_01302026_s1_d3.png,165,1,4223 +composed,2,train/2/synthetic_window_00165_s2_d2.png,train/4/meter_07012020_s3_d4.png|train/2/meter_01122026_s3_d2.png|train/2/meter_10092025_s1_d2.png|train/3/meter_01302026_s1_d3.png,165,2,4223 +composed,3,train/3/synthetic_window_00165_s3_d3.png,train/4/meter_07012020_s3_d4.png|train/2/meter_01122026_s3_d2.png|train/2/meter_10092025_s1_d2.png|train/3/meter_01302026_s1_d3.png,165,3,4223 +composed,3,train/3/synthetic_window_00166_s0_d3.png,train/3/meter_02192026_s1_d3.png|train/2/meter_02242026_s0_d2.png|train/9/meter_11112020_s3_d9.png|train/8/meter_11112020_s1_d8.png,166,0,3298 +composed,2,train/2/synthetic_window_00166_s1_d2.png,train/3/meter_02192026_s1_d3.png|train/2/meter_02242026_s0_d2.png|train/9/meter_11112020_s3_d9.png|train/8/meter_11112020_s1_d8.png,166,1,3298 +composed,9,train/9/synthetic_window_00166_s2_d9.png,train/3/meter_02192026_s1_d3.png|train/2/meter_02242026_s0_d2.png|train/9/meter_11112020_s3_d9.png|train/8/meter_11112020_s1_d8.png,166,2,3298 +composed,8,train/8/synthetic_window_00166_s3_d8.png,train/3/meter_02192026_s1_d3.png|train/2/meter_02242026_s0_d2.png|train/9/meter_11112020_s3_d9.png|train/8/meter_11112020_s1_d8.png,166,3,3298 +composed,6,train/6/synthetic_window_00167_s0_d6.png,train/6/meter_03032026_s3_d6.png|train/2/meter_02242026_s0_d2.png|train/6/meter_03032026_s3_d6.png|train/8/meter_07012020_s2_d8.png,167,0,6268 +composed,2,train/2/synthetic_window_00167_s1_d2.png,train/6/meter_03032026_s3_d6.png|train/2/meter_02242026_s0_d2.png|train/6/meter_03032026_s3_d6.png|train/8/meter_07012020_s2_d8.png,167,1,6268 +composed,6,train/6/synthetic_window_00167_s2_d6.png,train/6/meter_03032026_s3_d6.png|train/2/meter_02242026_s0_d2.png|train/6/meter_03032026_s3_d6.png|train/8/meter_07012020_s2_d8.png,167,2,6268 +composed,8,train/8/synthetic_window_00167_s3_d8.png,train/6/meter_03032026_s3_d6.png|train/2/meter_02242026_s0_d2.png|train/6/meter_03032026_s3_d6.png|train/8/meter_07012020_s2_d8.png,167,3,6268 +composed,2,train/2/synthetic_window_00168_s0_d2.png,train/2/meter_02202026_s0_d2.png|train/8/meter_11112020_s1_d8.png|train/7/meter_07012020_s1_d7.png|train/7/meter_10092025_s2_d7.png,168,0,2877 +composed,8,train/8/synthetic_window_00168_s1_d8.png,train/2/meter_02202026_s0_d2.png|train/8/meter_11112020_s1_d8.png|train/7/meter_07012020_s1_d7.png|train/7/meter_10092025_s2_d7.png,168,1,2877 +composed,7,train/7/synthetic_window_00168_s2_d7.png,train/2/meter_02202026_s0_d2.png|train/8/meter_11112020_s1_d8.png|train/7/meter_07012020_s1_d7.png|train/7/meter_10092025_s2_d7.png,168,2,2877 +composed,7,train/7/synthetic_window_00168_s3_d7.png,train/2/meter_02202026_s0_d2.png|train/8/meter_11112020_s1_d8.png|train/7/meter_07012020_s1_d7.png|train/7/meter_10092025_s2_d7.png,168,3,2877 +composed,2,train/2/synthetic_window_00169_s0_d2.png,train/2/meter_03032026_s0_d2.png|train/8/meter_07012020_s2_d8.png|train/7/meter_07012020_s1_d7.png|train/5/meter_02272026_s3_d5.png,169,0,2875 +composed,8,train/8/synthetic_window_00169_s1_d8.png,train/2/meter_03032026_s0_d2.png|train/8/meter_07012020_s2_d8.png|train/7/meter_07012020_s1_d7.png|train/5/meter_02272026_s3_d5.png,169,1,2875 +composed,7,train/7/synthetic_window_00169_s2_d7.png,train/2/meter_03032026_s0_d2.png|train/8/meter_07012020_s2_d8.png|train/7/meter_07012020_s1_d7.png|train/5/meter_02272026_s3_d5.png,169,2,2875 +composed,5,train/5/synthetic_window_00169_s3_d5.png,train/2/meter_03032026_s0_d2.png|train/8/meter_07012020_s2_d8.png|train/7/meter_07012020_s1_d7.png|train/5/meter_02272026_s3_d5.png,169,3,2875 +composed,2,train/2/synthetic_window_00170_s0_d2.png,train/2/meter_01132026_s3_d2.png|train/0/meter_01122026_s2_d0.png|train/8/meter_11112020_s1_d8.png|train/9/meter_11112020_s3_d9.png,170,0,2089 +composed,0,train/0/synthetic_window_00170_s1_d0.png,train/2/meter_01132026_s3_d2.png|train/0/meter_01122026_s2_d0.png|train/8/meter_11112020_s1_d8.png|train/9/meter_11112020_s3_d9.png,170,1,2089 +composed,8,train/8/synthetic_window_00170_s2_d8.png,train/2/meter_01132026_s3_d2.png|train/0/meter_01122026_s2_d0.png|train/8/meter_11112020_s1_d8.png|train/9/meter_11112020_s3_d9.png,170,2,2089 +composed,9,train/9/synthetic_window_00170_s3_d9.png,train/2/meter_01132026_s3_d2.png|train/0/meter_01122026_s2_d0.png|train/8/meter_11112020_s1_d8.png|train/9/meter_11112020_s3_d9.png,170,3,2089 +composed,9,train/9/synthetic_window_00171_s0_d9.png,train/9/meter_11112020_s3_d9.png|train/8/meter_11112020_s1_d8.png|train/7/meter_01302026_s3_d7.png|train/2/meter_01132026_s3_d2.png,171,0,9872 +composed,8,train/8/synthetic_window_00171_s1_d8.png,train/9/meter_11112020_s3_d9.png|train/8/meter_11112020_s1_d8.png|train/7/meter_01302026_s3_d7.png|train/2/meter_01132026_s3_d2.png,171,1,9872 +composed,7,train/7/synthetic_window_00171_s2_d7.png,train/9/meter_11112020_s3_d9.png|train/8/meter_11112020_s1_d8.png|train/7/meter_01302026_s3_d7.png|train/2/meter_01132026_s3_d2.png,171,2,9872 +composed,2,train/2/synthetic_window_00171_s3_d2.png,train/9/meter_11112020_s3_d9.png|train/8/meter_11112020_s1_d8.png|train/7/meter_01302026_s3_d7.png|train/2/meter_01132026_s3_d2.png,171,3,9872 +composed,4,train/4/synthetic_window_00172_s0_d4.png,train/4/meter_02242026_s3_d4.png|train/9/meter_11112020_s3_d9.png|train/2/meter_02242026_s0_d2.png|train/1/meter_02242026_s2_d1.png,172,0,4921 +composed,9,train/9/synthetic_window_00172_s1_d9.png,train/4/meter_02242026_s3_d4.png|train/9/meter_11112020_s3_d9.png|train/2/meter_02242026_s0_d2.png|train/1/meter_02242026_s2_d1.png,172,1,4921 +composed,2,train/2/synthetic_window_00172_s2_d2.png,train/4/meter_02242026_s3_d4.png|train/9/meter_11112020_s3_d9.png|train/2/meter_02242026_s0_d2.png|train/1/meter_02242026_s2_d1.png,172,2,4921 +composed,1,train/1/synthetic_window_00172_s3_d1.png,train/4/meter_02242026_s3_d4.png|train/9/meter_11112020_s3_d9.png|train/2/meter_02242026_s0_d2.png|train/1/meter_02242026_s2_d1.png,172,3,4921 +composed,2,train/2/synthetic_window_00173_s0_d2.png,train/2/meter_01132026_s0_d2.png|train/5/meter_02272026_s3_d5.png|train/3/meter_02242026_s1_d3.png|train/9/meter_11112020_s3_d9.png,173,0,2539 +composed,5,train/5/synthetic_window_00173_s1_d5.png,train/2/meter_01132026_s0_d2.png|train/5/meter_02272026_s3_d5.png|train/3/meter_02242026_s1_d3.png|train/9/meter_11112020_s3_d9.png,173,1,2539 +composed,3,train/3/synthetic_window_00173_s2_d3.png,train/2/meter_01132026_s0_d2.png|train/5/meter_02272026_s3_d5.png|train/3/meter_02242026_s1_d3.png|train/9/meter_11112020_s3_d9.png,173,2,2539 +composed,9,train/9/synthetic_window_00173_s3_d9.png,train/2/meter_01132026_s0_d2.png|train/5/meter_02272026_s3_d5.png|train/3/meter_02242026_s1_d3.png|train/9/meter_11112020_s3_d9.png,173,3,2539 +composed,9,train/9/synthetic_window_00174_s0_d9.png,train/9/meter_11112020_s3_d9.png|train/6/meter_03032026_s3_d6.png|train/6/meter_03032026_s3_d6.png|train/1/meter_07012020_s0_d1.png,174,0,9661 +composed,6,train/6/synthetic_window_00174_s1_d6.png,train/9/meter_11112020_s3_d9.png|train/6/meter_03032026_s3_d6.png|train/6/meter_03032026_s3_d6.png|train/1/meter_07012020_s0_d1.png,174,1,9661 +composed,6,train/6/synthetic_window_00174_s2_d6.png,train/9/meter_11112020_s3_d9.png|train/6/meter_03032026_s3_d6.png|train/6/meter_03032026_s3_d6.png|train/1/meter_07012020_s0_d1.png,174,2,9661 +composed,1,train/1/synthetic_window_00174_s3_d1.png,train/9/meter_11112020_s3_d9.png|train/6/meter_03032026_s3_d6.png|train/6/meter_03032026_s3_d6.png|train/1/meter_07012020_s0_d1.png,174,3,9661 +composed,2,train/2/synthetic_window_00175_s0_d2.png,train/2/meter_10092025_s0_d2.png|train/5/meter_02272026_s3_d5.png|train/5/meter_02272026_s3_d5.png|train/2/meter_01132026_s3_d2.png,175,0,2552 +composed,5,train/5/synthetic_window_00175_s1_d5.png,train/2/meter_10092025_s0_d2.png|train/5/meter_02272026_s3_d5.png|train/5/meter_02272026_s3_d5.png|train/2/meter_01132026_s3_d2.png,175,1,2552 +composed,5,train/5/synthetic_window_00175_s2_d5.png,train/2/meter_10092025_s0_d2.png|train/5/meter_02272026_s3_d5.png|train/5/meter_02272026_s3_d5.png|train/2/meter_01132026_s3_d2.png,175,2,2552 +composed,2,train/2/synthetic_window_00175_s3_d2.png,train/2/meter_10092025_s0_d2.png|train/5/meter_02272026_s3_d5.png|train/5/meter_02272026_s3_d5.png|train/2/meter_01132026_s3_d2.png,175,3,2552 +composed,6,train/6/synthetic_window_00176_s0_d6.png,train/6/meter_03032026_s3_d6.png|train/6/meter_03032026_s3_d6.png|train/5/meter_02272026_s3_d5.png|train/3/meter_02162026_s1_d3.png,176,0,6653 +composed,6,train/6/synthetic_window_00176_s1_d6.png,train/6/meter_03032026_s3_d6.png|train/6/meter_03032026_s3_d6.png|train/5/meter_02272026_s3_d5.png|train/3/meter_02162026_s1_d3.png,176,1,6653 +composed,5,train/5/synthetic_window_00176_s2_d5.png,train/6/meter_03032026_s3_d6.png|train/6/meter_03032026_s3_d6.png|train/5/meter_02272026_s3_d5.png|train/3/meter_02162026_s1_d3.png,176,2,6653 +composed,3,train/3/synthetic_window_00176_s3_d3.png,train/6/meter_03032026_s3_d6.png|train/6/meter_03032026_s3_d6.png|train/5/meter_02272026_s3_d5.png|train/3/meter_02162026_s1_d3.png,176,3,6653 +composed,9,train/9/synthetic_window_00177_s0_d9.png,train/9/meter_11112020_s3_d9.png|train/0/meter_01132026_s2_d0.png|train/0/meter_01132026_s2_d0.png|train/3/meter_02192026_s1_d3.png,177,0,9003 +composed,0,train/0/synthetic_window_00177_s1_d0.png,train/9/meter_11112020_s3_d9.png|train/0/meter_01132026_s2_d0.png|train/0/meter_01132026_s2_d0.png|train/3/meter_02192026_s1_d3.png,177,1,9003 +composed,0,train/0/synthetic_window_00177_s2_d0.png,train/9/meter_11112020_s3_d9.png|train/0/meter_01132026_s2_d0.png|train/0/meter_01132026_s2_d0.png|train/3/meter_02192026_s1_d3.png,177,2,9003 +composed,3,train/3/synthetic_window_00177_s3_d3.png,train/9/meter_11112020_s3_d9.png|train/0/meter_01132026_s2_d0.png|train/0/meter_01132026_s2_d0.png|train/3/meter_02192026_s1_d3.png,177,3,9003 +composed,3,train/3/synthetic_window_00178_s0_d3.png,train/3/meter_02192026_s1_d3.png|train/4/meter_07012020_s3_d4.png|train/6/meter_03032026_s3_d6.png|train/8/meter_11112020_s1_d8.png,178,0,3468 +composed,4,train/4/synthetic_window_00178_s1_d4.png,train/3/meter_02192026_s1_d3.png|train/4/meter_07012020_s3_d4.png|train/6/meter_03032026_s3_d6.png|train/8/meter_11112020_s1_d8.png,178,1,3468 +composed,6,train/6/synthetic_window_00178_s2_d6.png,train/3/meter_02192026_s1_d3.png|train/4/meter_07012020_s3_d4.png|train/6/meter_03032026_s3_d6.png|train/8/meter_11112020_s1_d8.png,178,2,3468 +composed,8,train/8/synthetic_window_00178_s3_d8.png,train/3/meter_02192026_s1_d3.png|train/4/meter_07012020_s3_d4.png|train/6/meter_03032026_s3_d6.png|train/8/meter_11112020_s1_d8.png,178,3,3468 +composed,7,train/7/synthetic_window_00179_s0_d7.png,train/7/meter_10092025_s2_d7.png|train/9/meter_10092025_s3_d9.png|train/2/meter_02192026_s0_d2.png|train/1/meter_02242026_s2_d1.png,179,0,7921 +composed,9,train/9/synthetic_window_00179_s1_d9.png,train/7/meter_10092025_s2_d7.png|train/9/meter_10092025_s3_d9.png|train/2/meter_02192026_s0_d2.png|train/1/meter_02242026_s2_d1.png,179,1,7921 +composed,2,train/2/synthetic_window_00179_s2_d2.png,train/7/meter_10092025_s2_d7.png|train/9/meter_10092025_s3_d9.png|train/2/meter_02192026_s0_d2.png|train/1/meter_02242026_s2_d1.png,179,2,7921 +composed,1,train/1/synthetic_window_00179_s3_d1.png,train/7/meter_10092025_s2_d7.png|train/9/meter_10092025_s3_d9.png|train/2/meter_02192026_s0_d2.png|train/1/meter_02242026_s2_d1.png,179,3,7921 diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/meter_01122026_s2_d0__direct_r00.png b/backend/data/digit_dataset/sections_synthetic/train/0/meter_01122026_s2_d0__direct_r00.png new file mode 100644 index 0000000..a577808 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/meter_01122026_s2_d0__direct_r00.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/meter_01122026_s2_d0__direct_r01.png b/backend/data/digit_dataset/sections_synthetic/train/0/meter_01122026_s2_d0__direct_r01.png new file mode 100644 index 0000000..699f280 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/meter_01122026_s2_d0__direct_r01.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/meter_01122026_s2_d0__direct_r02.png b/backend/data/digit_dataset/sections_synthetic/train/0/meter_01122026_s2_d0__direct_r02.png new file mode 100644 index 0000000..8daa99c Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/meter_01122026_s2_d0__direct_r02.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/meter_01122026_s2_d0__direct_r03.png b/backend/data/digit_dataset/sections_synthetic/train/0/meter_01122026_s2_d0__direct_r03.png new file mode 100644 index 0000000..b80cae5 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/meter_01122026_s2_d0__direct_r03.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/meter_01122026_s2_d0__direct_r04.png b/backend/data/digit_dataset/sections_synthetic/train/0/meter_01122026_s2_d0__direct_r04.png new file mode 100644 index 0000000..7e58c89 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/meter_01122026_s2_d0__direct_r04.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/meter_01122026_s2_d0__direct_r05.png b/backend/data/digit_dataset/sections_synthetic/train/0/meter_01122026_s2_d0__direct_r05.png new file mode 100644 index 0000000..c87e15b Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/meter_01122026_s2_d0__direct_r05.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/meter_01132026_s2_d0__direct_r00.png b/backend/data/digit_dataset/sections_synthetic/train/0/meter_01132026_s2_d0__direct_r00.png new file mode 100644 index 0000000..bec2537 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/meter_01132026_s2_d0__direct_r00.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/meter_01132026_s2_d0__direct_r01.png b/backend/data/digit_dataset/sections_synthetic/train/0/meter_01132026_s2_d0__direct_r01.png new file mode 100644 index 0000000..326adfd Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/meter_01132026_s2_d0__direct_r01.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/meter_01132026_s2_d0__direct_r02.png b/backend/data/digit_dataset/sections_synthetic/train/0/meter_01132026_s2_d0__direct_r02.png new file mode 100644 index 0000000..9814549 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/meter_01132026_s2_d0__direct_r02.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/meter_01132026_s2_d0__direct_r03.png b/backend/data/digit_dataset/sections_synthetic/train/0/meter_01132026_s2_d0__direct_r03.png new file mode 100644 index 0000000..b56d738 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/meter_01132026_s2_d0__direct_r03.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/meter_01132026_s2_d0__direct_r04.png b/backend/data/digit_dataset/sections_synthetic/train/0/meter_01132026_s2_d0__direct_r04.png new file mode 100644 index 0000000..92a006f Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/meter_01132026_s2_d0__direct_r04.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/meter_01132026_s2_d0__direct_r05.png b/backend/data/digit_dataset/sections_synthetic/train/0/meter_01132026_s2_d0__direct_r05.png new file mode 100644 index 0000000..69fe1dd Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/meter_01132026_s2_d0__direct_r05.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/meter_01302026_s2_d0__direct_r00.png b/backend/data/digit_dataset/sections_synthetic/train/0/meter_01302026_s2_d0__direct_r00.png new file mode 100644 index 0000000..3e935e3 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/meter_01302026_s2_d0__direct_r00.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/meter_01302026_s2_d0__direct_r01.png b/backend/data/digit_dataset/sections_synthetic/train/0/meter_01302026_s2_d0__direct_r01.png new file mode 100644 index 0000000..3f2f8d5 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/meter_01302026_s2_d0__direct_r01.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/meter_01302026_s2_d0__direct_r02.png b/backend/data/digit_dataset/sections_synthetic/train/0/meter_01302026_s2_d0__direct_r02.png new file mode 100644 index 0000000..99d9f9b Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/meter_01302026_s2_d0__direct_r02.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/meter_01302026_s2_d0__direct_r03.png b/backend/data/digit_dataset/sections_synthetic/train/0/meter_01302026_s2_d0__direct_r03.png new file mode 100644 index 0000000..45afac7 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/meter_01302026_s2_d0__direct_r03.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/meter_01302026_s2_d0__direct_r04.png b/backend/data/digit_dataset/sections_synthetic/train/0/meter_01302026_s2_d0__direct_r04.png new file mode 100644 index 0000000..55de4b6 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/meter_01302026_s2_d0__direct_r04.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/meter_01302026_s2_d0__direct_r05.png b/backend/data/digit_dataset/sections_synthetic/train/0/meter_01302026_s2_d0__direct_r05.png new file mode 100644 index 0000000..12df644 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/meter_01302026_s2_d0__direct_r05.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00000_s3_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00000_s3_d0.png new file mode 100644 index 0000000..ddd8776 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00000_s3_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00002_s3_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00002_s3_d0.png new file mode 100644 index 0000000..c24a4da Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00002_s3_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00005_s0_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00005_s0_d0.png new file mode 100644 index 0000000..3ec1c4a Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00005_s0_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00008_s3_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00008_s3_d0.png new file mode 100644 index 0000000..5a8f068 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00008_s3_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00009_s1_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00009_s1_d0.png new file mode 100644 index 0000000..ea3c1f5 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00009_s1_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00010_s2_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00010_s2_d0.png new file mode 100644 index 0000000..954eb59 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00010_s2_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00011_s0_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00011_s0_d0.png new file mode 100644 index 0000000..5434d4b Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00011_s0_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00011_s1_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00011_s1_d0.png new file mode 100644 index 0000000..508dc46 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00011_s1_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00012_s1_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00012_s1_d0.png new file mode 100644 index 0000000..d3cadb2 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00012_s1_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00015_s2_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00015_s2_d0.png new file mode 100644 index 0000000..6747e45 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00015_s2_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00017_s1_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00017_s1_d0.png new file mode 100644 index 0000000..5a80b0b Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00017_s1_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00018_s1_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00018_s1_d0.png new file mode 100644 index 0000000..da17d9d Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00018_s1_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00018_s3_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00018_s3_d0.png new file mode 100644 index 0000000..a46cca4 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00018_s3_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00021_s1_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00021_s1_d0.png new file mode 100644 index 0000000..0bdf721 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00021_s1_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00023_s2_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00023_s2_d0.png new file mode 100644 index 0000000..bdc1954 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00023_s2_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00026_s1_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00026_s1_d0.png new file mode 100644 index 0000000..6d98a56 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00026_s1_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00028_s0_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00028_s0_d0.png new file mode 100644 index 0000000..1ca88d3 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00028_s0_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00029_s3_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00029_s3_d0.png new file mode 100644 index 0000000..fc581ee Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00029_s3_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00030_s2_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00030_s2_d0.png new file mode 100644 index 0000000..ef5112d Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00030_s2_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00032_s2_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00032_s2_d0.png new file mode 100644 index 0000000..1a65242 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00032_s2_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00035_s3_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00035_s3_d0.png new file mode 100644 index 0000000..7330547 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00035_s3_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00041_s0_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00041_s0_d0.png new file mode 100644 index 0000000..0ed0643 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00041_s0_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00041_s2_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00041_s2_d0.png new file mode 100644 index 0000000..ccfe160 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00041_s2_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00050_s0_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00050_s0_d0.png new file mode 100644 index 0000000..2f2dadd Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00050_s0_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00051_s3_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00051_s3_d0.png new file mode 100644 index 0000000..bdd042c Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00051_s3_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00058_s1_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00058_s1_d0.png new file mode 100644 index 0000000..4566ced Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00058_s1_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00060_s2_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00060_s2_d0.png new file mode 100644 index 0000000..7fbe53f Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00060_s2_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00062_s0_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00062_s0_d0.png new file mode 100644 index 0000000..9722161 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00062_s0_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00063_s2_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00063_s2_d0.png new file mode 100644 index 0000000..9289394 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00063_s2_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00063_s3_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00063_s3_d0.png new file mode 100644 index 0000000..1cc0e8b Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00063_s3_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00066_s0_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00066_s0_d0.png new file mode 100644 index 0000000..1f1fbc1 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00066_s0_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00068_s1_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00068_s1_d0.png new file mode 100644 index 0000000..b400d85 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00068_s1_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00068_s3_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00068_s3_d0.png new file mode 100644 index 0000000..87d6b31 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00068_s3_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00070_s2_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00070_s2_d0.png new file mode 100644 index 0000000..925ad2f Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00070_s2_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00070_s3_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00070_s3_d0.png new file mode 100644 index 0000000..fa4696d Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00070_s3_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00071_s1_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00071_s1_d0.png new file mode 100644 index 0000000..6096ba2 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00071_s1_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00071_s2_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00071_s2_d0.png new file mode 100644 index 0000000..6298056 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00071_s2_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00071_s3_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00071_s3_d0.png new file mode 100644 index 0000000..3afd346 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00071_s3_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00074_s3_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00074_s3_d0.png new file mode 100644 index 0000000..20b3bec Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00074_s3_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00076_s1_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00076_s1_d0.png new file mode 100644 index 0000000..47af1c1 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00076_s1_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00082_s1_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00082_s1_d0.png new file mode 100644 index 0000000..1e10d1f Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00082_s1_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00083_s0_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00083_s0_d0.png new file mode 100644 index 0000000..452acf8 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00083_s0_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00089_s1_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00089_s1_d0.png new file mode 100644 index 0000000..c0c57aa Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00089_s1_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00089_s2_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00089_s2_d0.png new file mode 100644 index 0000000..7281722 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00089_s2_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00090_s1_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00090_s1_d0.png new file mode 100644 index 0000000..071a42a Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00090_s1_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00092_s1_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00092_s1_d0.png new file mode 100644 index 0000000..560c8e0 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00092_s1_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00094_s0_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00094_s0_d0.png new file mode 100644 index 0000000..0444bc6 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00094_s0_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00094_s2_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00094_s2_d0.png new file mode 100644 index 0000000..eb0aa63 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00094_s2_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00097_s1_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00097_s1_d0.png new file mode 100644 index 0000000..1dd9b7a Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00097_s1_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00098_s2_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00098_s2_d0.png new file mode 100644 index 0000000..9520e69 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00098_s2_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00099_s1_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00099_s1_d0.png new file mode 100644 index 0000000..e14492a Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00099_s1_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00101_s3_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00101_s3_d0.png new file mode 100644 index 0000000..58c5288 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00101_s3_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00110_s2_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00110_s2_d0.png new file mode 100644 index 0000000..821afb0 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00110_s2_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00111_s2_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00111_s2_d0.png new file mode 100644 index 0000000..46a39a1 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00111_s2_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00113_s3_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00113_s3_d0.png new file mode 100644 index 0000000..3530e0a Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00113_s3_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00114_s1_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00114_s1_d0.png new file mode 100644 index 0000000..27808fc Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00114_s1_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00114_s3_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00114_s3_d0.png new file mode 100644 index 0000000..54fba41 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00114_s3_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00118_s2_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00118_s2_d0.png new file mode 100644 index 0000000..6cd76d9 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00118_s2_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00119_s2_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00119_s2_d0.png new file mode 100644 index 0000000..0612857 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00119_s2_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00121_s0_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00121_s0_d0.png new file mode 100644 index 0000000..ee1df05 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00121_s0_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00127_s2_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00127_s2_d0.png new file mode 100644 index 0000000..ddbbf5c Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00127_s2_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00129_s0_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00129_s0_d0.png new file mode 100644 index 0000000..d027dda Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00129_s0_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00138_s1_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00138_s1_d0.png new file mode 100644 index 0000000..e78b123 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00138_s1_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00140_s1_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00140_s1_d0.png new file mode 100644 index 0000000..8a180d4 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00140_s1_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00141_s1_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00141_s1_d0.png new file mode 100644 index 0000000..3f53227 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00141_s1_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00142_s1_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00142_s1_d0.png new file mode 100644 index 0000000..45ebb25 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00142_s1_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00144_s2_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00144_s2_d0.png new file mode 100644 index 0000000..f8d678a Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00144_s2_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00145_s2_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00145_s2_d0.png new file mode 100644 index 0000000..8502063 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00145_s2_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00147_s1_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00147_s1_d0.png new file mode 100644 index 0000000..d507767 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00147_s1_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00149_s1_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00149_s1_d0.png new file mode 100644 index 0000000..a7b978b Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00149_s1_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00149_s3_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00149_s3_d0.png new file mode 100644 index 0000000..4dbc4fa Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00149_s3_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00150_s3_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00150_s3_d0.png new file mode 100644 index 0000000..fce4fe2 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00150_s3_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00154_s1_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00154_s1_d0.png new file mode 100644 index 0000000..168c97b Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00154_s1_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00164_s3_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00164_s3_d0.png new file mode 100644 index 0000000..76de217 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00164_s3_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00170_s1_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00170_s1_d0.png new file mode 100644 index 0000000..6d11924 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00170_s1_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00177_s1_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00177_s1_d0.png new file mode 100644 index 0000000..1e36904 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00177_s1_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00177_s2_d0.png b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00177_s2_d0.png new file mode 100644 index 0000000..51050e4 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/0/synthetic_window_00177_s2_d0.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/meter_02162026_s2_d1__direct_r00.png b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02162026_s2_d1__direct_r00.png new file mode 100644 index 0000000..afa20fe Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02162026_s2_d1__direct_r00.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/meter_02162026_s2_d1__direct_r01.png b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02162026_s2_d1__direct_r01.png new file mode 100644 index 0000000..7828254 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02162026_s2_d1__direct_r01.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/meter_02162026_s2_d1__direct_r02.png b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02162026_s2_d1__direct_r02.png new file mode 100644 index 0000000..de802a5 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02162026_s2_d1__direct_r02.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/meter_02162026_s2_d1__direct_r03.png b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02162026_s2_d1__direct_r03.png new file mode 100644 index 0000000..878499f Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02162026_s2_d1__direct_r03.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/meter_02162026_s2_d1__direct_r04.png b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02162026_s2_d1__direct_r04.png new file mode 100644 index 0000000..28f285e Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02162026_s2_d1__direct_r04.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/meter_02162026_s2_d1__direct_r05.png b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02162026_s2_d1__direct_r05.png new file mode 100644 index 0000000..2644fc8 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02162026_s2_d1__direct_r05.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/meter_02192026_s2_d1__direct_r00.png b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02192026_s2_d1__direct_r00.png new file mode 100644 index 0000000..4690f0e Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02192026_s2_d1__direct_r00.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/meter_02192026_s2_d1__direct_r01.png b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02192026_s2_d1__direct_r01.png new file mode 100644 index 0000000..c5591b8 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02192026_s2_d1__direct_r01.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/meter_02192026_s2_d1__direct_r02.png b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02192026_s2_d1__direct_r02.png new file mode 100644 index 0000000..56994e7 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02192026_s2_d1__direct_r02.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/meter_02192026_s2_d1__direct_r03.png b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02192026_s2_d1__direct_r03.png new file mode 100644 index 0000000..bf60d73 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02192026_s2_d1__direct_r03.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/meter_02192026_s2_d1__direct_r04.png b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02192026_s2_d1__direct_r04.png new file mode 100644 index 0000000..a3aa0a6 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02192026_s2_d1__direct_r04.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/meter_02192026_s2_d1__direct_r05.png b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02192026_s2_d1__direct_r05.png new file mode 100644 index 0000000..421ac1f Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02192026_s2_d1__direct_r05.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/meter_02202026_s2_d1__direct_r00.png b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02202026_s2_d1__direct_r00.png new file mode 100644 index 0000000..3839269 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02202026_s2_d1__direct_r00.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/meter_02202026_s2_d1__direct_r01.png b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02202026_s2_d1__direct_r01.png new file mode 100644 index 0000000..91832b5 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02202026_s2_d1__direct_r01.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/meter_02202026_s2_d1__direct_r02.png b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02202026_s2_d1__direct_r02.png new file mode 100644 index 0000000..4e25d23 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02202026_s2_d1__direct_r02.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/meter_02202026_s2_d1__direct_r03.png b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02202026_s2_d1__direct_r03.png new file mode 100644 index 0000000..dce8c81 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02202026_s2_d1__direct_r03.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/meter_02202026_s2_d1__direct_r04.png b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02202026_s2_d1__direct_r04.png new file mode 100644 index 0000000..19af94e Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02202026_s2_d1__direct_r04.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/meter_02202026_s2_d1__direct_r05.png b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02202026_s2_d1__direct_r05.png new file mode 100644 index 0000000..91b6380 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02202026_s2_d1__direct_r05.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/meter_02242026_s2_d1__direct_r00.png b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02242026_s2_d1__direct_r00.png new file mode 100644 index 0000000..cdf5ba0 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02242026_s2_d1__direct_r00.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/meter_02242026_s2_d1__direct_r01.png b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02242026_s2_d1__direct_r01.png new file mode 100644 index 0000000..493aa9f Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02242026_s2_d1__direct_r01.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/meter_02242026_s2_d1__direct_r02.png b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02242026_s2_d1__direct_r02.png new file mode 100644 index 0000000..ff7ee7a Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02242026_s2_d1__direct_r02.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/meter_02242026_s2_d1__direct_r03.png b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02242026_s2_d1__direct_r03.png new file mode 100644 index 0000000..84f8000 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02242026_s2_d1__direct_r03.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/meter_02242026_s2_d1__direct_r04.png b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02242026_s2_d1__direct_r04.png new file mode 100644 index 0000000..43cb346 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02242026_s2_d1__direct_r04.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/meter_02242026_s2_d1__direct_r05.png b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02242026_s2_d1__direct_r05.png new file mode 100644 index 0000000..769ff22 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02242026_s2_d1__direct_r05.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/meter_02272026_s2_d1__direct_r00.png b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02272026_s2_d1__direct_r00.png new file mode 100644 index 0000000..8a39ec9 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02272026_s2_d1__direct_r00.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/meter_02272026_s2_d1__direct_r01.png b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02272026_s2_d1__direct_r01.png new file mode 100644 index 0000000..0289b4c Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02272026_s2_d1__direct_r01.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/meter_02272026_s2_d1__direct_r02.png b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02272026_s2_d1__direct_r02.png new file mode 100644 index 0000000..160c9a2 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02272026_s2_d1__direct_r02.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/meter_02272026_s2_d1__direct_r03.png b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02272026_s2_d1__direct_r03.png new file mode 100644 index 0000000..120fe66 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02272026_s2_d1__direct_r03.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/meter_02272026_s2_d1__direct_r04.png b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02272026_s2_d1__direct_r04.png new file mode 100644 index 0000000..561ae6c Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02272026_s2_d1__direct_r04.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/meter_02272026_s2_d1__direct_r05.png b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02272026_s2_d1__direct_r05.png new file mode 100644 index 0000000..c8e7454 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/meter_02272026_s2_d1__direct_r05.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/meter_03032026_s2_d1__direct_r00.png b/backend/data/digit_dataset/sections_synthetic/train/1/meter_03032026_s2_d1__direct_r00.png new file mode 100644 index 0000000..8f6998d Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/meter_03032026_s2_d1__direct_r00.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/meter_03032026_s2_d1__direct_r01.png b/backend/data/digit_dataset/sections_synthetic/train/1/meter_03032026_s2_d1__direct_r01.png new file mode 100644 index 0000000..9f0f7e7 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/meter_03032026_s2_d1__direct_r01.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/meter_03032026_s2_d1__direct_r02.png b/backend/data/digit_dataset/sections_synthetic/train/1/meter_03032026_s2_d1__direct_r02.png new file mode 100644 index 0000000..5a256f6 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/meter_03032026_s2_d1__direct_r02.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/meter_03032026_s2_d1__direct_r03.png b/backend/data/digit_dataset/sections_synthetic/train/1/meter_03032026_s2_d1__direct_r03.png new file mode 100644 index 0000000..4790e49 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/meter_03032026_s2_d1__direct_r03.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/meter_03032026_s2_d1__direct_r04.png b/backend/data/digit_dataset/sections_synthetic/train/1/meter_03032026_s2_d1__direct_r04.png new file mode 100644 index 0000000..53f5b60 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/meter_03032026_s2_d1__direct_r04.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/meter_03032026_s2_d1__direct_r05.png b/backend/data/digit_dataset/sections_synthetic/train/1/meter_03032026_s2_d1__direct_r05.png new file mode 100644 index 0000000..bc2bdd4 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/meter_03032026_s2_d1__direct_r05.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/meter_07012020_s0_d1__direct_r00.png b/backend/data/digit_dataset/sections_synthetic/train/1/meter_07012020_s0_d1__direct_r00.png new file mode 100644 index 0000000..4975eda Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/meter_07012020_s0_d1__direct_r00.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/meter_07012020_s0_d1__direct_r01.png b/backend/data/digit_dataset/sections_synthetic/train/1/meter_07012020_s0_d1__direct_r01.png new file mode 100644 index 0000000..16dfed2 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/meter_07012020_s0_d1__direct_r01.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/meter_07012020_s0_d1__direct_r02.png b/backend/data/digit_dataset/sections_synthetic/train/1/meter_07012020_s0_d1__direct_r02.png new file mode 100644 index 0000000..2140912 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/meter_07012020_s0_d1__direct_r02.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/meter_07012020_s0_d1__direct_r03.png b/backend/data/digit_dataset/sections_synthetic/train/1/meter_07012020_s0_d1__direct_r03.png new file mode 100644 index 0000000..09ec5f1 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/meter_07012020_s0_d1__direct_r03.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/meter_07012020_s0_d1__direct_r04.png b/backend/data/digit_dataset/sections_synthetic/train/1/meter_07012020_s0_d1__direct_r04.png new file mode 100644 index 0000000..e8a7423 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/meter_07012020_s0_d1__direct_r04.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/meter_07012020_s0_d1__direct_r05.png b/backend/data/digit_dataset/sections_synthetic/train/1/meter_07012020_s0_d1__direct_r05.png new file mode 100644 index 0000000..a0f0593 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/meter_07012020_s0_d1__direct_r05.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/meter_11112020_s0_d1__direct_r00.png b/backend/data/digit_dataset/sections_synthetic/train/1/meter_11112020_s0_d1__direct_r00.png new file mode 100644 index 0000000..b9a3075 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/meter_11112020_s0_d1__direct_r00.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/meter_11112020_s0_d1__direct_r01.png b/backend/data/digit_dataset/sections_synthetic/train/1/meter_11112020_s0_d1__direct_r01.png new file mode 100644 index 0000000..ce3bf27 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/meter_11112020_s0_d1__direct_r01.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/meter_11112020_s0_d1__direct_r02.png b/backend/data/digit_dataset/sections_synthetic/train/1/meter_11112020_s0_d1__direct_r02.png new file mode 100644 index 0000000..403c2f7 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/meter_11112020_s0_d1__direct_r02.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/meter_11112020_s0_d1__direct_r03.png b/backend/data/digit_dataset/sections_synthetic/train/1/meter_11112020_s0_d1__direct_r03.png new file mode 100644 index 0000000..dc0683c Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/meter_11112020_s0_d1__direct_r03.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/meter_11112020_s0_d1__direct_r04.png b/backend/data/digit_dataset/sections_synthetic/train/1/meter_11112020_s0_d1__direct_r04.png new file mode 100644 index 0000000..326807b Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/meter_11112020_s0_d1__direct_r04.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/meter_11112020_s0_d1__direct_r05.png b/backend/data/digit_dataset/sections_synthetic/train/1/meter_11112020_s0_d1__direct_r05.png new file mode 100644 index 0000000..99e49ed Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/meter_11112020_s0_d1__direct_r05.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/meter_11112020_s2_d1__direct_r00.png b/backend/data/digit_dataset/sections_synthetic/train/1/meter_11112020_s2_d1__direct_r00.png new file mode 100644 index 0000000..9747937 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/meter_11112020_s2_d1__direct_r00.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/meter_11112020_s2_d1__direct_r01.png b/backend/data/digit_dataset/sections_synthetic/train/1/meter_11112020_s2_d1__direct_r01.png new file mode 100644 index 0000000..6ef8415 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/meter_11112020_s2_d1__direct_r01.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/meter_11112020_s2_d1__direct_r02.png b/backend/data/digit_dataset/sections_synthetic/train/1/meter_11112020_s2_d1__direct_r02.png new file mode 100644 index 0000000..db83fd6 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/meter_11112020_s2_d1__direct_r02.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/meter_11112020_s2_d1__direct_r03.png b/backend/data/digit_dataset/sections_synthetic/train/1/meter_11112020_s2_d1__direct_r03.png new file mode 100644 index 0000000..3938644 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/meter_11112020_s2_d1__direct_r03.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/meter_11112020_s2_d1__direct_r04.png b/backend/data/digit_dataset/sections_synthetic/train/1/meter_11112020_s2_d1__direct_r04.png new file mode 100644 index 0000000..016124f Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/meter_11112020_s2_d1__direct_r04.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/meter_11112020_s2_d1__direct_r05.png b/backend/data/digit_dataset/sections_synthetic/train/1/meter_11112020_s2_d1__direct_r05.png new file mode 100644 index 0000000..b12e948 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/meter_11112020_s2_d1__direct_r05.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00002_s2_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00002_s2_d1.png new file mode 100644 index 0000000..b319621 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00002_s2_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00003_s0_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00003_s0_d1.png new file mode 100644 index 0000000..5df6e32 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00003_s0_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00003_s1_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00003_s1_d1.png new file mode 100644 index 0000000..097d084 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00003_s1_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00004_s3_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00004_s3_d1.png new file mode 100644 index 0000000..fea8e55 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00004_s3_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00006_s2_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00006_s2_d1.png new file mode 100644 index 0000000..9f25ab2 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00006_s2_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00007_s3_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00007_s3_d1.png new file mode 100644 index 0000000..3ae42ab Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00007_s3_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00009_s2_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00009_s2_d1.png new file mode 100644 index 0000000..958fa64 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00009_s2_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00010_s1_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00010_s1_d1.png new file mode 100644 index 0000000..c16bd6d Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00010_s1_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00011_s3_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00011_s3_d1.png new file mode 100644 index 0000000..72171a4 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00011_s3_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00013_s0_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00013_s0_d1.png new file mode 100644 index 0000000..3935f96 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00013_s0_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00013_s1_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00013_s1_d1.png new file mode 100644 index 0000000..63d09c0 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00013_s1_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00014_s0_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00014_s0_d1.png new file mode 100644 index 0000000..898f83e Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00014_s0_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00019_s3_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00019_s3_d1.png new file mode 100644 index 0000000..9e7f551 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00019_s3_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00026_s0_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00026_s0_d1.png new file mode 100644 index 0000000..2fe8f33 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00026_s0_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00026_s3_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00026_s3_d1.png new file mode 100644 index 0000000..6934ecb Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00026_s3_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00027_s0_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00027_s0_d1.png new file mode 100644 index 0000000..e1d9c76 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00027_s0_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00027_s3_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00027_s3_d1.png new file mode 100644 index 0000000..be49e74 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00027_s3_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00028_s1_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00028_s1_d1.png new file mode 100644 index 0000000..9f4a1e9 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00028_s1_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00029_s1_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00029_s1_d1.png new file mode 100644 index 0000000..7068bba Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00029_s1_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00030_s3_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00030_s3_d1.png new file mode 100644 index 0000000..87ef44b Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00030_s3_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00032_s0_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00032_s0_d1.png new file mode 100644 index 0000000..91364ac Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00032_s0_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00034_s2_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00034_s2_d1.png new file mode 100644 index 0000000..2682b5d Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00034_s2_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00037_s0_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00037_s0_d1.png new file mode 100644 index 0000000..11a7afc Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00037_s0_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00037_s1_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00037_s1_d1.png new file mode 100644 index 0000000..e70d8cd Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00037_s1_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00037_s3_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00037_s3_d1.png new file mode 100644 index 0000000..e7569ea Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00037_s3_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00038_s3_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00038_s3_d1.png new file mode 100644 index 0000000..b543544 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00038_s3_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00039_s0_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00039_s0_d1.png new file mode 100644 index 0000000..345acd9 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00039_s0_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00042_s2_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00042_s2_d1.png new file mode 100644 index 0000000..4151e7b Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00042_s2_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00047_s2_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00047_s2_d1.png new file mode 100644 index 0000000..5313dff Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00047_s2_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00048_s1_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00048_s1_d1.png new file mode 100644 index 0000000..27cddef Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00048_s1_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00051_s0_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00051_s0_d1.png new file mode 100644 index 0000000..c478054 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00051_s0_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00051_s1_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00051_s1_d1.png new file mode 100644 index 0000000..0ca412e Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00051_s1_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00053_s3_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00053_s3_d1.png new file mode 100644 index 0000000..52aa97e Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00053_s3_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00055_s1_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00055_s1_d1.png new file mode 100644 index 0000000..40e73ad Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00055_s1_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00055_s2_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00055_s2_d1.png new file mode 100644 index 0000000..089d956 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00055_s2_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00057_s1_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00057_s1_d1.png new file mode 100644 index 0000000..9665123 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00057_s1_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00057_s2_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00057_s2_d1.png new file mode 100644 index 0000000..419bc14 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00057_s2_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00066_s1_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00066_s1_d1.png new file mode 100644 index 0000000..8bdf177 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00066_s1_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00067_s1_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00067_s1_d1.png new file mode 100644 index 0000000..33e799e Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00067_s1_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00068_s0_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00068_s0_d1.png new file mode 100644 index 0000000..acf3a73 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00068_s0_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00069_s3_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00069_s3_d1.png new file mode 100644 index 0000000..11ecf33 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00069_s3_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00072_s1_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00072_s1_d1.png new file mode 100644 index 0000000..d5bd2a4 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00072_s1_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00073_s0_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00073_s0_d1.png new file mode 100644 index 0000000..e6537b9 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00073_s0_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00073_s1_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00073_s1_d1.png new file mode 100644 index 0000000..e2a4c87 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00073_s1_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00078_s1_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00078_s1_d1.png new file mode 100644 index 0000000..31a4de7 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00078_s1_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00079_s3_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00079_s3_d1.png new file mode 100644 index 0000000..320274a Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00079_s3_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00081_s0_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00081_s0_d1.png new file mode 100644 index 0000000..3f4185f Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00081_s0_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00085_s0_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00085_s0_d1.png new file mode 100644 index 0000000..c90fd5b Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00085_s0_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00096_s1_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00096_s1_d1.png new file mode 100644 index 0000000..d4e301e Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00096_s1_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00097_s3_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00097_s3_d1.png new file mode 100644 index 0000000..6d3fe50 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00097_s3_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00098_s0_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00098_s0_d1.png new file mode 100644 index 0000000..a3de272 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00098_s0_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00098_s3_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00098_s3_d1.png new file mode 100644 index 0000000..95c6249 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00098_s3_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00099_s3_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00099_s3_d1.png new file mode 100644 index 0000000..697146c Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00099_s3_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00105_s1_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00105_s1_d1.png new file mode 100644 index 0000000..7b7a22b Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00105_s1_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00106_s0_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00106_s0_d1.png new file mode 100644 index 0000000..f881983 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00106_s0_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00107_s0_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00107_s0_d1.png new file mode 100644 index 0000000..8e1722d Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00107_s0_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00108_s2_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00108_s2_d1.png new file mode 100644 index 0000000..109beb0 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00108_s2_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00108_s3_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00108_s3_d1.png new file mode 100644 index 0000000..76a1518 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00108_s3_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00111_s0_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00111_s0_d1.png new file mode 100644 index 0000000..0e27efe Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00111_s0_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00114_s2_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00114_s2_d1.png new file mode 100644 index 0000000..77f01e4 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00114_s2_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00115_s2_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00115_s2_d1.png new file mode 100644 index 0000000..4edf1bb Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00115_s2_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00116_s2_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00116_s2_d1.png new file mode 100644 index 0000000..8f0abb2 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00116_s2_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00119_s0_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00119_s0_d1.png new file mode 100644 index 0000000..1748795 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00119_s0_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00119_s3_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00119_s3_d1.png new file mode 100644 index 0000000..8b8fab3 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00119_s3_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00122_s2_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00122_s2_d1.png new file mode 100644 index 0000000..c5f8119 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00122_s2_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00123_s1_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00123_s1_d1.png new file mode 100644 index 0000000..c9d0f28 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00123_s1_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00123_s2_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00123_s2_d1.png new file mode 100644 index 0000000..12c38aa Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00123_s2_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00126_s3_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00126_s3_d1.png new file mode 100644 index 0000000..90242b2 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00126_s3_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00128_s0_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00128_s0_d1.png new file mode 100644 index 0000000..57b68dc Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00128_s0_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00128_s2_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00128_s2_d1.png new file mode 100644 index 0000000..63a4589 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00128_s2_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00131_s0_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00131_s0_d1.png new file mode 100644 index 0000000..b19cfa1 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00131_s0_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00136_s2_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00136_s2_d1.png new file mode 100644 index 0000000..a2a2676 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00136_s2_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00137_s0_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00137_s0_d1.png new file mode 100644 index 0000000..afc49e2 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00137_s0_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00140_s0_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00140_s0_d1.png new file mode 100644 index 0000000..3bb3c50 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00140_s0_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00141_s0_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00141_s0_d1.png new file mode 100644 index 0000000..2ff3783 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00141_s0_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00143_s2_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00143_s2_d1.png new file mode 100644 index 0000000..2e80c5e Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00143_s2_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00145_s1_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00145_s1_d1.png new file mode 100644 index 0000000..61b8213 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00145_s1_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00146_s3_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00146_s3_d1.png new file mode 100644 index 0000000..a59658b Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00146_s3_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00147_s0_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00147_s0_d1.png new file mode 100644 index 0000000..1973226 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00147_s0_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00151_s0_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00151_s0_d1.png new file mode 100644 index 0000000..60b5c0f Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00151_s0_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00158_s3_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00158_s3_d1.png new file mode 100644 index 0000000..4e7885c Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00158_s3_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00159_s0_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00159_s0_d1.png new file mode 100644 index 0000000..3b50218 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00159_s0_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00172_s3_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00172_s3_d1.png new file mode 100644 index 0000000..f717b1e Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00172_s3_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00174_s3_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00174_s3_d1.png new file mode 100644 index 0000000..26a1197 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00174_s3_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00179_s3_d1.png b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00179_s3_d1.png new file mode 100644 index 0000000..bffa110 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/1/synthetic_window_00179_s3_d1.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_01122026_s0_d2__direct_r00.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01122026_s0_d2__direct_r00.png new file mode 100644 index 0000000..7b7f5a3 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01122026_s0_d2__direct_r00.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_01122026_s0_d2__direct_r01.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01122026_s0_d2__direct_r01.png new file mode 100644 index 0000000..0373baf Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01122026_s0_d2__direct_r01.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_01122026_s0_d2__direct_r02.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01122026_s0_d2__direct_r02.png new file mode 100644 index 0000000..d0687a3 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01122026_s0_d2__direct_r02.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_01122026_s0_d2__direct_r03.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01122026_s0_d2__direct_r03.png new file mode 100644 index 0000000..3931d2d Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01122026_s0_d2__direct_r03.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_01122026_s0_d2__direct_r04.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01122026_s0_d2__direct_r04.png new file mode 100644 index 0000000..d0b7248 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01122026_s0_d2__direct_r04.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_01122026_s0_d2__direct_r05.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01122026_s0_d2__direct_r05.png new file mode 100644 index 0000000..f4f750a Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01122026_s0_d2__direct_r05.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_01122026_s3_d2__direct_r00.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01122026_s3_d2__direct_r00.png new file mode 100644 index 0000000..42eb695 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01122026_s3_d2__direct_r00.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_01122026_s3_d2__direct_r01.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01122026_s3_d2__direct_r01.png new file mode 100644 index 0000000..a30ab45 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01122026_s3_d2__direct_r01.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_01122026_s3_d2__direct_r02.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01122026_s3_d2__direct_r02.png new file mode 100644 index 0000000..a5b9582 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01122026_s3_d2__direct_r02.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_01122026_s3_d2__direct_r03.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01122026_s3_d2__direct_r03.png new file mode 100644 index 0000000..470d238 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01122026_s3_d2__direct_r03.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_01122026_s3_d2__direct_r04.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01122026_s3_d2__direct_r04.png new file mode 100644 index 0000000..35278e8 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01122026_s3_d2__direct_r04.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_01122026_s3_d2__direct_r05.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01122026_s3_d2__direct_r05.png new file mode 100644 index 0000000..228fcb7 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01122026_s3_d2__direct_r05.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_01132026_s0_d2__direct_r00.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01132026_s0_d2__direct_r00.png new file mode 100644 index 0000000..c89d996 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01132026_s0_d2__direct_r00.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_01132026_s0_d2__direct_r01.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01132026_s0_d2__direct_r01.png new file mode 100644 index 0000000..176843e Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01132026_s0_d2__direct_r01.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_01132026_s0_d2__direct_r02.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01132026_s0_d2__direct_r02.png new file mode 100644 index 0000000..8768eb3 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01132026_s0_d2__direct_r02.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_01132026_s0_d2__direct_r03.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01132026_s0_d2__direct_r03.png new file mode 100644 index 0000000..009737f Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01132026_s0_d2__direct_r03.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_01132026_s0_d2__direct_r04.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01132026_s0_d2__direct_r04.png new file mode 100644 index 0000000..10cdd3b Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01132026_s0_d2__direct_r04.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_01132026_s0_d2__direct_r05.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01132026_s0_d2__direct_r05.png new file mode 100644 index 0000000..94f9249 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01132026_s0_d2__direct_r05.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_01132026_s3_d2__direct_r00.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01132026_s3_d2__direct_r00.png new file mode 100644 index 0000000..f891d7c Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01132026_s3_d2__direct_r00.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_01132026_s3_d2__direct_r01.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01132026_s3_d2__direct_r01.png new file mode 100644 index 0000000..bf28476 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01132026_s3_d2__direct_r01.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_01132026_s3_d2__direct_r02.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01132026_s3_d2__direct_r02.png new file mode 100644 index 0000000..c483839 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01132026_s3_d2__direct_r02.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_01132026_s3_d2__direct_r03.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01132026_s3_d2__direct_r03.png new file mode 100644 index 0000000..d49ccea Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01132026_s3_d2__direct_r03.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_01132026_s3_d2__direct_r04.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01132026_s3_d2__direct_r04.png new file mode 100644 index 0000000..a349445 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01132026_s3_d2__direct_r04.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_01132026_s3_d2__direct_r05.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01132026_s3_d2__direct_r05.png new file mode 100644 index 0000000..0767d5f Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01132026_s3_d2__direct_r05.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_01302026_s0_d2__direct_r00.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01302026_s0_d2__direct_r00.png new file mode 100644 index 0000000..e019468 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01302026_s0_d2__direct_r00.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_01302026_s0_d2__direct_r01.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01302026_s0_d2__direct_r01.png new file mode 100644 index 0000000..2566c4a Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01302026_s0_d2__direct_r01.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_01302026_s0_d2__direct_r02.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01302026_s0_d2__direct_r02.png new file mode 100644 index 0000000..166ec87 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01302026_s0_d2__direct_r02.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_01302026_s0_d2__direct_r03.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01302026_s0_d2__direct_r03.png new file mode 100644 index 0000000..2642e38 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01302026_s0_d2__direct_r03.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_01302026_s0_d2__direct_r04.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01302026_s0_d2__direct_r04.png new file mode 100644 index 0000000..8c74887 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01302026_s0_d2__direct_r04.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_01302026_s0_d2__direct_r05.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01302026_s0_d2__direct_r05.png new file mode 100644 index 0000000..913da02 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_01302026_s0_d2__direct_r05.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_02162026_s0_d2__direct_r00.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02162026_s0_d2__direct_r00.png new file mode 100644 index 0000000..e958d6c Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02162026_s0_d2__direct_r00.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_02162026_s0_d2__direct_r01.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02162026_s0_d2__direct_r01.png new file mode 100644 index 0000000..8f9c544 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02162026_s0_d2__direct_r01.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_02162026_s0_d2__direct_r02.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02162026_s0_d2__direct_r02.png new file mode 100644 index 0000000..47620a1 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02162026_s0_d2__direct_r02.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_02162026_s0_d2__direct_r03.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02162026_s0_d2__direct_r03.png new file mode 100644 index 0000000..8a11596 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02162026_s0_d2__direct_r03.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_02162026_s0_d2__direct_r04.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02162026_s0_d2__direct_r04.png new file mode 100644 index 0000000..7f87448 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02162026_s0_d2__direct_r04.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_02162026_s0_d2__direct_r05.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02162026_s0_d2__direct_r05.png new file mode 100644 index 0000000..891b061 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02162026_s0_d2__direct_r05.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_02162026_s3_d2__direct_r00.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02162026_s3_d2__direct_r00.png new file mode 100644 index 0000000..2de1d3e Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02162026_s3_d2__direct_r00.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_02162026_s3_d2__direct_r01.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02162026_s3_d2__direct_r01.png new file mode 100644 index 0000000..34b3cc0 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02162026_s3_d2__direct_r01.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_02162026_s3_d2__direct_r02.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02162026_s3_d2__direct_r02.png new file mode 100644 index 0000000..b99a27b Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02162026_s3_d2__direct_r02.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_02162026_s3_d2__direct_r03.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02162026_s3_d2__direct_r03.png new file mode 100644 index 0000000..600e487 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02162026_s3_d2__direct_r03.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_02162026_s3_d2__direct_r04.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02162026_s3_d2__direct_r04.png new file mode 100644 index 0000000..7b8a964 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02162026_s3_d2__direct_r04.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_02162026_s3_d2__direct_r05.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02162026_s3_d2__direct_r05.png new file mode 100644 index 0000000..25d3dcb Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02162026_s3_d2__direct_r05.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_02192026_s0_d2__direct_r00.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02192026_s0_d2__direct_r00.png new file mode 100644 index 0000000..0cca994 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02192026_s0_d2__direct_r00.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_02192026_s0_d2__direct_r01.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02192026_s0_d2__direct_r01.png new file mode 100644 index 0000000..5233640 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02192026_s0_d2__direct_r01.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_02192026_s0_d2__direct_r02.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02192026_s0_d2__direct_r02.png new file mode 100644 index 0000000..1af0988 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02192026_s0_d2__direct_r02.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_02192026_s0_d2__direct_r03.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02192026_s0_d2__direct_r03.png new file mode 100644 index 0000000..396da9b Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02192026_s0_d2__direct_r03.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_02192026_s0_d2__direct_r04.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02192026_s0_d2__direct_r04.png new file mode 100644 index 0000000..b5c5a21 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02192026_s0_d2__direct_r04.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_02192026_s0_d2__direct_r05.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02192026_s0_d2__direct_r05.png new file mode 100644 index 0000000..805a85f Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02192026_s0_d2__direct_r05.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_02192026_s3_d2__direct_r00.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02192026_s3_d2__direct_r00.png new file mode 100644 index 0000000..7fbcda0 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02192026_s3_d2__direct_r00.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_02192026_s3_d2__direct_r01.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02192026_s3_d2__direct_r01.png new file mode 100644 index 0000000..dd78329 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02192026_s3_d2__direct_r01.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_02192026_s3_d2__direct_r02.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02192026_s3_d2__direct_r02.png new file mode 100644 index 0000000..d9e477f Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02192026_s3_d2__direct_r02.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_02192026_s3_d2__direct_r03.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02192026_s3_d2__direct_r03.png new file mode 100644 index 0000000..7aff0c6 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02192026_s3_d2__direct_r03.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_02192026_s3_d2__direct_r04.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02192026_s3_d2__direct_r04.png new file mode 100644 index 0000000..bbb9099 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02192026_s3_d2__direct_r04.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_02192026_s3_d2__direct_r05.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02192026_s3_d2__direct_r05.png new file mode 100644 index 0000000..2f3cb36 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02192026_s3_d2__direct_r05.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_02202026_s0_d2__direct_r00.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02202026_s0_d2__direct_r00.png new file mode 100644 index 0000000..138229f Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02202026_s0_d2__direct_r00.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_02202026_s0_d2__direct_r01.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02202026_s0_d2__direct_r01.png new file mode 100644 index 0000000..3a20401 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02202026_s0_d2__direct_r01.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_02202026_s0_d2__direct_r02.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02202026_s0_d2__direct_r02.png new file mode 100644 index 0000000..56e0993 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02202026_s0_d2__direct_r02.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_02202026_s0_d2__direct_r03.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02202026_s0_d2__direct_r03.png new file mode 100644 index 0000000..076035d Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02202026_s0_d2__direct_r03.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_02202026_s0_d2__direct_r04.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02202026_s0_d2__direct_r04.png new file mode 100644 index 0000000..8f3666a Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02202026_s0_d2__direct_r04.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_02202026_s0_d2__direct_r05.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02202026_s0_d2__direct_r05.png new file mode 100644 index 0000000..5672b38 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02202026_s0_d2__direct_r05.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_02242026_s0_d2__direct_r00.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02242026_s0_d2__direct_r00.png new file mode 100644 index 0000000..553b966 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02242026_s0_d2__direct_r00.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_02242026_s0_d2__direct_r01.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02242026_s0_d2__direct_r01.png new file mode 100644 index 0000000..22d75b1 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02242026_s0_d2__direct_r01.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_02242026_s0_d2__direct_r02.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02242026_s0_d2__direct_r02.png new file mode 100644 index 0000000..82bbfe9 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02242026_s0_d2__direct_r02.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_02242026_s0_d2__direct_r03.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02242026_s0_d2__direct_r03.png new file mode 100644 index 0000000..4fac327 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02242026_s0_d2__direct_r03.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_02242026_s0_d2__direct_r04.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02242026_s0_d2__direct_r04.png new file mode 100644 index 0000000..a61d81f Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02242026_s0_d2__direct_r04.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_02242026_s0_d2__direct_r05.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02242026_s0_d2__direct_r05.png new file mode 100644 index 0000000..259501d Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02242026_s0_d2__direct_r05.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_02272026_s0_d2__direct_r00.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02272026_s0_d2__direct_r00.png new file mode 100644 index 0000000..6d0dbc0 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02272026_s0_d2__direct_r00.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_02272026_s0_d2__direct_r01.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02272026_s0_d2__direct_r01.png new file mode 100644 index 0000000..c89d9a7 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02272026_s0_d2__direct_r01.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_02272026_s0_d2__direct_r02.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02272026_s0_d2__direct_r02.png new file mode 100644 index 0000000..37ae000 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02272026_s0_d2__direct_r02.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_02272026_s0_d2__direct_r03.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02272026_s0_d2__direct_r03.png new file mode 100644 index 0000000..60bf673 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02272026_s0_d2__direct_r03.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_02272026_s0_d2__direct_r04.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02272026_s0_d2__direct_r04.png new file mode 100644 index 0000000..ac1bc8e Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02272026_s0_d2__direct_r04.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_02272026_s0_d2__direct_r05.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02272026_s0_d2__direct_r05.png new file mode 100644 index 0000000..73bb0ba Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_02272026_s0_d2__direct_r05.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_03032026_s0_d2__direct_r00.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_03032026_s0_d2__direct_r00.png new file mode 100644 index 0000000..38cd58b Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_03032026_s0_d2__direct_r00.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_03032026_s0_d2__direct_r01.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_03032026_s0_d2__direct_r01.png new file mode 100644 index 0000000..edfb619 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_03032026_s0_d2__direct_r01.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_03032026_s0_d2__direct_r02.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_03032026_s0_d2__direct_r02.png new file mode 100644 index 0000000..fe6fb46 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_03032026_s0_d2__direct_r02.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_03032026_s0_d2__direct_r03.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_03032026_s0_d2__direct_r03.png new file mode 100644 index 0000000..1a28e0e Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_03032026_s0_d2__direct_r03.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_03032026_s0_d2__direct_r04.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_03032026_s0_d2__direct_r04.png new file mode 100644 index 0000000..841e59d Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_03032026_s0_d2__direct_r04.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_03032026_s0_d2__direct_r05.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_03032026_s0_d2__direct_r05.png new file mode 100644 index 0000000..8688877 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_03032026_s0_d2__direct_r05.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_10092025_s0_d2__direct_r00.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_10092025_s0_d2__direct_r00.png new file mode 100644 index 0000000..0555d14 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_10092025_s0_d2__direct_r00.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_10092025_s0_d2__direct_r01.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_10092025_s0_d2__direct_r01.png new file mode 100644 index 0000000..d0c68c1 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_10092025_s0_d2__direct_r01.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_10092025_s0_d2__direct_r02.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_10092025_s0_d2__direct_r02.png new file mode 100644 index 0000000..03ea6dc Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_10092025_s0_d2__direct_r02.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_10092025_s0_d2__direct_r03.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_10092025_s0_d2__direct_r03.png new file mode 100644 index 0000000..c658883 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_10092025_s0_d2__direct_r03.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_10092025_s0_d2__direct_r04.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_10092025_s0_d2__direct_r04.png new file mode 100644 index 0000000..160e976 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_10092025_s0_d2__direct_r04.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_10092025_s0_d2__direct_r05.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_10092025_s0_d2__direct_r05.png new file mode 100644 index 0000000..b213a32 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_10092025_s0_d2__direct_r05.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_10092025_s1_d2__direct_r00.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_10092025_s1_d2__direct_r00.png new file mode 100644 index 0000000..de449cc Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_10092025_s1_d2__direct_r00.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_10092025_s1_d2__direct_r01.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_10092025_s1_d2__direct_r01.png new file mode 100644 index 0000000..0cfc02a Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_10092025_s1_d2__direct_r01.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_10092025_s1_d2__direct_r02.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_10092025_s1_d2__direct_r02.png new file mode 100644 index 0000000..3d46860 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_10092025_s1_d2__direct_r02.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_10092025_s1_d2__direct_r03.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_10092025_s1_d2__direct_r03.png new file mode 100644 index 0000000..9e13b6d Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_10092025_s1_d2__direct_r03.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_10092025_s1_d2__direct_r04.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_10092025_s1_d2__direct_r04.png new file mode 100644 index 0000000..bd959bc Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_10092025_s1_d2__direct_r04.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/meter_10092025_s1_d2__direct_r05.png b/backend/data/digit_dataset/sections_synthetic/train/2/meter_10092025_s1_d2__direct_r05.png new file mode 100644 index 0000000..a7423b0 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/meter_10092025_s1_d2__direct_r05.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00009_s0_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00009_s0_d2.png new file mode 100644 index 0000000..3db45b3 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00009_s0_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00018_s2_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00018_s2_d2.png new file mode 100644 index 0000000..b034caa Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00018_s2_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00022_s2_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00022_s2_d2.png new file mode 100644 index 0000000..28bcd0d Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00022_s2_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00022_s3_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00022_s3_d2.png new file mode 100644 index 0000000..75374f1 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00022_s3_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00025_s3_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00025_s3_d2.png new file mode 100644 index 0000000..84b5954 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00025_s3_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00027_s1_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00027_s1_d2.png new file mode 100644 index 0000000..b5d76a2 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00027_s1_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00028_s2_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00028_s2_d2.png new file mode 100644 index 0000000..3a1b2c8 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00028_s2_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00030_s1_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00030_s1_d2.png new file mode 100644 index 0000000..b845319 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00030_s1_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00031_s2_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00031_s2_d2.png new file mode 100644 index 0000000..1960236 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00031_s2_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00032_s3_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00032_s3_d2.png new file mode 100644 index 0000000..7543149 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00032_s3_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00034_s3_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00034_s3_d2.png new file mode 100644 index 0000000..c6fb06d Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00034_s3_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00035_s2_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00035_s2_d2.png new file mode 100644 index 0000000..f8d5cc0 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00035_s2_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00039_s1_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00039_s1_d2.png new file mode 100644 index 0000000..d62accd Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00039_s1_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00039_s3_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00039_s3_d2.png new file mode 100644 index 0000000..26488b9 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00039_s3_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00042_s0_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00042_s0_d2.png new file mode 100644 index 0000000..204561f Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00042_s0_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00044_s1_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00044_s1_d2.png new file mode 100644 index 0000000..f075486 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00044_s1_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00044_s2_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00044_s2_d2.png new file mode 100644 index 0000000..02665dc Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00044_s2_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00046_s1_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00046_s1_d2.png new file mode 100644 index 0000000..ca421e0 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00046_s1_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00049_s3_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00049_s3_d2.png new file mode 100644 index 0000000..0434b3d Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00049_s3_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00052_s0_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00052_s0_d2.png new file mode 100644 index 0000000..2951336 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00052_s0_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00052_s3_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00052_s3_d2.png new file mode 100644 index 0000000..f260a41 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00052_s3_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00054_s2_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00054_s2_d2.png new file mode 100644 index 0000000..af70b79 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00054_s2_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00062_s3_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00062_s3_d2.png new file mode 100644 index 0000000..7ff7f40 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00062_s3_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00063_s1_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00063_s1_d2.png new file mode 100644 index 0000000..f449b49 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00063_s1_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00069_s0_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00069_s0_d2.png new file mode 100644 index 0000000..a38d17f Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00069_s0_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00072_s0_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00072_s0_d2.png new file mode 100644 index 0000000..adf7dc3 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00072_s0_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00076_s0_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00076_s0_d2.png new file mode 100644 index 0000000..9493695 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00076_s0_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00078_s3_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00078_s3_d2.png new file mode 100644 index 0000000..87ebe4a Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00078_s3_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00084_s1_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00084_s1_d2.png new file mode 100644 index 0000000..eb772d0 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00084_s1_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00085_s2_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00085_s2_d2.png new file mode 100644 index 0000000..99ad882 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00085_s2_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00086_s0_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00086_s0_d2.png new file mode 100644 index 0000000..72161b8 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00086_s0_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00087_s1_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00087_s1_d2.png new file mode 100644 index 0000000..6467daa Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00087_s1_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00089_s0_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00089_s0_d2.png new file mode 100644 index 0000000..fce44d4 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00089_s0_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00090_s3_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00090_s3_d2.png new file mode 100644 index 0000000..132bd36 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00090_s3_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00093_s1_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00093_s1_d2.png new file mode 100644 index 0000000..d360d8a Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00093_s1_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00100_s0_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00100_s0_d2.png new file mode 100644 index 0000000..fb4bdfd Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00100_s0_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00100_s1_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00100_s1_d2.png new file mode 100644 index 0000000..a22d51e Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00100_s1_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00103_s0_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00103_s0_d2.png new file mode 100644 index 0000000..731dbf9 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00103_s0_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00107_s3_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00107_s3_d2.png new file mode 100644 index 0000000..23bb39a Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00107_s3_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00112_s1_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00112_s1_d2.png new file mode 100644 index 0000000..7cd85b5 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00112_s1_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00112_s2_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00112_s2_d2.png new file mode 100644 index 0000000..a15ccf3 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00112_s2_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00119_s1_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00119_s1_d2.png new file mode 100644 index 0000000..e08f8cb Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00119_s1_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00121_s3_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00121_s3_d2.png new file mode 100644 index 0000000..23d830c Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00121_s3_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00125_s3_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00125_s3_d2.png new file mode 100644 index 0000000..70d4416 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00125_s3_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00130_s0_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00130_s0_d2.png new file mode 100644 index 0000000..2a74bfe Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00130_s0_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00130_s3_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00130_s3_d2.png new file mode 100644 index 0000000..1db2903 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00130_s3_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00131_s3_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00131_s3_d2.png new file mode 100644 index 0000000..622d39b Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00131_s3_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00136_s0_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00136_s0_d2.png new file mode 100644 index 0000000..319d880 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00136_s0_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00139_s2_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00139_s2_d2.png new file mode 100644 index 0000000..de90c22 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00139_s2_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00139_s3_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00139_s3_d2.png new file mode 100644 index 0000000..6c76259 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00139_s3_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00146_s2_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00146_s2_d2.png new file mode 100644 index 0000000..7d0a17c Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00146_s2_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00152_s1_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00152_s1_d2.png new file mode 100644 index 0000000..4cbb1a0 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00152_s1_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00153_s2_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00153_s2_d2.png new file mode 100644 index 0000000..be43523 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00153_s2_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00154_s2_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00154_s2_d2.png new file mode 100644 index 0000000..9d53174 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00154_s2_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00160_s0_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00160_s0_d2.png new file mode 100644 index 0000000..80761b8 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00160_s0_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00162_s2_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00162_s2_d2.png new file mode 100644 index 0000000..07b43df Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00162_s2_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00164_s2_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00164_s2_d2.png new file mode 100644 index 0000000..0e0d6e6 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00164_s2_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00165_s1_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00165_s1_d2.png new file mode 100644 index 0000000..af6442d Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00165_s1_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00165_s2_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00165_s2_d2.png new file mode 100644 index 0000000..027453a Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00165_s2_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00166_s1_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00166_s1_d2.png new file mode 100644 index 0000000..ebe7116 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00166_s1_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00167_s1_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00167_s1_d2.png new file mode 100644 index 0000000..05dc659 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00167_s1_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00168_s0_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00168_s0_d2.png new file mode 100644 index 0000000..e7cab3a Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00168_s0_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00169_s0_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00169_s0_d2.png new file mode 100644 index 0000000..0b644e3 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00169_s0_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00170_s0_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00170_s0_d2.png new file mode 100644 index 0000000..c8e1172 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00170_s0_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00171_s3_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00171_s3_d2.png new file mode 100644 index 0000000..ee7616a Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00171_s3_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00172_s2_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00172_s2_d2.png new file mode 100644 index 0000000..e46e528 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00172_s2_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00173_s0_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00173_s0_d2.png new file mode 100644 index 0000000..c7f0b37 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00173_s0_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00175_s0_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00175_s0_d2.png new file mode 100644 index 0000000..26c7d40 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00175_s0_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00175_s3_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00175_s3_d2.png new file mode 100644 index 0000000..d8e13e4 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00175_s3_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00179_s2_d2.png b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00179_s2_d2.png new file mode 100644 index 0000000..bc44105 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/2/synthetic_window_00179_s2_d2.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_01122026_s1_d3__direct_r00.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_01122026_s1_d3__direct_r00.png new file mode 100644 index 0000000..df7130d Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_01122026_s1_d3__direct_r00.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_01122026_s1_d3__direct_r01.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_01122026_s1_d3__direct_r01.png new file mode 100644 index 0000000..ba8f346 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_01122026_s1_d3__direct_r01.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_01122026_s1_d3__direct_r02.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_01122026_s1_d3__direct_r02.png new file mode 100644 index 0000000..fd261fd Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_01122026_s1_d3__direct_r02.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_01122026_s1_d3__direct_r03.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_01122026_s1_d3__direct_r03.png new file mode 100644 index 0000000..bd33ee6 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_01122026_s1_d3__direct_r03.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_01122026_s1_d3__direct_r04.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_01122026_s1_d3__direct_r04.png new file mode 100644 index 0000000..bd0b3e3 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_01122026_s1_d3__direct_r04.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_01122026_s1_d3__direct_r05.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_01122026_s1_d3__direct_r05.png new file mode 100644 index 0000000..4cd4ed6 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_01122026_s1_d3__direct_r05.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_01132026_s1_d3__direct_r00.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_01132026_s1_d3__direct_r00.png new file mode 100644 index 0000000..8fe9aad Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_01132026_s1_d3__direct_r00.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_01132026_s1_d3__direct_r01.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_01132026_s1_d3__direct_r01.png new file mode 100644 index 0000000..f7b5d66 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_01132026_s1_d3__direct_r01.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_01132026_s1_d3__direct_r02.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_01132026_s1_d3__direct_r02.png new file mode 100644 index 0000000..7bc5cd8 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_01132026_s1_d3__direct_r02.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_01132026_s1_d3__direct_r03.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_01132026_s1_d3__direct_r03.png new file mode 100644 index 0000000..22c0ddd Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_01132026_s1_d3__direct_r03.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_01132026_s1_d3__direct_r04.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_01132026_s1_d3__direct_r04.png new file mode 100644 index 0000000..7c7e787 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_01132026_s1_d3__direct_r04.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_01132026_s1_d3__direct_r05.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_01132026_s1_d3__direct_r05.png new file mode 100644 index 0000000..c5df6ad Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_01132026_s1_d3__direct_r05.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_01302026_s1_d3__direct_r00.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_01302026_s1_d3__direct_r00.png new file mode 100644 index 0000000..a1d2849 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_01302026_s1_d3__direct_r00.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_01302026_s1_d3__direct_r01.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_01302026_s1_d3__direct_r01.png new file mode 100644 index 0000000..648d9b3 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_01302026_s1_d3__direct_r01.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_01302026_s1_d3__direct_r02.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_01302026_s1_d3__direct_r02.png new file mode 100644 index 0000000..f4d1767 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_01302026_s1_d3__direct_r02.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_01302026_s1_d3__direct_r03.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_01302026_s1_d3__direct_r03.png new file mode 100644 index 0000000..2fdc65a Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_01302026_s1_d3__direct_r03.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_01302026_s1_d3__direct_r04.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_01302026_s1_d3__direct_r04.png new file mode 100644 index 0000000..27072cb Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_01302026_s1_d3__direct_r04.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_01302026_s1_d3__direct_r05.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_01302026_s1_d3__direct_r05.png new file mode 100644 index 0000000..93c4acd Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_01302026_s1_d3__direct_r05.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_02162026_s1_d3__direct_r00.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02162026_s1_d3__direct_r00.png new file mode 100644 index 0000000..02031d8 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02162026_s1_d3__direct_r00.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_02162026_s1_d3__direct_r01.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02162026_s1_d3__direct_r01.png new file mode 100644 index 0000000..6c9e43c Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02162026_s1_d3__direct_r01.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_02162026_s1_d3__direct_r02.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02162026_s1_d3__direct_r02.png new file mode 100644 index 0000000..ad45d7a Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02162026_s1_d3__direct_r02.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_02162026_s1_d3__direct_r03.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02162026_s1_d3__direct_r03.png new file mode 100644 index 0000000..930284c Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02162026_s1_d3__direct_r03.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_02162026_s1_d3__direct_r04.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02162026_s1_d3__direct_r04.png new file mode 100644 index 0000000..e8da4ff Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02162026_s1_d3__direct_r04.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_02162026_s1_d3__direct_r05.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02162026_s1_d3__direct_r05.png new file mode 100644 index 0000000..a281cb2 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02162026_s1_d3__direct_r05.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_02192026_s1_d3__direct_r00.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02192026_s1_d3__direct_r00.png new file mode 100644 index 0000000..8967284 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02192026_s1_d3__direct_r00.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_02192026_s1_d3__direct_r01.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02192026_s1_d3__direct_r01.png new file mode 100644 index 0000000..cbfed9a Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02192026_s1_d3__direct_r01.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_02192026_s1_d3__direct_r02.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02192026_s1_d3__direct_r02.png new file mode 100644 index 0000000..f2d496f Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02192026_s1_d3__direct_r02.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_02192026_s1_d3__direct_r03.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02192026_s1_d3__direct_r03.png new file mode 100644 index 0000000..a311325 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02192026_s1_d3__direct_r03.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_02192026_s1_d3__direct_r04.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02192026_s1_d3__direct_r04.png new file mode 100644 index 0000000..a239f74 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02192026_s1_d3__direct_r04.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_02192026_s1_d3__direct_r05.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02192026_s1_d3__direct_r05.png new file mode 100644 index 0000000..b99eaba Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02192026_s1_d3__direct_r05.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_02202026_s1_d3__direct_r00.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02202026_s1_d3__direct_r00.png new file mode 100644 index 0000000..10db0ce Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02202026_s1_d3__direct_r00.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_02202026_s1_d3__direct_r01.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02202026_s1_d3__direct_r01.png new file mode 100644 index 0000000..c8651b4 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02202026_s1_d3__direct_r01.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_02202026_s1_d3__direct_r02.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02202026_s1_d3__direct_r02.png new file mode 100644 index 0000000..4a90cc2 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02202026_s1_d3__direct_r02.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_02202026_s1_d3__direct_r03.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02202026_s1_d3__direct_r03.png new file mode 100644 index 0000000..e5deac0 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02202026_s1_d3__direct_r03.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_02202026_s1_d3__direct_r04.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02202026_s1_d3__direct_r04.png new file mode 100644 index 0000000..e90d8b3 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02202026_s1_d3__direct_r04.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_02202026_s1_d3__direct_r05.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02202026_s1_d3__direct_r05.png new file mode 100644 index 0000000..dd72333 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02202026_s1_d3__direct_r05.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_02202026_s3_d3__direct_r00.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02202026_s3_d3__direct_r00.png new file mode 100644 index 0000000..cf93a16 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02202026_s3_d3__direct_r00.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_02202026_s3_d3__direct_r01.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02202026_s3_d3__direct_r01.png new file mode 100644 index 0000000..e41e949 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02202026_s3_d3__direct_r01.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_02202026_s3_d3__direct_r02.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02202026_s3_d3__direct_r02.png new file mode 100644 index 0000000..e2bf2f8 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02202026_s3_d3__direct_r02.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_02202026_s3_d3__direct_r03.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02202026_s3_d3__direct_r03.png new file mode 100644 index 0000000..dc914b5 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02202026_s3_d3__direct_r03.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_02202026_s3_d3__direct_r04.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02202026_s3_d3__direct_r04.png new file mode 100644 index 0000000..0893bf0 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02202026_s3_d3__direct_r04.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_02202026_s3_d3__direct_r05.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02202026_s3_d3__direct_r05.png new file mode 100644 index 0000000..542804e Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02202026_s3_d3__direct_r05.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_02242026_s1_d3__direct_r00.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02242026_s1_d3__direct_r00.png new file mode 100644 index 0000000..56b2049 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02242026_s1_d3__direct_r00.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_02242026_s1_d3__direct_r01.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02242026_s1_d3__direct_r01.png new file mode 100644 index 0000000..19c65a7 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02242026_s1_d3__direct_r01.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_02242026_s1_d3__direct_r02.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02242026_s1_d3__direct_r02.png new file mode 100644 index 0000000..e4e61f4 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02242026_s1_d3__direct_r02.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_02242026_s1_d3__direct_r03.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02242026_s1_d3__direct_r03.png new file mode 100644 index 0000000..65df3ef Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02242026_s1_d3__direct_r03.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_02242026_s1_d3__direct_r04.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02242026_s1_d3__direct_r04.png new file mode 100644 index 0000000..9b827e9 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02242026_s1_d3__direct_r04.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_02242026_s1_d3__direct_r05.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02242026_s1_d3__direct_r05.png new file mode 100644 index 0000000..5f1bfec Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02242026_s1_d3__direct_r05.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_02272026_s1_d3__direct_r00.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02272026_s1_d3__direct_r00.png new file mode 100644 index 0000000..4df93e4 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02272026_s1_d3__direct_r00.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_02272026_s1_d3__direct_r01.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02272026_s1_d3__direct_r01.png new file mode 100644 index 0000000..79fe71a Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02272026_s1_d3__direct_r01.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_02272026_s1_d3__direct_r02.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02272026_s1_d3__direct_r02.png new file mode 100644 index 0000000..3578923 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02272026_s1_d3__direct_r02.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_02272026_s1_d3__direct_r03.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02272026_s1_d3__direct_r03.png new file mode 100644 index 0000000..09887d2 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02272026_s1_d3__direct_r03.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_02272026_s1_d3__direct_r04.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02272026_s1_d3__direct_r04.png new file mode 100644 index 0000000..a6a8070 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02272026_s1_d3__direct_r04.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_02272026_s1_d3__direct_r05.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02272026_s1_d3__direct_r05.png new file mode 100644 index 0000000..bcc824b Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_02272026_s1_d3__direct_r05.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_03032026_s1_d3__direct_r00.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_03032026_s1_d3__direct_r00.png new file mode 100644 index 0000000..ba2a2ea Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_03032026_s1_d3__direct_r00.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_03032026_s1_d3__direct_r01.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_03032026_s1_d3__direct_r01.png new file mode 100644 index 0000000..3dc743f Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_03032026_s1_d3__direct_r01.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_03032026_s1_d3__direct_r02.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_03032026_s1_d3__direct_r02.png new file mode 100644 index 0000000..8835843 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_03032026_s1_d3__direct_r02.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_03032026_s1_d3__direct_r03.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_03032026_s1_d3__direct_r03.png new file mode 100644 index 0000000..1b0d5d7 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_03032026_s1_d3__direct_r03.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_03032026_s1_d3__direct_r04.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_03032026_s1_d3__direct_r04.png new file mode 100644 index 0000000..7a5a1cb Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_03032026_s1_d3__direct_r04.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/meter_03032026_s1_d3__direct_r05.png b/backend/data/digit_dataset/sections_synthetic/train/3/meter_03032026_s1_d3__direct_r05.png new file mode 100644 index 0000000..47ee8d2 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/meter_03032026_s1_d3__direct_r05.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00014_s3_d3.png b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00014_s3_d3.png new file mode 100644 index 0000000..7037608 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00014_s3_d3.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00016_s1_d3.png b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00016_s1_d3.png new file mode 100644 index 0000000..a777851 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00016_s1_d3.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00017_s2_d3.png b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00017_s2_d3.png new file mode 100644 index 0000000..a464752 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00017_s2_d3.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00020_s1_d3.png b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00020_s1_d3.png new file mode 100644 index 0000000..11c55f3 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00020_s1_d3.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00036_s3_d3.png b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00036_s3_d3.png new file mode 100644 index 0000000..f67ec8d Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00036_s3_d3.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00040_s3_d3.png b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00040_s3_d3.png new file mode 100644 index 0000000..450b759 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00040_s3_d3.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00041_s1_d3.png b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00041_s1_d3.png new file mode 100644 index 0000000..2281c42 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00041_s1_d3.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00043_s0_d3.png b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00043_s0_d3.png new file mode 100644 index 0000000..de5c81e Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00043_s0_d3.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00043_s3_d3.png b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00043_s3_d3.png new file mode 100644 index 0000000..11de60c Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00043_s3_d3.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00045_s1_d3.png b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00045_s1_d3.png new file mode 100644 index 0000000..e335347 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00045_s1_d3.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00046_s0_d3.png b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00046_s0_d3.png new file mode 100644 index 0000000..6015f88 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00046_s0_d3.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00046_s2_d3.png b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00046_s2_d3.png new file mode 100644 index 0000000..baf6f6b Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00046_s2_d3.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00048_s0_d3.png b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00048_s0_d3.png new file mode 100644 index 0000000..60d9568 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00048_s0_d3.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00049_s1_d3.png b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00049_s1_d3.png new file mode 100644 index 0000000..a888cc9 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00049_s1_d3.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00058_s0_d3.png b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00058_s0_d3.png new file mode 100644 index 0000000..83fe9f1 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00058_s0_d3.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00059_s0_d3.png b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00059_s0_d3.png new file mode 100644 index 0000000..f4fff52 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00059_s0_d3.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00062_s1_d3.png b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00062_s1_d3.png new file mode 100644 index 0000000..f4d7443 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00062_s1_d3.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00064_s0_d3.png b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00064_s0_d3.png new file mode 100644 index 0000000..04bcc72 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00064_s0_d3.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00064_s2_d3.png b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00064_s2_d3.png new file mode 100644 index 0000000..1aeee17 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00064_s2_d3.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00065_s0_d3.png b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00065_s0_d3.png new file mode 100644 index 0000000..ff1a5a5 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00065_s0_d3.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00065_s1_d3.png b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00065_s1_d3.png new file mode 100644 index 0000000..cd20284 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00065_s1_d3.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00067_s0_d3.png b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00067_s0_d3.png new file mode 100644 index 0000000..96ce4d1 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00067_s0_d3.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00071_s0_d3.png b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00071_s0_d3.png new file mode 100644 index 0000000..4a7f859 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00071_s0_d3.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00075_s1_d3.png b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00075_s1_d3.png new file mode 100644 index 0000000..b078623 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00075_s1_d3.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00075_s2_d3.png b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00075_s2_d3.png new file mode 100644 index 0000000..9f8b097 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00075_s2_d3.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00079_s0_d3.png b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00079_s0_d3.png new file mode 100644 index 0000000..9cc93ac Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00079_s0_d3.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00080_s3_d3.png b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00080_s3_d3.png new file mode 100644 index 0000000..8802adc Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00080_s3_d3.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00087_s0_d3.png b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00087_s0_d3.png new file mode 100644 index 0000000..c77beb9 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00087_s0_d3.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00093_s0_d3.png b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00093_s0_d3.png new file mode 100644 index 0000000..2c168df Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00093_s0_d3.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00097_s0_d3.png b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00097_s0_d3.png new file mode 100644 index 0000000..f4edc64 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00097_s0_d3.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00099_s2_d3.png b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00099_s2_d3.png new file mode 100644 index 0000000..c837f7e Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00099_s2_d3.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00100_s2_d3.png b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00100_s2_d3.png new file mode 100644 index 0000000..912a5f6 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00100_s2_d3.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00100_s3_d3.png b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00100_s3_d3.png new file mode 100644 index 0000000..a277cc7 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00100_s3_d3.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00102_s0_d3.png b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00102_s0_d3.png new file mode 100644 index 0000000..734b2dc Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00102_s0_d3.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00105_s0_d3.png b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00105_s0_d3.png new file mode 100644 index 0000000..a69e5ba Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00105_s0_d3.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00109_s1_d3.png b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00109_s1_d3.png new file mode 100644 index 0000000..1d94060 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00109_s1_d3.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00109_s3_d3.png b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00109_s3_d3.png new file mode 100644 index 0000000..87a690e Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00109_s3_d3.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00110_s0_d3.png b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00110_s0_d3.png new file mode 100644 index 0000000..31c739e Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00110_s0_d3.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00111_s1_d3.png b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00111_s1_d3.png new file mode 100644 index 0000000..0169cdb Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00111_s1_d3.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00111_s3_d3.png b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00111_s3_d3.png new file mode 100644 index 0000000..efec4e5 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00111_s3_d3.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00113_s1_d3.png b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00113_s1_d3.png new file mode 100644 index 0000000..983d326 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00113_s1_d3.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00122_s3_d3.png b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00122_s3_d3.png new file mode 100644 index 0000000..33e7163 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00122_s3_d3.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00132_s0_d3.png b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00132_s0_d3.png new file mode 100644 index 0000000..7853380 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00132_s0_d3.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00134_s3_d3.png b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00134_s3_d3.png new file mode 100644 index 0000000..1c1a0e0 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00134_s3_d3.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00142_s0_d3.png b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00142_s0_d3.png new file mode 100644 index 0000000..5ecfc65 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00142_s0_d3.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00144_s0_d3.png b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00144_s0_d3.png new file mode 100644 index 0000000..19d165f Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00144_s0_d3.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00147_s2_d3.png b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00147_s2_d3.png new file mode 100644 index 0000000..dca5316 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00147_s2_d3.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00149_s2_d3.png b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00149_s2_d3.png new file mode 100644 index 0000000..ca9a558 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00149_s2_d3.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00152_s2_d3.png b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00152_s2_d3.png new file mode 100644 index 0000000..68ef16e Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00152_s2_d3.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00158_s1_d3.png b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00158_s1_d3.png new file mode 100644 index 0000000..70bba69 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00158_s1_d3.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00161_s2_d3.png b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00161_s2_d3.png new file mode 100644 index 0000000..5627c37 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00161_s2_d3.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00163_s3_d3.png b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00163_s3_d3.png new file mode 100644 index 0000000..20d2343 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00163_s3_d3.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00164_s0_d3.png b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00164_s0_d3.png new file mode 100644 index 0000000..34a975b Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00164_s0_d3.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00165_s3_d3.png b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00165_s3_d3.png new file mode 100644 index 0000000..7bf259e Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00165_s3_d3.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00166_s0_d3.png b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00166_s0_d3.png new file mode 100644 index 0000000..e131cbc Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00166_s0_d3.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00173_s2_d3.png b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00173_s2_d3.png new file mode 100644 index 0000000..ccb42f5 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00173_s2_d3.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00176_s3_d3.png b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00176_s3_d3.png new file mode 100644 index 0000000..e48c0e7 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00176_s3_d3.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00177_s3_d3.png b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00177_s3_d3.png new file mode 100644 index 0000000..e9a4d5e Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00177_s3_d3.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00178_s0_d3.png b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00178_s0_d3.png new file mode 100644 index 0000000..2276a42 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/3/synthetic_window_00178_s0_d3.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/meter_02242026_s3_d4__direct_r00.png b/backend/data/digit_dataset/sections_synthetic/train/4/meter_02242026_s3_d4__direct_r00.png new file mode 100644 index 0000000..036308e Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/meter_02242026_s3_d4__direct_r00.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/meter_02242026_s3_d4__direct_r01.png b/backend/data/digit_dataset/sections_synthetic/train/4/meter_02242026_s3_d4__direct_r01.png new file mode 100644 index 0000000..d3a9f0d Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/meter_02242026_s3_d4__direct_r01.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/meter_02242026_s3_d4__direct_r02.png b/backend/data/digit_dataset/sections_synthetic/train/4/meter_02242026_s3_d4__direct_r02.png new file mode 100644 index 0000000..c945448 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/meter_02242026_s3_d4__direct_r02.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/meter_02242026_s3_d4__direct_r03.png b/backend/data/digit_dataset/sections_synthetic/train/4/meter_02242026_s3_d4__direct_r03.png new file mode 100644 index 0000000..b3ffe83 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/meter_02242026_s3_d4__direct_r03.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/meter_02242026_s3_d4__direct_r04.png b/backend/data/digit_dataset/sections_synthetic/train/4/meter_02242026_s3_d4__direct_r04.png new file mode 100644 index 0000000..0d2ac1a Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/meter_02242026_s3_d4__direct_r04.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/meter_02242026_s3_d4__direct_r05.png b/backend/data/digit_dataset/sections_synthetic/train/4/meter_02242026_s3_d4__direct_r05.png new file mode 100644 index 0000000..300e46b Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/meter_02242026_s3_d4__direct_r05.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/meter_07012020_s3_d4__direct_r00.png b/backend/data/digit_dataset/sections_synthetic/train/4/meter_07012020_s3_d4__direct_r00.png new file mode 100644 index 0000000..e995271 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/meter_07012020_s3_d4__direct_r00.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/meter_07012020_s3_d4__direct_r01.png b/backend/data/digit_dataset/sections_synthetic/train/4/meter_07012020_s3_d4__direct_r01.png new file mode 100644 index 0000000..221e9b6 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/meter_07012020_s3_d4__direct_r01.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/meter_07012020_s3_d4__direct_r02.png b/backend/data/digit_dataset/sections_synthetic/train/4/meter_07012020_s3_d4__direct_r02.png new file mode 100644 index 0000000..3ae280a Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/meter_07012020_s3_d4__direct_r02.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/meter_07012020_s3_d4__direct_r03.png b/backend/data/digit_dataset/sections_synthetic/train/4/meter_07012020_s3_d4__direct_r03.png new file mode 100644 index 0000000..8482948 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/meter_07012020_s3_d4__direct_r03.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/meter_07012020_s3_d4__direct_r04.png b/backend/data/digit_dataset/sections_synthetic/train/4/meter_07012020_s3_d4__direct_r04.png new file mode 100644 index 0000000..6ccf029 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/meter_07012020_s3_d4__direct_r04.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/meter_07012020_s3_d4__direct_r05.png b/backend/data/digit_dataset/sections_synthetic/train/4/meter_07012020_s3_d4__direct_r05.png new file mode 100644 index 0000000..7a228a5 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/meter_07012020_s3_d4__direct_r05.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00000_s2_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00000_s2_d4.png new file mode 100644 index 0000000..a561dbc Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00000_s2_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00002_s1_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00002_s1_d4.png new file mode 100644 index 0000000..a73f3f5 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00002_s1_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00006_s0_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00006_s0_d4.png new file mode 100644 index 0000000..a85481f Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00006_s0_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00009_s3_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00009_s3_d4.png new file mode 100644 index 0000000..1326647 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00009_s3_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00010_s3_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00010_s3_d4.png new file mode 100644 index 0000000..53561a0 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00010_s3_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00012_s0_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00012_s0_d4.png new file mode 100644 index 0000000..482e7fc Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00012_s0_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00015_s3_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00015_s3_d4.png new file mode 100644 index 0000000..6733245 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00015_s3_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00022_s1_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00022_s1_d4.png new file mode 100644 index 0000000..d3fc6cf Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00022_s1_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00023_s0_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00023_s0_d4.png new file mode 100644 index 0000000..0b587e0 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00023_s0_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00029_s2_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00029_s2_d4.png new file mode 100644 index 0000000..2339434 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00029_s2_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00031_s0_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00031_s0_d4.png new file mode 100644 index 0000000..b74e695 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00031_s0_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00032_s1_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00032_s1_d4.png new file mode 100644 index 0000000..5bb09c3 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00032_s1_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00034_s0_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00034_s0_d4.png new file mode 100644 index 0000000..81f4fd2 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00034_s0_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00040_s2_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00040_s2_d4.png new file mode 100644 index 0000000..10b3c64 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00040_s2_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00045_s0_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00045_s0_d4.png new file mode 100644 index 0000000..9af3e84 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00045_s0_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00047_s3_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00047_s3_d4.png new file mode 100644 index 0000000..eb15f4b Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00047_s3_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00048_s2_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00048_s2_d4.png new file mode 100644 index 0000000..ec096a2 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00048_s2_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00053_s1_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00053_s1_d4.png new file mode 100644 index 0000000..1dc65c6 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00053_s1_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00056_s0_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00056_s0_d4.png new file mode 100644 index 0000000..bc59d31 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00056_s0_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00056_s2_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00056_s2_d4.png new file mode 100644 index 0000000..a19e780 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00056_s2_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00057_s0_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00057_s0_d4.png new file mode 100644 index 0000000..0bf2788 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00057_s0_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00059_s1_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00059_s1_d4.png new file mode 100644 index 0000000..467b812 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00059_s1_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00061_s1_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00061_s1_d4.png new file mode 100644 index 0000000..0b7a9f8 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00061_s1_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00064_s1_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00064_s1_d4.png new file mode 100644 index 0000000..2a5bbfb Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00064_s1_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00065_s2_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00065_s2_d4.png new file mode 100644 index 0000000..f268f71 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00065_s2_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00066_s3_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00066_s3_d4.png new file mode 100644 index 0000000..7a2e665 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00066_s3_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00069_s1_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00069_s1_d4.png new file mode 100644 index 0000000..e6153e9 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00069_s1_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00069_s2_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00069_s2_d4.png new file mode 100644 index 0000000..456798d Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00069_s2_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00070_s0_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00070_s0_d4.png new file mode 100644 index 0000000..9a50951 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00070_s0_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00074_s0_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00074_s0_d4.png new file mode 100644 index 0000000..f64151f Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00074_s0_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00077_s1_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00077_s1_d4.png new file mode 100644 index 0000000..4fe2280 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00077_s1_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00082_s2_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00082_s2_d4.png new file mode 100644 index 0000000..3b7b200 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00082_s2_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00083_s2_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00083_s2_d4.png new file mode 100644 index 0000000..b9241a1 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00083_s2_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00083_s3_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00083_s3_d4.png new file mode 100644 index 0000000..9b9f122 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00083_s3_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00085_s3_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00085_s3_d4.png new file mode 100644 index 0000000..d42bc34 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00085_s3_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00091_s1_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00091_s1_d4.png new file mode 100644 index 0000000..190de24 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00091_s1_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00091_s3_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00091_s3_d4.png new file mode 100644 index 0000000..8ba6de9 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00091_s3_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00093_s3_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00093_s3_d4.png new file mode 100644 index 0000000..94104ac Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00093_s3_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00095_s0_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00095_s0_d4.png new file mode 100644 index 0000000..57871e2 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00095_s0_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00097_s2_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00097_s2_d4.png new file mode 100644 index 0000000..5382243 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00097_s2_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00098_s1_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00098_s1_d4.png new file mode 100644 index 0000000..cf1407e Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00098_s1_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00099_s0_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00099_s0_d4.png new file mode 100644 index 0000000..70fda54 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00099_s0_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00102_s2_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00102_s2_d4.png new file mode 100644 index 0000000..3bd6d6b Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00102_s2_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00104_s3_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00104_s3_d4.png new file mode 100644 index 0000000..8c58538 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00104_s3_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00107_s1_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00107_s1_d4.png new file mode 100644 index 0000000..8520b65 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00107_s1_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00107_s2_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00107_s2_d4.png new file mode 100644 index 0000000..8c05959 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00107_s2_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00110_s1_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00110_s1_d4.png new file mode 100644 index 0000000..310af51 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00110_s1_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00113_s2_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00113_s2_d4.png new file mode 100644 index 0000000..f64a4d0 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00113_s2_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00114_s0_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00114_s0_d4.png new file mode 100644 index 0000000..96db1ea Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00114_s0_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00117_s2_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00117_s2_d4.png new file mode 100644 index 0000000..dfb31bd Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00117_s2_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00117_s3_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00117_s3_d4.png new file mode 100644 index 0000000..c268adf Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00117_s3_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00120_s2_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00120_s2_d4.png new file mode 100644 index 0000000..40e51ea Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00120_s2_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00122_s0_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00122_s0_d4.png new file mode 100644 index 0000000..1c30701 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00122_s0_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00123_s3_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00123_s3_d4.png new file mode 100644 index 0000000..0014f18 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00123_s3_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00124_s0_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00124_s0_d4.png new file mode 100644 index 0000000..23ce5b8 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00124_s0_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00124_s3_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00124_s3_d4.png new file mode 100644 index 0000000..4b8cfdb Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00124_s3_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00126_s1_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00126_s1_d4.png new file mode 100644 index 0000000..a353449 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00126_s1_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00127_s3_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00127_s3_d4.png new file mode 100644 index 0000000..a447fea Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00127_s3_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00128_s3_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00128_s3_d4.png new file mode 100644 index 0000000..bd18232 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00128_s3_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00129_s1_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00129_s1_d4.png new file mode 100644 index 0000000..a79272f Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00129_s1_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00131_s1_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00131_s1_d4.png new file mode 100644 index 0000000..210421f Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00131_s1_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00132_s2_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00132_s2_d4.png new file mode 100644 index 0000000..68f3633 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00132_s2_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00133_s1_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00133_s1_d4.png new file mode 100644 index 0000000..ab739ac Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00133_s1_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00144_s3_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00144_s3_d4.png new file mode 100644 index 0000000..fa1de6a Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00144_s3_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00148_s3_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00148_s3_d4.png new file mode 100644 index 0000000..94c6a2b Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00148_s3_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00153_s1_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00153_s1_d4.png new file mode 100644 index 0000000..3ff1124 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00153_s1_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00158_s0_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00158_s0_d4.png new file mode 100644 index 0000000..002e5ce Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00158_s0_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00159_s2_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00159_s2_d4.png new file mode 100644 index 0000000..8d7580e Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00159_s2_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00163_s1_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00163_s1_d4.png new file mode 100644 index 0000000..0a75144 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00163_s1_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00165_s0_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00165_s0_d4.png new file mode 100644 index 0000000..8db9f39 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00165_s0_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00172_s0_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00172_s0_d4.png new file mode 100644 index 0000000..ce93aa1 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00172_s0_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00178_s1_d4.png b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00178_s1_d4.png new file mode 100644 index 0000000..d13309b Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/4/synthetic_window_00178_s1_d4.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/meter_02272026_s3_d5__direct_r00.png b/backend/data/digit_dataset/sections_synthetic/train/5/meter_02272026_s3_d5__direct_r00.png new file mode 100644 index 0000000..ab2a8ec Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/meter_02272026_s3_d5__direct_r00.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/meter_02272026_s3_d5__direct_r01.png b/backend/data/digit_dataset/sections_synthetic/train/5/meter_02272026_s3_d5__direct_r01.png new file mode 100644 index 0000000..063cdee Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/meter_02272026_s3_d5__direct_r01.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/meter_02272026_s3_d5__direct_r02.png b/backend/data/digit_dataset/sections_synthetic/train/5/meter_02272026_s3_d5__direct_r02.png new file mode 100644 index 0000000..2763a1b Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/meter_02272026_s3_d5__direct_r02.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/meter_02272026_s3_d5__direct_r03.png b/backend/data/digit_dataset/sections_synthetic/train/5/meter_02272026_s3_d5__direct_r03.png new file mode 100644 index 0000000..17909f7 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/meter_02272026_s3_d5__direct_r03.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/meter_02272026_s3_d5__direct_r04.png b/backend/data/digit_dataset/sections_synthetic/train/5/meter_02272026_s3_d5__direct_r04.png new file mode 100644 index 0000000..079cb59 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/meter_02272026_s3_d5__direct_r04.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/meter_02272026_s3_d5__direct_r05.png b/backend/data/digit_dataset/sections_synthetic/train/5/meter_02272026_s3_d5__direct_r05.png new file mode 100644 index 0000000..e633cca Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/meter_02272026_s3_d5__direct_r05.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00006_s3_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00006_s3_d5.png new file mode 100644 index 0000000..f0134ff Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00006_s3_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00007_s1_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00007_s1_d5.png new file mode 100644 index 0000000..be7fa9b Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00007_s1_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00010_s0_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00010_s0_d5.png new file mode 100644 index 0000000..dd6ee7c Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00010_s0_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00011_s2_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00011_s2_d5.png new file mode 100644 index 0000000..e144c94 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00011_s2_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00012_s3_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00012_s3_d5.png new file mode 100644 index 0000000..652ec56 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00012_s3_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00013_s2_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00013_s2_d5.png new file mode 100644 index 0000000..a790f48 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00013_s2_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00016_s0_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00016_s0_d5.png new file mode 100644 index 0000000..66ed954 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00016_s0_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00021_s3_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00021_s3_d5.png new file mode 100644 index 0000000..1647c00 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00021_s3_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00024_s2_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00024_s2_d5.png new file mode 100644 index 0000000..17df2c7 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00024_s2_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00025_s2_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00025_s2_d5.png new file mode 100644 index 0000000..2f5afc6 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00025_s2_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00029_s0_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00029_s0_d5.png new file mode 100644 index 0000000..2b30830 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00029_s0_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00034_s1_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00034_s1_d5.png new file mode 100644 index 0000000..441105d Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00034_s1_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00036_s0_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00036_s0_d5.png new file mode 100644 index 0000000..4b0edbe Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00036_s0_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00036_s2_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00036_s2_d5.png new file mode 100644 index 0000000..a247f95 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00036_s2_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00043_s2_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00043_s2_d5.png new file mode 100644 index 0000000..7dbe9f1 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00043_s2_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00044_s3_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00044_s3_d5.png new file mode 100644 index 0000000..c0b8d44 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00044_s3_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00045_s2_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00045_s2_d5.png new file mode 100644 index 0000000..15870e7 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00045_s2_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00048_s3_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00048_s3_d5.png new file mode 100644 index 0000000..49b3660 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00048_s3_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00058_s3_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00058_s3_d5.png new file mode 100644 index 0000000..e05c9fd Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00058_s3_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00060_s3_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00060_s3_d5.png new file mode 100644 index 0000000..e47d64e Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00060_s3_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00061_s3_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00061_s3_d5.png new file mode 100644 index 0000000..7a97175 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00061_s3_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00063_s0_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00063_s0_d5.png new file mode 100644 index 0000000..cd9af6b Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00063_s0_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00066_s2_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00066_s2_d5.png new file mode 100644 index 0000000..4f0476a Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00066_s2_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00072_s2_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00072_s2_d5.png new file mode 100644 index 0000000..71c9536 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00072_s2_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00073_s2_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00073_s2_d5.png new file mode 100644 index 0000000..15994bf Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00073_s2_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00082_s3_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00082_s3_d5.png new file mode 100644 index 0000000..b628611 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00082_s3_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00088_s3_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00088_s3_d5.png new file mode 100644 index 0000000..ad57b7f Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00088_s3_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00092_s3_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00092_s3_d5.png new file mode 100644 index 0000000..dfbd308 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00092_s3_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00096_s3_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00096_s3_d5.png new file mode 100644 index 0000000..9193e56 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00096_s3_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00102_s3_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00102_s3_d5.png new file mode 100644 index 0000000..cb7e0fe Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00102_s3_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00106_s3_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00106_s3_d5.png new file mode 100644 index 0000000..c339882 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00106_s3_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00112_s0_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00112_s0_d5.png new file mode 100644 index 0000000..d454454 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00112_s0_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00120_s1_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00120_s1_d5.png new file mode 100644 index 0000000..acf72a4 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00120_s1_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00121_s2_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00121_s2_d5.png new file mode 100644 index 0000000..1f1456d Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00121_s2_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00123_s0_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00123_s0_d5.png new file mode 100644 index 0000000..1d60a6c Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00123_s0_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00124_s1_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00124_s1_d5.png new file mode 100644 index 0000000..e4564ec Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00124_s1_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00125_s2_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00125_s2_d5.png new file mode 100644 index 0000000..8c87710 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00125_s2_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00130_s2_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00130_s2_d5.png new file mode 100644 index 0000000..e4500fc Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00130_s2_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00132_s3_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00132_s3_d5.png new file mode 100644 index 0000000..240bde1 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00132_s3_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00133_s0_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00133_s0_d5.png new file mode 100644 index 0000000..52351b3 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00133_s0_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00134_s0_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00134_s0_d5.png new file mode 100644 index 0000000..8f729ba Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00134_s0_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00135_s3_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00135_s3_d5.png new file mode 100644 index 0000000..fc7e7d0 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00135_s3_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00137_s1_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00137_s1_d5.png new file mode 100644 index 0000000..573a46b Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00137_s1_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00137_s2_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00137_s2_d5.png new file mode 100644 index 0000000..f78e6c4 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00137_s2_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00137_s3_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00137_s3_d5.png new file mode 100644 index 0000000..6dea8cf Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00137_s3_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00139_s1_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00139_s1_d5.png new file mode 100644 index 0000000..b7ca893 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00139_s1_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00140_s2_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00140_s2_d5.png new file mode 100644 index 0000000..8166fc8 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00140_s2_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00141_s2_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00141_s2_d5.png new file mode 100644 index 0000000..bf6e24a Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00141_s2_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00144_s1_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00144_s1_d5.png new file mode 100644 index 0000000..7e3419a Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00144_s1_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00146_s1_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00146_s1_d5.png new file mode 100644 index 0000000..85d3b57 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00146_s1_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00150_s0_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00150_s0_d5.png new file mode 100644 index 0000000..bc9d8ad Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00150_s0_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00154_s0_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00154_s0_d5.png new file mode 100644 index 0000000..7a59619 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00154_s0_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00155_s1_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00155_s1_d5.png new file mode 100644 index 0000000..7c988f8 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00155_s1_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00159_s1_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00159_s1_d5.png new file mode 100644 index 0000000..1d265f3 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00159_s1_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00160_s3_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00160_s3_d5.png new file mode 100644 index 0000000..e4de8d8 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00160_s3_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00161_s0_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00161_s0_d5.png new file mode 100644 index 0000000..61d25b3 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00161_s0_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00162_s3_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00162_s3_d5.png new file mode 100644 index 0000000..e49a9ed Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00162_s3_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00169_s3_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00169_s3_d5.png new file mode 100644 index 0000000..cc2def5 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00169_s3_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00173_s1_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00173_s1_d5.png new file mode 100644 index 0000000..1d82ae3 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00173_s1_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00175_s1_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00175_s1_d5.png new file mode 100644 index 0000000..e093386 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00175_s1_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00175_s2_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00175_s2_d5.png new file mode 100644 index 0000000..9710e92 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00175_s2_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00176_s2_d5.png b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00176_s2_d5.png new file mode 100644 index 0000000..e9d6ac0 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/5/synthetic_window_00176_s2_d5.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/meter_03032026_s3_d6__direct_r00.png b/backend/data/digit_dataset/sections_synthetic/train/6/meter_03032026_s3_d6__direct_r00.png new file mode 100644 index 0000000..9c10a73 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/meter_03032026_s3_d6__direct_r00.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/meter_03032026_s3_d6__direct_r01.png b/backend/data/digit_dataset/sections_synthetic/train/6/meter_03032026_s3_d6__direct_r01.png new file mode 100644 index 0000000..24f2566 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/meter_03032026_s3_d6__direct_r01.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/meter_03032026_s3_d6__direct_r02.png b/backend/data/digit_dataset/sections_synthetic/train/6/meter_03032026_s3_d6__direct_r02.png new file mode 100644 index 0000000..487c2f6 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/meter_03032026_s3_d6__direct_r02.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/meter_03032026_s3_d6__direct_r03.png b/backend/data/digit_dataset/sections_synthetic/train/6/meter_03032026_s3_d6__direct_r03.png new file mode 100644 index 0000000..97faf62 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/meter_03032026_s3_d6__direct_r03.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/meter_03032026_s3_d6__direct_r04.png b/backend/data/digit_dataset/sections_synthetic/train/6/meter_03032026_s3_d6__direct_r04.png new file mode 100644 index 0000000..8bff857 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/meter_03032026_s3_d6__direct_r04.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/meter_03032026_s3_d6__direct_r05.png b/backend/data/digit_dataset/sections_synthetic/train/6/meter_03032026_s3_d6__direct_r05.png new file mode 100644 index 0000000..3e5d132 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/meter_03032026_s3_d6__direct_r05.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00001_s2_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00001_s2_d6.png new file mode 100644 index 0000000..2f32761 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00001_s2_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00002_s0_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00002_s0_d6.png new file mode 100644 index 0000000..a928f2a Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00002_s0_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00004_s0_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00004_s0_d6.png new file mode 100644 index 0000000..35d415b Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00004_s0_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00004_s2_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00004_s2_d6.png new file mode 100644 index 0000000..a44cedf Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00004_s2_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00005_s1_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00005_s1_d6.png new file mode 100644 index 0000000..c6227d5 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00005_s1_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00005_s3_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00005_s3_d6.png new file mode 100644 index 0000000..7bf85d3 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00005_s3_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00008_s0_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00008_s0_d6.png new file mode 100644 index 0000000..bf2f274 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00008_s0_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00008_s1_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00008_s1_d6.png new file mode 100644 index 0000000..9699a31 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00008_s1_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00012_s2_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00012_s2_d6.png new file mode 100644 index 0000000..6a4e8e2 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00012_s2_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00016_s2_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00016_s2_d6.png new file mode 100644 index 0000000..d53a570 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00016_s2_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00018_s0_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00018_s0_d6.png new file mode 100644 index 0000000..7cdac39 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00018_s0_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00020_s2_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00020_s2_d6.png new file mode 100644 index 0000000..41caa4c Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00020_s2_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00024_s3_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00024_s3_d6.png new file mode 100644 index 0000000..24329dc Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00024_s3_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00026_s2_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00026_s2_d6.png new file mode 100644 index 0000000..dce7dde Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00026_s2_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00028_s3_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00028_s3_d6.png new file mode 100644 index 0000000..a0ec8bb Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00028_s3_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00030_s0_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00030_s0_d6.png new file mode 100644 index 0000000..66acf7c Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00030_s0_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00031_s1_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00031_s1_d6.png new file mode 100644 index 0000000..d97eb60 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00031_s1_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00033_s0_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00033_s0_d6.png new file mode 100644 index 0000000..03058f4 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00033_s0_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00033_s3_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00033_s3_d6.png new file mode 100644 index 0000000..98905b3 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00033_s3_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00036_s1_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00036_s1_d6.png new file mode 100644 index 0000000..615b152 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00036_s1_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00038_s0_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00038_s0_d6.png new file mode 100644 index 0000000..906f79b Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00038_s0_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00039_s2_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00039_s2_d6.png new file mode 100644 index 0000000..ed5b7f2 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00039_s2_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00041_s3_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00041_s3_d6.png new file mode 100644 index 0000000..7593f9f Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00041_s3_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00045_s3_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00045_s3_d6.png new file mode 100644 index 0000000..f0653bd Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00045_s3_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00049_s0_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00049_s0_d6.png new file mode 100644 index 0000000..cb0ced0 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00049_s0_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00054_s0_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00054_s0_d6.png new file mode 100644 index 0000000..43896b6 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00054_s0_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00054_s1_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00054_s1_d6.png new file mode 100644 index 0000000..4c8d84f Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00054_s1_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00058_s2_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00058_s2_d6.png new file mode 100644 index 0000000..b8ce78e Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00058_s2_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00060_s1_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00060_s1_d6.png new file mode 100644 index 0000000..ff13fb8 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00060_s1_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00062_s2_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00062_s2_d6.png new file mode 100644 index 0000000..b698a89 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00062_s2_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00064_s3_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00064_s3_d6.png new file mode 100644 index 0000000..e013fc8 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00064_s3_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00070_s1_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00070_s1_d6.png new file mode 100644 index 0000000..455c7df Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00070_s1_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00072_s3_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00072_s3_d6.png new file mode 100644 index 0000000..8f36291 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00072_s3_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00074_s1_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00074_s1_d6.png new file mode 100644 index 0000000..b789f3d Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00074_s1_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00074_s2_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00074_s2_d6.png new file mode 100644 index 0000000..caee5bf Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00074_s2_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00079_s2_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00079_s2_d6.png new file mode 100644 index 0000000..b8c34dd Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00079_s2_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00086_s3_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00086_s3_d6.png new file mode 100644 index 0000000..d1b9df2 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00086_s3_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00090_s2_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00090_s2_d6.png new file mode 100644 index 0000000..1917806 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00090_s2_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00092_s2_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00092_s2_d6.png new file mode 100644 index 0000000..cf87fa2 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00092_s2_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00095_s3_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00095_s3_d6.png new file mode 100644 index 0000000..78285d3 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00095_s3_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00108_s1_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00108_s1_d6.png new file mode 100644 index 0000000..4a1a404 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00108_s1_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00124_s2_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00124_s2_d6.png new file mode 100644 index 0000000..af04772 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00124_s2_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00125_s0_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00125_s0_d6.png new file mode 100644 index 0000000..08dd7dd Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00125_s0_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00125_s1_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00125_s1_d6.png new file mode 100644 index 0000000..27b1c6f Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00125_s1_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00127_s0_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00127_s0_d6.png new file mode 100644 index 0000000..944e139 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00127_s0_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00128_s1_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00128_s1_d6.png new file mode 100644 index 0000000..5c2a7c7 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00128_s1_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00129_s2_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00129_s2_d6.png new file mode 100644 index 0000000..dbe73ef Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00129_s2_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00133_s2_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00133_s2_d6.png new file mode 100644 index 0000000..45769ed Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00133_s2_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00134_s1_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00134_s1_d6.png new file mode 100644 index 0000000..cd17baa Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00134_s1_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00135_s1_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00135_s1_d6.png new file mode 100644 index 0000000..389c2fb Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00135_s1_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00138_s0_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00138_s0_d6.png new file mode 100644 index 0000000..9461779 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00138_s0_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00138_s2_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00138_s2_d6.png new file mode 100644 index 0000000..5ceb2fe Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00138_s2_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00138_s3_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00138_s3_d6.png new file mode 100644 index 0000000..1b7878b Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00138_s3_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00143_s1_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00143_s1_d6.png new file mode 100644 index 0000000..c310bce Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00143_s1_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00143_s3_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00143_s3_d6.png new file mode 100644 index 0000000..d31f1ac Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00143_s3_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00148_s0_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00148_s0_d6.png new file mode 100644 index 0000000..8cfd824 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00148_s0_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00148_s2_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00148_s2_d6.png new file mode 100644 index 0000000..92756b3 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00148_s2_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00149_s0_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00149_s0_d6.png new file mode 100644 index 0000000..9372950 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00149_s0_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00150_s2_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00150_s2_d6.png new file mode 100644 index 0000000..e110d49 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00150_s2_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00152_s0_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00152_s0_d6.png new file mode 100644 index 0000000..db2765c Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00152_s0_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00152_s3_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00152_s3_d6.png new file mode 100644 index 0000000..b05108f Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00152_s3_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00155_s0_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00155_s0_d6.png new file mode 100644 index 0000000..0b8d349 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00155_s0_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00156_s2_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00156_s2_d6.png new file mode 100644 index 0000000..91f02ab Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00156_s2_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00160_s2_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00160_s2_d6.png new file mode 100644 index 0000000..1d675fc Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00160_s2_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00161_s3_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00161_s3_d6.png new file mode 100644 index 0000000..8e8a6ff Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00161_s3_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00162_s0_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00162_s0_d6.png new file mode 100644 index 0000000..e72e5f3 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00162_s0_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00164_s1_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00164_s1_d6.png new file mode 100644 index 0000000..4ee4669 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00164_s1_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00167_s0_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00167_s0_d6.png new file mode 100644 index 0000000..f938649 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00167_s0_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00167_s2_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00167_s2_d6.png new file mode 100644 index 0000000..6f6f330 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00167_s2_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00174_s1_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00174_s1_d6.png new file mode 100644 index 0000000..33d7050 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00174_s1_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00174_s2_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00174_s2_d6.png new file mode 100644 index 0000000..0bae3ed Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00174_s2_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00176_s0_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00176_s0_d6.png new file mode 100644 index 0000000..61bfb09 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00176_s0_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00176_s1_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00176_s1_d6.png new file mode 100644 index 0000000..faead2f Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00176_s1_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00178_s2_d6.png b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00178_s2_d6.png new file mode 100644 index 0000000..5f6eb9c Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/6/synthetic_window_00178_s2_d6.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/meter_01302026_s3_d7__direct_r00.png b/backend/data/digit_dataset/sections_synthetic/train/7/meter_01302026_s3_d7__direct_r00.png new file mode 100644 index 0000000..a982b01 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/meter_01302026_s3_d7__direct_r00.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/meter_01302026_s3_d7__direct_r01.png b/backend/data/digit_dataset/sections_synthetic/train/7/meter_01302026_s3_d7__direct_r01.png new file mode 100644 index 0000000..a3d024d Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/meter_01302026_s3_d7__direct_r01.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/meter_01302026_s3_d7__direct_r02.png b/backend/data/digit_dataset/sections_synthetic/train/7/meter_01302026_s3_d7__direct_r02.png new file mode 100644 index 0000000..b1cb716 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/meter_01302026_s3_d7__direct_r02.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/meter_01302026_s3_d7__direct_r03.png b/backend/data/digit_dataset/sections_synthetic/train/7/meter_01302026_s3_d7__direct_r03.png new file mode 100644 index 0000000..cb06725 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/meter_01302026_s3_d7__direct_r03.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/meter_01302026_s3_d7__direct_r04.png b/backend/data/digit_dataset/sections_synthetic/train/7/meter_01302026_s3_d7__direct_r04.png new file mode 100644 index 0000000..ceab2d4 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/meter_01302026_s3_d7__direct_r04.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/meter_01302026_s3_d7__direct_r05.png b/backend/data/digit_dataset/sections_synthetic/train/7/meter_01302026_s3_d7__direct_r05.png new file mode 100644 index 0000000..6c4fdcb Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/meter_01302026_s3_d7__direct_r05.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/meter_07012020_s1_d7__direct_r00.png b/backend/data/digit_dataset/sections_synthetic/train/7/meter_07012020_s1_d7__direct_r00.png new file mode 100644 index 0000000..c3ffce5 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/meter_07012020_s1_d7__direct_r00.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/meter_07012020_s1_d7__direct_r01.png b/backend/data/digit_dataset/sections_synthetic/train/7/meter_07012020_s1_d7__direct_r01.png new file mode 100644 index 0000000..bec8ce6 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/meter_07012020_s1_d7__direct_r01.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/meter_07012020_s1_d7__direct_r02.png b/backend/data/digit_dataset/sections_synthetic/train/7/meter_07012020_s1_d7__direct_r02.png new file mode 100644 index 0000000..39a458c Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/meter_07012020_s1_d7__direct_r02.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/meter_07012020_s1_d7__direct_r03.png b/backend/data/digit_dataset/sections_synthetic/train/7/meter_07012020_s1_d7__direct_r03.png new file mode 100644 index 0000000..d829abd Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/meter_07012020_s1_d7__direct_r03.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/meter_07012020_s1_d7__direct_r04.png b/backend/data/digit_dataset/sections_synthetic/train/7/meter_07012020_s1_d7__direct_r04.png new file mode 100644 index 0000000..cf7680d Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/meter_07012020_s1_d7__direct_r04.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/meter_07012020_s1_d7__direct_r05.png b/backend/data/digit_dataset/sections_synthetic/train/7/meter_07012020_s1_d7__direct_r05.png new file mode 100644 index 0000000..37c62a0 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/meter_07012020_s1_d7__direct_r05.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/meter_10092025_s2_d7__direct_r00.png b/backend/data/digit_dataset/sections_synthetic/train/7/meter_10092025_s2_d7__direct_r00.png new file mode 100644 index 0000000..1f9bfd9 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/meter_10092025_s2_d7__direct_r00.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/meter_10092025_s2_d7__direct_r01.png b/backend/data/digit_dataset/sections_synthetic/train/7/meter_10092025_s2_d7__direct_r01.png new file mode 100644 index 0000000..9ab2766 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/meter_10092025_s2_d7__direct_r01.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/meter_10092025_s2_d7__direct_r02.png b/backend/data/digit_dataset/sections_synthetic/train/7/meter_10092025_s2_d7__direct_r02.png new file mode 100644 index 0000000..ce12b05 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/meter_10092025_s2_d7__direct_r02.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/meter_10092025_s2_d7__direct_r03.png b/backend/data/digit_dataset/sections_synthetic/train/7/meter_10092025_s2_d7__direct_r03.png new file mode 100644 index 0000000..5767f08 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/meter_10092025_s2_d7__direct_r03.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/meter_10092025_s2_d7__direct_r04.png b/backend/data/digit_dataset/sections_synthetic/train/7/meter_10092025_s2_d7__direct_r04.png new file mode 100644 index 0000000..a2887c6 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/meter_10092025_s2_d7__direct_r04.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/meter_10092025_s2_d7__direct_r05.png b/backend/data/digit_dataset/sections_synthetic/train/7/meter_10092025_s2_d7__direct_r05.png new file mode 100644 index 0000000..eec439f Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/meter_10092025_s2_d7__direct_r05.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00001_s0_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00001_s0_d7.png new file mode 100644 index 0000000..9032da7 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00001_s0_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00001_s3_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00001_s3_d7.png new file mode 100644 index 0000000..d3e9871 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00001_s3_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00005_s2_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00005_s2_d7.png new file mode 100644 index 0000000..3012f9c Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00005_s2_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00006_s1_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00006_s1_d7.png new file mode 100644 index 0000000..dee9902 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00006_s1_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00007_s2_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00007_s2_d7.png new file mode 100644 index 0000000..1f01044 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00007_s2_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00013_s3_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00013_s3_d7.png new file mode 100644 index 0000000..d14e13e Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00013_s3_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00014_s1_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00014_s1_d7.png new file mode 100644 index 0000000..3f3aceb Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00014_s1_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00015_s0_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00015_s0_d7.png new file mode 100644 index 0000000..9cb236d Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00015_s0_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00015_s1_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00015_s1_d7.png new file mode 100644 index 0000000..6fc824f Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00015_s1_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00019_s0_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00019_s0_d7.png new file mode 100644 index 0000000..19a11b7 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00019_s0_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00019_s1_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00019_s1_d7.png new file mode 100644 index 0000000..99e2f0c Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00019_s1_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00020_s0_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00020_s0_d7.png new file mode 100644 index 0000000..e299266 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00020_s0_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00021_s0_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00021_s0_d7.png new file mode 100644 index 0000000..fe89b6d Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00021_s0_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00021_s2_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00021_s2_d7.png new file mode 100644 index 0000000..29c3e95 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00021_s2_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00023_s3_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00023_s3_d7.png new file mode 100644 index 0000000..53426f7 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00023_s3_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00024_s0_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00024_s0_d7.png new file mode 100644 index 0000000..bc99d00 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00024_s0_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00025_s1_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00025_s1_d7.png new file mode 100644 index 0000000..1a53555 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00025_s1_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00031_s3_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00031_s3_d7.png new file mode 100644 index 0000000..a96ab42 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00031_s3_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00033_s1_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00033_s1_d7.png new file mode 100644 index 0000000..6c36448 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00033_s1_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00035_s1_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00035_s1_d7.png new file mode 100644 index 0000000..b59b546 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00035_s1_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00040_s1_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00040_s1_d7.png new file mode 100644 index 0000000..2fcc99e Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00040_s1_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00043_s1_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00043_s1_d7.png new file mode 100644 index 0000000..7530f85 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00043_s1_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00047_s1_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00047_s1_d7.png new file mode 100644 index 0000000..4c37dcd Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00047_s1_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00050_s3_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00050_s3_d7.png new file mode 100644 index 0000000..32da21f Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00050_s3_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00052_s2_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00052_s2_d7.png new file mode 100644 index 0000000..e279d0a Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00052_s2_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00055_s0_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00055_s0_d7.png new file mode 100644 index 0000000..9e6c74f Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00055_s0_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00055_s3_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00055_s3_d7.png new file mode 100644 index 0000000..cd90315 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00055_s3_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00056_s1_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00056_s1_d7.png new file mode 100644 index 0000000..c456bfe Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00056_s1_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00059_s2_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00059_s2_d7.png new file mode 100644 index 0000000..203fb4c Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00059_s2_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00067_s3_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00067_s3_d7.png new file mode 100644 index 0000000..2b00d6e Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00067_s3_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00077_s2_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00077_s2_d7.png new file mode 100644 index 0000000..6068a34 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00077_s2_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00077_s3_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00077_s3_d7.png new file mode 100644 index 0000000..f323ea8 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00077_s3_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00079_s1_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00079_s1_d7.png new file mode 100644 index 0000000..81ad144 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00079_s1_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00081_s3_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00081_s3_d7.png new file mode 100644 index 0000000..78c8121 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00081_s3_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00082_s0_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00082_s0_d7.png new file mode 100644 index 0000000..ad2f471 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00082_s0_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00084_s0_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00084_s0_d7.png new file mode 100644 index 0000000..672c34a Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00084_s0_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00087_s2_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00087_s2_d7.png new file mode 100644 index 0000000..584f8d9 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00087_s2_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00088_s0_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00088_s0_d7.png new file mode 100644 index 0000000..aa132ea Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00088_s0_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00088_s2_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00088_s2_d7.png new file mode 100644 index 0000000..d29688e Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00088_s2_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00091_s2_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00091_s2_d7.png new file mode 100644 index 0000000..dad98de Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00091_s2_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00092_s0_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00092_s0_d7.png new file mode 100644 index 0000000..812e7bd Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00092_s0_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00094_s3_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00094_s3_d7.png new file mode 100644 index 0000000..f6c79ad Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00094_s3_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00096_s0_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00096_s0_d7.png new file mode 100644 index 0000000..a1d461e Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00096_s0_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00101_s0_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00101_s0_d7.png new file mode 100644 index 0000000..ab20ffa Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00101_s0_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00101_s2_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00101_s2_d7.png new file mode 100644 index 0000000..82ff117 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00101_s2_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00103_s2_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00103_s2_d7.png new file mode 100644 index 0000000..247d1ed Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00103_s2_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00103_s3_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00103_s3_d7.png new file mode 100644 index 0000000..25accc9 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00103_s3_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00104_s1_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00104_s1_d7.png new file mode 100644 index 0000000..ba87be4 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00104_s1_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00104_s2_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00104_s2_d7.png new file mode 100644 index 0000000..84cec5b Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00104_s2_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00108_s0_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00108_s0_d7.png new file mode 100644 index 0000000..7bc71ae Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00108_s0_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00110_s3_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00110_s3_d7.png new file mode 100644 index 0000000..ee6a1b6 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00110_s3_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00112_s3_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00112_s3_d7.png new file mode 100644 index 0000000..c56ebb9 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00112_s3_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00115_s0_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00115_s0_d7.png new file mode 100644 index 0000000..d54e784 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00115_s0_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00116_s0_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00116_s0_d7.png new file mode 100644 index 0000000..f053a25 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00116_s0_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00118_s1_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00118_s1_d7.png new file mode 100644 index 0000000..dfcab29 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00118_s1_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00121_s1_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00121_s1_d7.png new file mode 100644 index 0000000..86c9613 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00121_s1_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00129_s3_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00129_s3_d7.png new file mode 100644 index 0000000..6accbc9 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00129_s3_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00130_s1_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00130_s1_d7.png new file mode 100644 index 0000000..9911590 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00130_s1_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00132_s1_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00132_s1_d7.png new file mode 100644 index 0000000..571236e Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00132_s1_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00134_s2_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00134_s2_d7.png new file mode 100644 index 0000000..dfae589 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00134_s2_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00135_s0_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00135_s0_d7.png new file mode 100644 index 0000000..a00b81f Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00135_s0_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00135_s2_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00135_s2_d7.png new file mode 100644 index 0000000..168a732 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00135_s2_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00136_s1_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00136_s1_d7.png new file mode 100644 index 0000000..e4f6d77 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00136_s1_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00139_s0_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00139_s0_d7.png new file mode 100644 index 0000000..2b51046 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00139_s0_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00140_s3_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00140_s3_d7.png new file mode 100644 index 0000000..e535962 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00140_s3_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00142_s3_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00142_s3_d7.png new file mode 100644 index 0000000..a621473 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00142_s3_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00143_s0_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00143_s0_d7.png new file mode 100644 index 0000000..b9a89b8 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00143_s0_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00145_s3_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00145_s3_d7.png new file mode 100644 index 0000000..95bb69a Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00145_s3_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00151_s3_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00151_s3_d7.png new file mode 100644 index 0000000..be5f97e Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00151_s3_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00153_s0_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00153_s0_d7.png new file mode 100644 index 0000000..3ea82ac Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00153_s0_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00155_s2_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00155_s2_d7.png new file mode 100644 index 0000000..3c762ee Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00155_s2_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00156_s0_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00156_s0_d7.png new file mode 100644 index 0000000..c556ac4 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00156_s0_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00156_s1_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00156_s1_d7.png new file mode 100644 index 0000000..81bc3c1 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00156_s1_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00160_s1_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00160_s1_d7.png new file mode 100644 index 0000000..2517f8c Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00160_s1_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00163_s2_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00163_s2_d7.png new file mode 100644 index 0000000..e9d28a6 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00163_s2_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00168_s2_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00168_s2_d7.png new file mode 100644 index 0000000..deca4b1 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00168_s2_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00168_s3_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00168_s3_d7.png new file mode 100644 index 0000000..e9d5162 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00168_s3_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00169_s2_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00169_s2_d7.png new file mode 100644 index 0000000..cec294b Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00169_s2_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00171_s2_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00171_s2_d7.png new file mode 100644 index 0000000..72735ae Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00171_s2_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00179_s0_d7.png b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00179_s0_d7.png new file mode 100644 index 0000000..4804f92 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/7/synthetic_window_00179_s0_d7.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/meter_07012020_s2_d8__direct_r00.png b/backend/data/digit_dataset/sections_synthetic/train/8/meter_07012020_s2_d8__direct_r00.png new file mode 100644 index 0000000..1b8401b Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/meter_07012020_s2_d8__direct_r00.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/meter_07012020_s2_d8__direct_r01.png b/backend/data/digit_dataset/sections_synthetic/train/8/meter_07012020_s2_d8__direct_r01.png new file mode 100644 index 0000000..af22d5f Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/meter_07012020_s2_d8__direct_r01.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/meter_07012020_s2_d8__direct_r02.png b/backend/data/digit_dataset/sections_synthetic/train/8/meter_07012020_s2_d8__direct_r02.png new file mode 100644 index 0000000..2fac05f Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/meter_07012020_s2_d8__direct_r02.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/meter_07012020_s2_d8__direct_r03.png b/backend/data/digit_dataset/sections_synthetic/train/8/meter_07012020_s2_d8__direct_r03.png new file mode 100644 index 0000000..9c1b0d1 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/meter_07012020_s2_d8__direct_r03.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/meter_07012020_s2_d8__direct_r04.png b/backend/data/digit_dataset/sections_synthetic/train/8/meter_07012020_s2_d8__direct_r04.png new file mode 100644 index 0000000..07aa2a3 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/meter_07012020_s2_d8__direct_r04.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/meter_07012020_s2_d8__direct_r05.png b/backend/data/digit_dataset/sections_synthetic/train/8/meter_07012020_s2_d8__direct_r05.png new file mode 100644 index 0000000..af4816d Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/meter_07012020_s2_d8__direct_r05.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/meter_11112020_s1_d8__direct_r00.png b/backend/data/digit_dataset/sections_synthetic/train/8/meter_11112020_s1_d8__direct_r00.png new file mode 100644 index 0000000..a098fc8 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/meter_11112020_s1_d8__direct_r00.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/meter_11112020_s1_d8__direct_r01.png b/backend/data/digit_dataset/sections_synthetic/train/8/meter_11112020_s1_d8__direct_r01.png new file mode 100644 index 0000000..3c2dfe3 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/meter_11112020_s1_d8__direct_r01.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/meter_11112020_s1_d8__direct_r02.png b/backend/data/digit_dataset/sections_synthetic/train/8/meter_11112020_s1_d8__direct_r02.png new file mode 100644 index 0000000..c7071ff Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/meter_11112020_s1_d8__direct_r02.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/meter_11112020_s1_d8__direct_r03.png b/backend/data/digit_dataset/sections_synthetic/train/8/meter_11112020_s1_d8__direct_r03.png new file mode 100644 index 0000000..bc83b3e Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/meter_11112020_s1_d8__direct_r03.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/meter_11112020_s1_d8__direct_r04.png b/backend/data/digit_dataset/sections_synthetic/train/8/meter_11112020_s1_d8__direct_r04.png new file mode 100644 index 0000000..a749ac7 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/meter_11112020_s1_d8__direct_r04.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/meter_11112020_s1_d8__direct_r05.png b/backend/data/digit_dataset/sections_synthetic/train/8/meter_11112020_s1_d8__direct_r05.png new file mode 100644 index 0000000..7f9bb29 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/meter_11112020_s1_d8__direct_r05.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00000_s0_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00000_s0_d8.png new file mode 100644 index 0000000..a20cf87 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00000_s0_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00001_s1_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00001_s1_d8.png new file mode 100644 index 0000000..9be0e39 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00001_s1_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00003_s3_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00003_s3_d8.png new file mode 100644 index 0000000..dce58c4 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00003_s3_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00004_s1_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00004_s1_d8.png new file mode 100644 index 0000000..35dc747 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00004_s1_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00007_s0_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00007_s0_d8.png new file mode 100644 index 0000000..00a1a4e Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00007_s0_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00008_s2_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00008_s2_d8.png new file mode 100644 index 0000000..1f98dcd Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00008_s2_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00019_s2_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00019_s2_d8.png new file mode 100644 index 0000000..b5fc842 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00019_s2_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00020_s3_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00020_s3_d8.png new file mode 100644 index 0000000..4448330 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00020_s3_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00023_s1_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00023_s1_d8.png new file mode 100644 index 0000000..377a394 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00023_s1_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00024_s1_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00024_s1_d8.png new file mode 100644 index 0000000..c096fcc Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00024_s1_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00027_s2_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00027_s2_d8.png new file mode 100644 index 0000000..90a03c5 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00027_s2_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00033_s2_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00033_s2_d8.png new file mode 100644 index 0000000..c139c05 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00033_s2_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00035_s0_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00035_s0_d8.png new file mode 100644 index 0000000..bb42d64 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00035_s0_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00037_s2_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00037_s2_d8.png new file mode 100644 index 0000000..ac60ec9 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00037_s2_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00038_s1_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00038_s1_d8.png new file mode 100644 index 0000000..a40167a Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00038_s1_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00040_s0_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00040_s0_d8.png new file mode 100644 index 0000000..6d0ec52 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00040_s0_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00042_s3_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00042_s3_d8.png new file mode 100644 index 0000000..7b2d775 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00042_s3_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00044_s0_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00044_s0_d8.png new file mode 100644 index 0000000..6a2a03b Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00044_s0_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00046_s3_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00046_s3_d8.png new file mode 100644 index 0000000..45e647a Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00046_s3_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00047_s0_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00047_s0_d8.png new file mode 100644 index 0000000..fee255d Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00047_s0_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00051_s2_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00051_s2_d8.png new file mode 100644 index 0000000..b88f1fa Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00051_s2_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00052_s1_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00052_s1_d8.png new file mode 100644 index 0000000..dfeed3d Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00052_s1_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00054_s3_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00054_s3_d8.png new file mode 100644 index 0000000..8e12df3 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00054_s3_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00059_s3_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00059_s3_d8.png new file mode 100644 index 0000000..bae2883 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00059_s3_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00075_s0_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00075_s0_d8.png new file mode 100644 index 0000000..920920d Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00075_s0_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00076_s3_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00076_s3_d8.png new file mode 100644 index 0000000..b5e2911 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00076_s3_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00077_s0_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00077_s0_d8.png new file mode 100644 index 0000000..2165fc5 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00077_s0_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00085_s1_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00085_s1_d8.png new file mode 100644 index 0000000..96787b7 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00085_s1_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00086_s2_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00086_s2_d8.png new file mode 100644 index 0000000..56f8b56 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00086_s2_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00088_s1_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00088_s1_d8.png new file mode 100644 index 0000000..1992b92 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00088_s1_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00089_s3_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00089_s3_d8.png new file mode 100644 index 0000000..85896f1 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00089_s3_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00093_s2_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00093_s2_d8.png new file mode 100644 index 0000000..3bba4e7 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00093_s2_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00095_s2_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00095_s2_d8.png new file mode 100644 index 0000000..4feea2a Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00095_s2_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00096_s2_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00096_s2_d8.png new file mode 100644 index 0000000..8ef96e1 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00096_s2_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00102_s1_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00102_s1_d8.png new file mode 100644 index 0000000..ced9b22 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00102_s1_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00103_s1_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00103_s1_d8.png new file mode 100644 index 0000000..e138cca Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00103_s1_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00105_s2_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00105_s2_d8.png new file mode 100644 index 0000000..f3cd17b Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00105_s2_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00106_s1_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00106_s1_d8.png new file mode 100644 index 0000000..a06957d Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00106_s1_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00106_s2_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00106_s2_d8.png new file mode 100644 index 0000000..25ad6b2 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00106_s2_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00109_s2_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00109_s2_d8.png new file mode 100644 index 0000000..f346fc8 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00109_s2_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00115_s1_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00115_s1_d8.png new file mode 100644 index 0000000..9a7cb12 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00115_s1_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00116_s1_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00116_s1_d8.png new file mode 100644 index 0000000..fa912c9 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00116_s1_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00116_s3_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00116_s3_d8.png new file mode 100644 index 0000000..2e8a8bd Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00116_s3_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00117_s0_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00117_s0_d8.png new file mode 100644 index 0000000..3377f2d Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00117_s0_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00120_s0_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00120_s0_d8.png new file mode 100644 index 0000000..67e91cf Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00120_s0_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00131_s2_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00131_s2_d8.png new file mode 100644 index 0000000..9dd6770 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00131_s2_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00136_s3_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00136_s3_d8.png new file mode 100644 index 0000000..4bfd862 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00136_s3_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00141_s3_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00141_s3_d8.png new file mode 100644 index 0000000..4acd752 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00141_s3_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00142_s2_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00142_s2_d8.png new file mode 100644 index 0000000..36d8af6 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00142_s2_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00148_s1_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00148_s1_d8.png new file mode 100644 index 0000000..3bc8c77 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00148_s1_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00151_s2_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00151_s2_d8.png new file mode 100644 index 0000000..f00f0d2 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00151_s2_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00153_s3_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00153_s3_d8.png new file mode 100644 index 0000000..17727a6 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00153_s3_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00155_s3_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00155_s3_d8.png new file mode 100644 index 0000000..c818e40 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00155_s3_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00156_s3_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00156_s3_d8.png new file mode 100644 index 0000000..673974c Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00156_s3_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00157_s0_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00157_s0_d8.png new file mode 100644 index 0000000..fbd4647 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00157_s0_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00157_s2_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00157_s2_d8.png new file mode 100644 index 0000000..974773b Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00157_s2_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00157_s3_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00157_s3_d8.png new file mode 100644 index 0000000..eb5b355 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00157_s3_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00158_s2_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00158_s2_d8.png new file mode 100644 index 0000000..6f45daa Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00158_s2_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00163_s0_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00163_s0_d8.png new file mode 100644 index 0000000..7f65b00 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00163_s0_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00166_s3_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00166_s3_d8.png new file mode 100644 index 0000000..d571182 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00166_s3_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00167_s3_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00167_s3_d8.png new file mode 100644 index 0000000..7e46504 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00167_s3_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00168_s1_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00168_s1_d8.png new file mode 100644 index 0000000..2756211 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00168_s1_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00169_s1_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00169_s1_d8.png new file mode 100644 index 0000000..291c9c0 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00169_s1_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00170_s2_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00170_s2_d8.png new file mode 100644 index 0000000..dc49f22 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00170_s2_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00171_s1_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00171_s1_d8.png new file mode 100644 index 0000000..2b58c97 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00171_s1_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00178_s3_d8.png b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00178_s3_d8.png new file mode 100644 index 0000000..33a94c2 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/8/synthetic_window_00178_s3_d8.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/meter_10092025_s3_d9__direct_r00.png b/backend/data/digit_dataset/sections_synthetic/train/9/meter_10092025_s3_d9__direct_r00.png new file mode 100644 index 0000000..96212d4 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/meter_10092025_s3_d9__direct_r00.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/meter_10092025_s3_d9__direct_r01.png b/backend/data/digit_dataset/sections_synthetic/train/9/meter_10092025_s3_d9__direct_r01.png new file mode 100644 index 0000000..da0f10a Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/meter_10092025_s3_d9__direct_r01.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/meter_10092025_s3_d9__direct_r02.png b/backend/data/digit_dataset/sections_synthetic/train/9/meter_10092025_s3_d9__direct_r02.png new file mode 100644 index 0000000..71a1ce7 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/meter_10092025_s3_d9__direct_r02.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/meter_10092025_s3_d9__direct_r03.png b/backend/data/digit_dataset/sections_synthetic/train/9/meter_10092025_s3_d9__direct_r03.png new file mode 100644 index 0000000..22fa76d Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/meter_10092025_s3_d9__direct_r03.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/meter_10092025_s3_d9__direct_r04.png b/backend/data/digit_dataset/sections_synthetic/train/9/meter_10092025_s3_d9__direct_r04.png new file mode 100644 index 0000000..71020f6 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/meter_10092025_s3_d9__direct_r04.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/meter_10092025_s3_d9__direct_r05.png b/backend/data/digit_dataset/sections_synthetic/train/9/meter_10092025_s3_d9__direct_r05.png new file mode 100644 index 0000000..4bf4496 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/meter_10092025_s3_d9__direct_r05.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/meter_11112020_s3_d9__direct_r00.png b/backend/data/digit_dataset/sections_synthetic/train/9/meter_11112020_s3_d9__direct_r00.png new file mode 100644 index 0000000..87f64e9 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/meter_11112020_s3_d9__direct_r00.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/meter_11112020_s3_d9__direct_r01.png b/backend/data/digit_dataset/sections_synthetic/train/9/meter_11112020_s3_d9__direct_r01.png new file mode 100644 index 0000000..f1dd665 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/meter_11112020_s3_d9__direct_r01.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/meter_11112020_s3_d9__direct_r02.png b/backend/data/digit_dataset/sections_synthetic/train/9/meter_11112020_s3_d9__direct_r02.png new file mode 100644 index 0000000..97db8be Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/meter_11112020_s3_d9__direct_r02.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/meter_11112020_s3_d9__direct_r03.png b/backend/data/digit_dataset/sections_synthetic/train/9/meter_11112020_s3_d9__direct_r03.png new file mode 100644 index 0000000..02b65a5 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/meter_11112020_s3_d9__direct_r03.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/meter_11112020_s3_d9__direct_r04.png b/backend/data/digit_dataset/sections_synthetic/train/9/meter_11112020_s3_d9__direct_r04.png new file mode 100644 index 0000000..733452f Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/meter_11112020_s3_d9__direct_r04.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/meter_11112020_s3_d9__direct_r05.png b/backend/data/digit_dataset/sections_synthetic/train/9/meter_11112020_s3_d9__direct_r05.png new file mode 100644 index 0000000..66a6118 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/meter_11112020_s3_d9__direct_r05.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00000_s1_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00000_s1_d9.png new file mode 100644 index 0000000..3a0f59e Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00000_s1_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00003_s2_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00003_s2_d9.png new file mode 100644 index 0000000..6c92165 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00003_s2_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00014_s2_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00014_s2_d9.png new file mode 100644 index 0000000..ab181af Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00014_s2_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00016_s3_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00016_s3_d9.png new file mode 100644 index 0000000..2eb492f Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00016_s3_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00017_s0_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00017_s0_d9.png new file mode 100644 index 0000000..069b7ed Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00017_s0_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00017_s3_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00017_s3_d9.png new file mode 100644 index 0000000..9e41ac2 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00017_s3_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00022_s0_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00022_s0_d9.png new file mode 100644 index 0000000..1cffe3f Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00022_s0_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00025_s0_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00025_s0_d9.png new file mode 100644 index 0000000..6d9e04d Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00025_s0_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00038_s2_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00038_s2_d9.png new file mode 100644 index 0000000..a291732 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00038_s2_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00042_s1_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00042_s1_d9.png new file mode 100644 index 0000000..3a60984 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00042_s1_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00049_s2_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00049_s2_d9.png new file mode 100644 index 0000000..64a2197 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00049_s2_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00050_s1_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00050_s1_d9.png new file mode 100644 index 0000000..0346f55 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00050_s1_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00050_s2_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00050_s2_d9.png new file mode 100644 index 0000000..d839208 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00050_s2_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00053_s0_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00053_s0_d9.png new file mode 100644 index 0000000..0b298dd Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00053_s0_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00053_s2_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00053_s2_d9.png new file mode 100644 index 0000000..e4eb00f Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00053_s2_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00056_s3_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00056_s3_d9.png new file mode 100644 index 0000000..d9525d6 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00056_s3_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00057_s3_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00057_s3_d9.png new file mode 100644 index 0000000..de405bd Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00057_s3_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00060_s0_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00060_s0_d9.png new file mode 100644 index 0000000..9023f2d Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00060_s0_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00061_s0_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00061_s0_d9.png new file mode 100644 index 0000000..de9c0e8 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00061_s0_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00061_s2_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00061_s2_d9.png new file mode 100644 index 0000000..d796c3e Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00061_s2_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00065_s3_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00065_s3_d9.png new file mode 100644 index 0000000..62f647c Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00065_s3_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00067_s2_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00067_s2_d9.png new file mode 100644 index 0000000..63687b3 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00067_s2_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00068_s2_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00068_s2_d9.png new file mode 100644 index 0000000..fbc0451 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00068_s2_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00073_s3_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00073_s3_d9.png new file mode 100644 index 0000000..1d9d9d2 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00073_s3_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00075_s3_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00075_s3_d9.png new file mode 100644 index 0000000..b505328 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00075_s3_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00076_s2_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00076_s2_d9.png new file mode 100644 index 0000000..b21f882 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00076_s2_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00078_s0_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00078_s0_d9.png new file mode 100644 index 0000000..fa36747 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00078_s0_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00078_s2_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00078_s2_d9.png new file mode 100644 index 0000000..23d8b4b Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00078_s2_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00080_s0_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00080_s0_d9.png new file mode 100644 index 0000000..b3655db Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00080_s0_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00080_s1_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00080_s1_d9.png new file mode 100644 index 0000000..6e8195c Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00080_s1_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00080_s2_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00080_s2_d9.png new file mode 100644 index 0000000..9e1e5d8 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00080_s2_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00081_s1_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00081_s1_d9.png new file mode 100644 index 0000000..11cd776 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00081_s1_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00081_s2_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00081_s2_d9.png new file mode 100644 index 0000000..8f463c2 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00081_s2_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00083_s1_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00083_s1_d9.png new file mode 100644 index 0000000..e26bbf3 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00083_s1_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00084_s2_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00084_s2_d9.png new file mode 100644 index 0000000..8e92c10 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00084_s2_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00084_s3_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00084_s3_d9.png new file mode 100644 index 0000000..115f8d3 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00084_s3_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00086_s1_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00086_s1_d9.png new file mode 100644 index 0000000..12c1ba3 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00086_s1_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00087_s3_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00087_s3_d9.png new file mode 100644 index 0000000..85049fa Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00087_s3_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00090_s0_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00090_s0_d9.png new file mode 100644 index 0000000..9f0db97 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00090_s0_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00091_s0_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00091_s0_d9.png new file mode 100644 index 0000000..eeda0c7 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00091_s0_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00094_s1_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00094_s1_d9.png new file mode 100644 index 0000000..9406aea Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00094_s1_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00095_s1_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00095_s1_d9.png new file mode 100644 index 0000000..4fe9fab Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00095_s1_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00101_s1_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00101_s1_d9.png new file mode 100644 index 0000000..d615da6 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00101_s1_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00104_s0_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00104_s0_d9.png new file mode 100644 index 0000000..aea6a5c Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00104_s0_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00105_s3_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00105_s3_d9.png new file mode 100644 index 0000000..125f081 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00105_s3_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00109_s0_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00109_s0_d9.png new file mode 100644 index 0000000..c84775b Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00109_s0_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00113_s0_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00113_s0_d9.png new file mode 100644 index 0000000..dc11927 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00113_s0_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00115_s3_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00115_s3_d9.png new file mode 100644 index 0000000..82c8643 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00115_s3_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00117_s1_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00117_s1_d9.png new file mode 100644 index 0000000..c88ed0e Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00117_s1_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00118_s0_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00118_s0_d9.png new file mode 100644 index 0000000..c2240c7 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00118_s0_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00118_s3_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00118_s3_d9.png new file mode 100644 index 0000000..b37a595 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00118_s3_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00120_s3_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00120_s3_d9.png new file mode 100644 index 0000000..223d01a Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00120_s3_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00122_s1_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00122_s1_d9.png new file mode 100644 index 0000000..35124f4 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00122_s1_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00126_s0_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00126_s0_d9.png new file mode 100644 index 0000000..2616713 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00126_s0_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00126_s2_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00126_s2_d9.png new file mode 100644 index 0000000..4a386e3 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00126_s2_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00127_s1_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00127_s1_d9.png new file mode 100644 index 0000000..a32fcbf Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00127_s1_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00133_s3_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00133_s3_d9.png new file mode 100644 index 0000000..c5b869e Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00133_s3_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00145_s0_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00145_s0_d9.png new file mode 100644 index 0000000..856cfb4 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00145_s0_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00146_s0_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00146_s0_d9.png new file mode 100644 index 0000000..9820e09 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00146_s0_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00147_s3_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00147_s3_d9.png new file mode 100644 index 0000000..07dce83 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00147_s3_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00150_s1_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00150_s1_d9.png new file mode 100644 index 0000000..bb94355 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00150_s1_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00151_s1_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00151_s1_d9.png new file mode 100644 index 0000000..ccec234 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00151_s1_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00154_s3_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00154_s3_d9.png new file mode 100644 index 0000000..e6005a2 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00154_s3_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00157_s1_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00157_s1_d9.png new file mode 100644 index 0000000..a0910d7 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00157_s1_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00159_s3_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00159_s3_d9.png new file mode 100644 index 0000000..c1229dc Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00159_s3_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00161_s1_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00161_s1_d9.png new file mode 100644 index 0000000..1fd137f Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00161_s1_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00162_s1_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00162_s1_d9.png new file mode 100644 index 0000000..318f128 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00162_s1_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00166_s2_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00166_s2_d9.png new file mode 100644 index 0000000..5db5efd Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00166_s2_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00170_s3_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00170_s3_d9.png new file mode 100644 index 0000000..a55cc51 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00170_s3_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00171_s0_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00171_s0_d9.png new file mode 100644 index 0000000..24b6ae8 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00171_s0_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00172_s1_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00172_s1_d9.png new file mode 100644 index 0000000..27d4689 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00172_s1_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00173_s3_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00173_s3_d9.png new file mode 100644 index 0000000..467e9d3 Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00173_s3_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00174_s0_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00174_s0_d9.png new file mode 100644 index 0000000..042c3ca Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00174_s0_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00177_s0_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00177_s0_d9.png new file mode 100644 index 0000000..244fdcf Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00177_s0_d9.png differ diff --git a/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00179_s1_d9.png b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00179_s1_d9.png new file mode 100644 index 0000000..278f6bd Binary files /dev/null and b/backend/data/digit_dataset/sections_synthetic/train/9/synthetic_window_00179_s1_d9.png differ diff --git a/backend/data/digit_dataset/windows/test/meter_02102026.png b/backend/data/digit_dataset/windows/test/meter_02102026.png new file mode 100644 index 0000000..7efbb0a Binary files /dev/null and b/backend/data/digit_dataset/windows/test/meter_02102026.png differ diff --git a/backend/data/digit_dataset/windows/train/meter_01122026.png b/backend/data/digit_dataset/windows/train/meter_01122026.png new file mode 100644 index 0000000..143e3d0 Binary files /dev/null and b/backend/data/digit_dataset/windows/train/meter_01122026.png differ diff --git a/backend/data/digit_dataset/windows/train/meter_01132026.png b/backend/data/digit_dataset/windows/train/meter_01132026.png new file mode 100644 index 0000000..191e297 Binary files /dev/null and b/backend/data/digit_dataset/windows/train/meter_01132026.png differ diff --git a/backend/data/digit_dataset/windows/train/meter_01302026.png b/backend/data/digit_dataset/windows/train/meter_01302026.png new file mode 100644 index 0000000..50c25a2 Binary files /dev/null and b/backend/data/digit_dataset/windows/train/meter_01302026.png differ diff --git a/backend/data/digit_dataset/windows/train/meter_02162026.png b/backend/data/digit_dataset/windows/train/meter_02162026.png new file mode 100644 index 0000000..f9c4821 Binary files /dev/null and b/backend/data/digit_dataset/windows/train/meter_02162026.png differ diff --git a/backend/data/digit_dataset/windows/train/meter_02192026.png b/backend/data/digit_dataset/windows/train/meter_02192026.png new file mode 100644 index 0000000..0973c89 Binary files /dev/null and b/backend/data/digit_dataset/windows/train/meter_02192026.png differ diff --git a/backend/data/digit_dataset/windows/train/meter_02202026.png b/backend/data/digit_dataset/windows/train/meter_02202026.png new file mode 100644 index 0000000..c49a5c6 Binary files /dev/null and b/backend/data/digit_dataset/windows/train/meter_02202026.png differ diff --git a/backend/data/digit_dataset/windows/train/meter_02242026.png b/backend/data/digit_dataset/windows/train/meter_02242026.png new file mode 100644 index 0000000..4a5cda4 Binary files /dev/null and b/backend/data/digit_dataset/windows/train/meter_02242026.png differ diff --git a/backend/data/digit_dataset/windows/train/meter_02272026.png b/backend/data/digit_dataset/windows/train/meter_02272026.png new file mode 100644 index 0000000..feee1e1 Binary files /dev/null and b/backend/data/digit_dataset/windows/train/meter_02272026.png differ diff --git a/backend/data/digit_dataset/windows/train/meter_03032026.png b/backend/data/digit_dataset/windows/train/meter_03032026.png new file mode 100644 index 0000000..0f6e745 Binary files /dev/null and b/backend/data/digit_dataset/windows/train/meter_03032026.png differ diff --git a/backend/data/digit_dataset/windows/train/meter_07012020.png b/backend/data/digit_dataset/windows/train/meter_07012020.png new file mode 100644 index 0000000..c600fbb Binary files /dev/null and b/backend/data/digit_dataset/windows/train/meter_07012020.png differ diff --git a/backend/data/digit_dataset/windows/train/meter_10092025.png b/backend/data/digit_dataset/windows/train/meter_10092025.png new file mode 100644 index 0000000..1839fbb Binary files /dev/null and b/backend/data/digit_dataset/windows/train/meter_10092025.png differ diff --git a/backend/data/digit_dataset/windows/train/meter_11112020.png b/backend/data/digit_dataset/windows/train/meter_11112020.png new file mode 100644 index 0000000..851773d Binary files /dev/null and b/backend/data/digit_dataset/windows/train/meter_11112020.png differ diff --git a/backend/data/digit_dataset/windows/val/meter_02022026.png b/backend/data/digit_dataset/windows/val/meter_02022026.png new file mode 100644 index 0000000..c8d171e Binary files /dev/null and b/backend/data/digit_dataset/windows/val/meter_02022026.png differ diff --git a/backend/data/digit_dataset/windows/val/meter_02142026.png b/backend/data/digit_dataset/windows/val/meter_02142026.png new file mode 100644 index 0000000..2ec47a5 Binary files /dev/null and b/backend/data/digit_dataset/windows/val/meter_02142026.png differ diff --git a/backend/data/digit_dataset/windows_canonical/test/meter_02102026.png b/backend/data/digit_dataset/windows_canonical/test/meter_02102026.png new file mode 100644 index 0000000..df91f96 Binary files /dev/null and b/backend/data/digit_dataset/windows_canonical/test/meter_02102026.png differ diff --git a/backend/data/digit_dataset/windows_canonical/train/meter_01122026.png b/backend/data/digit_dataset/windows_canonical/train/meter_01122026.png new file mode 100644 index 0000000..d685b29 Binary files /dev/null and b/backend/data/digit_dataset/windows_canonical/train/meter_01122026.png differ diff --git a/backend/data/digit_dataset/windows_canonical/train/meter_01132026.png b/backend/data/digit_dataset/windows_canonical/train/meter_01132026.png new file mode 100644 index 0000000..191e297 Binary files /dev/null and b/backend/data/digit_dataset/windows_canonical/train/meter_01132026.png differ diff --git a/backend/data/digit_dataset/windows_canonical/train/meter_01302026.png b/backend/data/digit_dataset/windows_canonical/train/meter_01302026.png new file mode 100644 index 0000000..c1d3bd6 Binary files /dev/null and b/backend/data/digit_dataset/windows_canonical/train/meter_01302026.png differ diff --git a/backend/data/digit_dataset/windows_canonical/train/meter_02162026.png b/backend/data/digit_dataset/windows_canonical/train/meter_02162026.png new file mode 100644 index 0000000..fbfe335 Binary files /dev/null and b/backend/data/digit_dataset/windows_canonical/train/meter_02162026.png differ diff --git a/backend/data/digit_dataset/windows_canonical/train/meter_02192026.png b/backend/data/digit_dataset/windows_canonical/train/meter_02192026.png new file mode 100644 index 0000000..afb0b51 Binary files /dev/null and b/backend/data/digit_dataset/windows_canonical/train/meter_02192026.png differ diff --git a/backend/data/digit_dataset/windows_canonical/train/meter_02202026.png b/backend/data/digit_dataset/windows_canonical/train/meter_02202026.png new file mode 100644 index 0000000..d9d4e85 Binary files /dev/null and b/backend/data/digit_dataset/windows_canonical/train/meter_02202026.png differ diff --git a/backend/data/digit_dataset/windows_canonical/train/meter_02242026.png b/backend/data/digit_dataset/windows_canonical/train/meter_02242026.png new file mode 100644 index 0000000..fb7e5f4 Binary files /dev/null and b/backend/data/digit_dataset/windows_canonical/train/meter_02242026.png differ diff --git a/backend/data/digit_dataset/windows_canonical/train/meter_02272026.png b/backend/data/digit_dataset/windows_canonical/train/meter_02272026.png new file mode 100644 index 0000000..522f446 Binary files /dev/null and b/backend/data/digit_dataset/windows_canonical/train/meter_02272026.png differ diff --git a/backend/data/digit_dataset/windows_canonical/train/meter_03032026.png b/backend/data/digit_dataset/windows_canonical/train/meter_03032026.png new file mode 100644 index 0000000..b18d6b4 Binary files /dev/null and b/backend/data/digit_dataset/windows_canonical/train/meter_03032026.png differ diff --git a/backend/data/digit_dataset/windows_canonical/train/meter_07012020.png b/backend/data/digit_dataset/windows_canonical/train/meter_07012020.png new file mode 100644 index 0000000..09cf9e7 Binary files /dev/null and b/backend/data/digit_dataset/windows_canonical/train/meter_07012020.png differ diff --git a/backend/data/digit_dataset/windows_canonical/train/meter_10092025.png b/backend/data/digit_dataset/windows_canonical/train/meter_10092025.png new file mode 100644 index 0000000..e3153ba Binary files /dev/null and b/backend/data/digit_dataset/windows_canonical/train/meter_10092025.png differ diff --git a/backend/data/digit_dataset/windows_canonical/train/meter_11112020.png b/backend/data/digit_dataset/windows_canonical/train/meter_11112020.png new file mode 100644 index 0000000..851773d Binary files /dev/null and b/backend/data/digit_dataset/windows_canonical/train/meter_11112020.png differ diff --git a/backend/data/digit_dataset/windows_canonical/val/meter_02022026.png b/backend/data/digit_dataset/windows_canonical/val/meter_02022026.png new file mode 100644 index 0000000..3485e25 Binary files /dev/null and b/backend/data/digit_dataset/windows_canonical/val/meter_02022026.png differ diff --git a/backend/data/digit_dataset/windows_canonical/val/meter_02142026.png b/backend/data/digit_dataset/windows_canonical/val/meter_02142026.png new file mode 100644 index 0000000..5d56ce2 Binary files /dev/null and b/backend/data/digit_dataset/windows_canonical/val/meter_02142026.png differ diff --git a/backend/data/roi_dataset.example.yaml b/backend/data/roi_dataset.example.yaml new file mode 100644 index 0000000..66c8be5 --- /dev/null +++ b/backend/data/roi_dataset.example.yaml @@ -0,0 +1,8 @@ +path: ./roi_dataset +train: images/train +val: images/val +test: images/test + +nc: 1 +names: + 0: digit_window diff --git a/backend/data/roi_dataset.yaml b/backend/data/roi_dataset.yaml new file mode 100644 index 0000000..66c8be5 --- /dev/null +++ b/backend/data/roi_dataset.yaml @@ -0,0 +1,8 @@ +path: ./roi_dataset +train: images/train +val: images/val +test: images/test + +nc: 1 +names: + 0: digit_window diff --git a/backend/data/roi_dataset/images/test/meter_02102026.JPEG b/backend/data/roi_dataset/images/test/meter_02102026.JPEG new file mode 100644 index 0000000..5898e49 Binary files /dev/null and b/backend/data/roi_dataset/images/test/meter_02102026.JPEG differ diff --git a/backend/data/roi_dataset/images/train/meter_01122026.JPEG b/backend/data/roi_dataset/images/train/meter_01122026.JPEG new file mode 100644 index 0000000..c070d41 Binary files /dev/null and b/backend/data/roi_dataset/images/train/meter_01122026.JPEG differ diff --git a/backend/data/roi_dataset/images/train/meter_01132026.jpg b/backend/data/roi_dataset/images/train/meter_01132026.jpg new file mode 100644 index 0000000..bc23134 Binary files /dev/null and b/backend/data/roi_dataset/images/train/meter_01132026.jpg differ diff --git a/backend/data/roi_dataset/images/train/meter_01302026.JPEG b/backend/data/roi_dataset/images/train/meter_01302026.JPEG new file mode 100644 index 0000000..7278eea Binary files /dev/null and b/backend/data/roi_dataset/images/train/meter_01302026.JPEG differ diff --git a/backend/data/roi_dataset/images/train/meter_02162026.JPEG b/backend/data/roi_dataset/images/train/meter_02162026.JPEG new file mode 100644 index 0000000..b5c6f12 Binary files /dev/null and b/backend/data/roi_dataset/images/train/meter_02162026.JPEG differ diff --git a/backend/data/roi_dataset/images/train/meter_02192026.JPEG b/backend/data/roi_dataset/images/train/meter_02192026.JPEG new file mode 100644 index 0000000..1742f04 Binary files /dev/null and b/backend/data/roi_dataset/images/train/meter_02192026.JPEG differ diff --git a/backend/data/roi_dataset/images/train/meter_02202026.JPEG b/backend/data/roi_dataset/images/train/meter_02202026.JPEG new file mode 100644 index 0000000..973d9b5 Binary files /dev/null and b/backend/data/roi_dataset/images/train/meter_02202026.JPEG differ diff --git a/backend/data/roi_dataset/images/train/meter_02242026.JPEG b/backend/data/roi_dataset/images/train/meter_02242026.JPEG new file mode 100644 index 0000000..c4582c5 Binary files /dev/null and b/backend/data/roi_dataset/images/train/meter_02242026.JPEG differ diff --git a/backend/data/roi_dataset/images/train/meter_02272026.JPEG b/backend/data/roi_dataset/images/train/meter_02272026.JPEG new file mode 100644 index 0000000..8fc5851 Binary files /dev/null and b/backend/data/roi_dataset/images/train/meter_02272026.JPEG differ diff --git a/backend/data/roi_dataset/images/train/meter_03032026.JPEG b/backend/data/roi_dataset/images/train/meter_03032026.JPEG new file mode 100644 index 0000000..ad7bb8e Binary files /dev/null and b/backend/data/roi_dataset/images/train/meter_03032026.JPEG differ diff --git a/backend/data/roi_dataset/images/train/meter_07012020.JPEG b/backend/data/roi_dataset/images/train/meter_07012020.JPEG new file mode 100644 index 0000000..ccbb9f9 Binary files /dev/null and b/backend/data/roi_dataset/images/train/meter_07012020.JPEG differ diff --git a/backend/data/roi_dataset/images/train/meter_10092025.JPEG b/backend/data/roi_dataset/images/train/meter_10092025.JPEG new file mode 100644 index 0000000..2cddb39 Binary files /dev/null and b/backend/data/roi_dataset/images/train/meter_10092025.JPEG differ diff --git a/backend/data/roi_dataset/images/train/meter_11112020.JPEG b/backend/data/roi_dataset/images/train/meter_11112020.JPEG new file mode 100644 index 0000000..e1333fc Binary files /dev/null and b/backend/data/roi_dataset/images/train/meter_11112020.JPEG differ diff --git a/backend/data/roi_dataset/images/val/meter_02022026.JPEG b/backend/data/roi_dataset/images/val/meter_02022026.JPEG new file mode 100644 index 0000000..51e44c8 Binary files /dev/null and b/backend/data/roi_dataset/images/val/meter_02022026.JPEG differ diff --git a/backend/data/roi_dataset/images/val/meter_02142026.JPEG b/backend/data/roi_dataset/images/val/meter_02142026.JPEG new file mode 100644 index 0000000..a0efe12 Binary files /dev/null and b/backend/data/roi_dataset/images/val/meter_02142026.JPEG differ diff --git a/backend/data/roi_dataset/labels/test/meter_02102026.txt b/backend/data/roi_dataset/labels/test/meter_02102026.txt new file mode 100644 index 0000000..63b0746 --- /dev/null +++ b/backend/data/roi_dataset/labels/test/meter_02102026.txt @@ -0,0 +1 @@ +0 0.424084 0.418194 0.064572 0.142670 \ No newline at end of file diff --git a/backend/data/roi_dataset/labels/train/meter_01122026.txt b/backend/data/roi_dataset/labels/train/meter_01122026.txt new file mode 100644 index 0000000..468b154 --- /dev/null +++ b/backend/data/roi_dataset/labels/train/meter_01122026.txt @@ -0,0 +1 @@ +0 0.446771 0.543194 0.061082 0.130890 \ No newline at end of file diff --git a/backend/data/roi_dataset/labels/train/meter_01132026.txt b/backend/data/roi_dataset/labels/train/meter_01132026.txt new file mode 100644 index 0000000..5e96dfb --- /dev/null +++ b/backend/data/roi_dataset/labels/train/meter_01132026.txt @@ -0,0 +1 @@ +0 0.458279 0.446990 0.130563 0.064136 \ No newline at end of file diff --git a/backend/data/roi_dataset/labels/train/meter_01302026.txt b/backend/data/roi_dataset/labels/train/meter_01302026.txt new file mode 100644 index 0000000..3cf7cda --- /dev/null +++ b/backend/data/roi_dataset/labels/train/meter_01302026.txt @@ -0,0 +1 @@ +0 0.426702 0.499346 0.069808 0.145288 \ No newline at end of file diff --git a/backend/data/roi_dataset/labels/train/meter_02162026.txt b/backend/data/roi_dataset/labels/train/meter_02162026.txt new file mode 100644 index 0000000..64b6a96 --- /dev/null +++ b/backend/data/roi_dataset/labels/train/meter_02162026.txt @@ -0,0 +1 @@ +0 0.424196 0.567758 0.131222 0.066592 diff --git a/backend/data/roi_dataset/labels/train/meter_02192026.txt b/backend/data/roi_dataset/labels/train/meter_02192026.txt new file mode 100644 index 0000000..0fb7ae1 --- /dev/null +++ b/backend/data/roi_dataset/labels/train/meter_02192026.txt @@ -0,0 +1 @@ +0 0.396769 0.410369 0.072200 0.137751 diff --git a/backend/data/roi_dataset/labels/train/meter_02202026.txt b/backend/data/roi_dataset/labels/train/meter_02202026.txt new file mode 100644 index 0000000..b56a690 --- /dev/null +++ b/backend/data/roi_dataset/labels/train/meter_02202026.txt @@ -0,0 +1 @@ +0 0.469297 0.492823 0.048318 0.093266 diff --git a/backend/data/roi_dataset/labels/train/meter_02242026.txt b/backend/data/roi_dataset/labels/train/meter_02242026.txt new file mode 100644 index 0000000..4284f62 --- /dev/null +++ b/backend/data/roi_dataset/labels/train/meter_02242026.txt @@ -0,0 +1 @@ +0 0.421452 0.433582 0.075050 0.140942 diff --git a/backend/data/roi_dataset/labels/train/meter_02272026.txt b/backend/data/roi_dataset/labels/train/meter_02272026.txt new file mode 100644 index 0000000..28da2c2 --- /dev/null +++ b/backend/data/roi_dataset/labels/train/meter_02272026.txt @@ -0,0 +1 @@ +0 0.414681 0.399847 0.077041 0.152156 diff --git a/backend/data/roi_dataset/labels/train/meter_03032026.txt b/backend/data/roi_dataset/labels/train/meter_03032026.txt new file mode 100644 index 0000000..b42f3dc --- /dev/null +++ b/backend/data/roi_dataset/labels/train/meter_03032026.txt @@ -0,0 +1 @@ +0 0.412604 0.364807 0.063274 0.139887 diff --git a/backend/data/roi_dataset/labels/train/meter_07012020.txt b/backend/data/roi_dataset/labels/train/meter_07012020.txt new file mode 100644 index 0000000..74b2c5c --- /dev/null +++ b/backend/data/roi_dataset/labels/train/meter_07012020.txt @@ -0,0 +1 @@ +0 0.384162 0.542539 0.107984 0.048429 \ No newline at end of file diff --git a/backend/data/roi_dataset/labels/train/meter_10092025.txt b/backend/data/roi_dataset/labels/train/meter_10092025.txt new file mode 100644 index 0000000..579273e --- /dev/null +++ b/backend/data/roi_dataset/labels/train/meter_10092025.txt @@ -0,0 +1 @@ +0 0.446771 0.501963 0.075044 0.137435 \ No newline at end of file diff --git a/backend/data/roi_dataset/labels/train/meter_11112020.txt b/backend/data/roi_dataset/labels/train/meter_11112020.txt new file mode 100644 index 0000000..bc2ee7d --- /dev/null +++ b/backend/data/roi_dataset/labels/train/meter_11112020.txt @@ -0,0 +1 @@ +0 0.477912 0.384817 0.205170 0.086387 \ No newline at end of file diff --git a/backend/data/roi_dataset/labels/val/meter_02022026.txt b/backend/data/roi_dataset/labels/val/meter_02022026.txt new file mode 100644 index 0000000..8104f89 --- /dev/null +++ b/backend/data/roi_dataset/labels/val/meter_02022026.txt @@ -0,0 +1 @@ +0 0.431065 0.452880 0.040140 0.102094 \ No newline at end of file diff --git a/backend/data/roi_dataset/labels/val/meter_02142026.txt b/backend/data/roi_dataset/labels/val/meter_02142026.txt new file mode 100644 index 0000000..99bae35 --- /dev/null +++ b/backend/data/roi_dataset/labels/val/meter_02142026.txt @@ -0,0 +1 @@ +0 0.415813 0.495390 0.062979 0.117801 \ No newline at end of file diff --git a/backend/detector.py b/backend/detector.py new file mode 100644 index 0000000..cacf184 --- /dev/null +++ b/backend/detector.py @@ -0,0 +1,124 @@ +from __future__ import annotations + +from dataclasses import dataclass +from pathlib import Path + +import numpy as np + + +class DetectorUnavailableError(RuntimeError): + """Raised when detector dependencies or weights are not available.""" + + +@dataclass +class RoiDetection: + x1: float + y1: float + x2: float + y2: float + confidence: float + class_id: int + class_name: str + + def to_normalized_bbox(self, width: int, height: int) -> dict[str, float]: + safe_width = max(1, int(width)) + safe_height = max(1, int(height)) + x = max(0.0, min(self.x1, float(safe_width - 1))) + y = max(0.0, min(self.y1, float(safe_height - 1))) + w = max(1.0, min(self.x2, float(safe_width)) - x) + h = max(1.0, min(self.y2, float(safe_height)) - y) + return { + "x": x / safe_width, + "y": y / safe_height, + "width": w / safe_width, + "height": h / safe_height + } + + +class RoiDetector: + def __init__(self, weights_path: Path, class_index: int | None = None, device: str | None = None) -> None: + self.weights_path = Path(weights_path) + self.class_index = class_index + self.device = self._normalize_device(device) + if not self.weights_path.exists(): + raise DetectorUnavailableError( + f"Model weights not found at {self.weights_path}. Train first or set ROI_MODEL_PATH." + ) + + try: + from ultralytics import YOLO + except ImportError as error: + raise DetectorUnavailableError( + "ultralytics is not installed. Install backend/requirements.txt first." + ) from error + + self._model = YOLO(str(self.weights_path)) + self._names = self._model.names or {} + + @staticmethod + def _normalize_device(device: str | None) -> str | None: + if device is None: + return None + normalized = str(device).strip() + if not normalized: + return None + if normalized.lower() == "auto": + return None + return normalized + + @property + def model_name(self) -> str: + return self.weights_path.name + + @property + def device_name(self) -> str: + return self.device or "auto" + + def _resolve_name(self, class_id: int) -> str: + if isinstance(self._names, dict): + return str(self._names.get(class_id, class_id)) + if isinstance(self._names, list) and 0 <= class_id < len(self._names): + return str(self._names[class_id]) + return str(class_id) + + def detect(self, image_rgb: np.ndarray, conf: float = 0.25, iou: float = 0.5, imgsz: int = 960) -> RoiDetection | None: + if image_rgb.ndim != 3 or image_rgb.shape[2] != 3: + raise ValueError("Expected RGB image with shape HxWx3.") + + results = self._model.predict( + source=image_rgb, + conf=conf, + iou=iou, + imgsz=imgsz, + device=self.device, + verbose=False + ) + if not results: + return None + + result = results[0] + boxes = result.boxes + if boxes is None or len(boxes) == 0: + return None + + best: RoiDetection | None = None + xyxy = boxes.xyxy.cpu().numpy() + confidences = boxes.conf.cpu().numpy() + class_ids = boxes.cls.cpu().numpy().astype(int) + for coords, score, class_id in zip(xyxy, confidences, class_ids): + if self.class_index is not None and class_id != self.class_index: + continue + x1, y1, x2, y2 = (float(value) for value in coords.tolist()) + detection = RoiDetection( + x1=x1, + y1=y1, + x2=x2, + y2=y2, + confidence=float(score), + class_id=int(class_id), + class_name=self._resolve_name(int(class_id)) + ) + if not best or detection.confidence > best.confidence: + best = detection + + return best diff --git a/backend/digit_classifier.py b/backend/digit_classifier.py new file mode 100644 index 0000000..7611a9a --- /dev/null +++ b/backend/digit_classifier.py @@ -0,0 +1,139 @@ +from __future__ import annotations + +from dataclasses import dataclass +from pathlib import Path + +import numpy as np +from PIL import Image + +try: + from .digit_model import DEFAULT_CLASS_NAMES, DEFAULT_IMAGE_SIZE, build_digit_cnn, prepare_digit_tensor +except ImportError: + from digit_model import DEFAULT_CLASS_NAMES, DEFAULT_IMAGE_SIZE, build_digit_cnn, prepare_digit_tensor + + +class DigitClassifierUnavailableError(RuntimeError): + """Raised when digit classifier dependencies or weights are unavailable.""" + + +@dataclass +class DigitPrediction: + digit: str + confidence: float + top_k: list[dict[str, float]] + + +class DigitClassifier: + def __init__(self, weights_path: Path, device: str | None = None) -> None: + self.weights_path = Path(weights_path) + if not self.weights_path.exists(): + raise DigitClassifierUnavailableError( + f"Digit classifier weights not found at {self.weights_path}. Train first or set DIGIT_MODEL_PATH." + ) + + try: + import torch + except ImportError as error: + raise DigitClassifierUnavailableError( + "torch is not installed. Install backend/requirements.txt first." + ) from error + + self._torch = torch + self.device = self._resolve_device(device) + self._checkpoint = self._load_checkpoint() + self.class_names = self._resolve_class_names(self._checkpoint) + self.image_size = self._resolve_image_size(self._checkpoint) + state_dict = self._resolve_state_dict(self._checkpoint) + + model = build_digit_cnn(num_classes=len(self.class_names)) + model.load_state_dict(state_dict) + model.eval() + model.to(self.device) + self._model = model + + def _resolve_device(self, raw: str | None) -> str: + if raw is None: + return "cpu" + normalized = str(raw).strip() + if not normalized: + return "cpu" + if normalized.lower() == "auto": + return "cuda:0" if self._torch.cuda.is_available() else "cpu" + return normalized + + def _load_checkpoint(self) -> dict: + try: + payload = self._torch.load(str(self.weights_path), map_location="cpu") + except Exception as error: + raise DigitClassifierUnavailableError( + f"Unable to load digit classifier checkpoint at {self.weights_path}: {error}" + ) from error + if not isinstance(payload, dict): + raise DigitClassifierUnavailableError( + f"Unexpected checkpoint payload at {self.weights_path}; expected dict." + ) + return payload + + @staticmethod + def _resolve_class_names(checkpoint: dict) -> list[str]: + names = checkpoint.get("class_names") + if isinstance(names, list) and names: + return [str(item) for item in names] + return list(DEFAULT_CLASS_NAMES) + + @staticmethod + def _resolve_image_size(checkpoint: dict) -> int: + image_size = checkpoint.get("image_size") + try: + parsed = int(image_size) + except (TypeError, ValueError): + parsed = DEFAULT_IMAGE_SIZE + return max(24, parsed) + + @staticmethod + def _resolve_state_dict(checkpoint: dict) -> dict: + state_dict = checkpoint.get("state_dict") + if isinstance(state_dict, dict) and state_dict: + return state_dict + raise DigitClassifierUnavailableError("Checkpoint is missing a valid state_dict.") + + @property + def model_name(self) -> str: + return self.weights_path.name + + @property + def device_name(self) -> str: + return self.device + + def predict(self, image_rgb: np.ndarray, top_k: int = 3) -> DigitPrediction: + if image_rgb.ndim != 3 or image_rgb.shape[2] != 3: + raise ValueError("Expected RGB image with shape HxWx3.") + + pil_image = Image.fromarray(image_rgb).convert("L") + tensor = prepare_digit_tensor(pil_image, self.image_size).unsqueeze(0) + tensor = tensor.to(self.device) + + with self._torch.no_grad(): + logits = self._model(tensor) + probabilities = self._torch.softmax(logits, dim=1)[0] + + confidence, index = self._torch.max(probabilities, dim=0) + predicted_index = int(index.item()) + predicted_digit = self.class_names[predicted_index] + confidence_value = float(confidence.item()) + + bounded_top_k = max(1, min(int(top_k), len(self.class_names))) + top_values, top_indices = self._torch.topk(probabilities, k=bounded_top_k) + top_payload = [] + for rank in range(bounded_top_k): + class_index = int(top_indices[rank].item()) + top_payload.append({ + "digit": self.class_names[class_index], + "confidence": float(top_values[rank].item()) + }) + + return DigitPrediction( + digit=predicted_digit, + confidence=confidence_value, + top_k=top_payload + ) diff --git a/backend/digit_model.py b/backend/digit_model.py new file mode 100644 index 0000000..bb1413f --- /dev/null +++ b/backend/digit_model.py @@ -0,0 +1,58 @@ +from __future__ import annotations + +import numpy as np + +DEFAULT_IMAGE_SIZE = 64 +DEFAULT_NUM_CLASSES = 10 +DEFAULT_CLASS_NAMES = [str(index) for index in range(DEFAULT_NUM_CLASSES)] + + +def _resolve_torch(): + try: + import torch + from torch import nn + except ImportError as error: + raise RuntimeError("torch is required for digit classifier training and inference.") from error + return torch, nn + + +def build_digit_cnn(num_classes: int = DEFAULT_NUM_CLASSES): + _, nn = _resolve_torch() + return nn.Sequential( + nn.Conv2d(1, 32, kernel_size=3, padding=1), + nn.BatchNorm2d(32), + nn.ReLU(inplace=True), + nn.MaxPool2d(kernel_size=2), + nn.Conv2d(32, 64, kernel_size=3, padding=1), + nn.BatchNorm2d(64), + nn.ReLU(inplace=True), + nn.MaxPool2d(kernel_size=2), + nn.Conv2d(64, 128, kernel_size=3, padding=1), + nn.BatchNorm2d(128), + nn.ReLU(inplace=True), + nn.MaxPool2d(kernel_size=2), + nn.Conv2d(128, 128, kernel_size=3, padding=1), + nn.BatchNorm2d(128), + nn.ReLU(inplace=True), + nn.AdaptiveAvgPool2d((1, 1)), + nn.Flatten(), + nn.Dropout(p=0.22), + nn.Linear(128, num_classes) + ) + + +def prepare_digit_tensor(image, image_size: int = DEFAULT_IMAGE_SIZE): + try: + from PIL import Image + except ImportError as error: + raise RuntimeError("Pillow is required for digit classifier preprocessing.") from error + torch, _ = _resolve_torch() + + if image.mode != "L": + image = image.convert("L") + if hasattr(Image, "Resampling"): + resized = image.resize((image_size, image_size), Image.Resampling.BILINEAR) + else: + resized = image.resize((image_size, image_size), Image.BILINEAR) + pixels = np.asarray(resized, dtype=np.float32) / 255.0 + return torch.from_numpy(pixels).unsqueeze(0) diff --git a/backend/extract_digit_windows.py b/backend/extract_digit_windows.py new file mode 100644 index 0000000..932c936 --- /dev/null +++ b/backend/extract_digit_windows.py @@ -0,0 +1,241 @@ +from __future__ import annotations + +import argparse +import csv +import json +import shutil +from pathlib import Path + +from PIL import Image + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description="Extract ROI digit windows as standalone images from the ROI dataset." + ) + parser.add_argument( + "--csv", + default="../assets/meter_readings.csv", + help="CSV with filename,value rows (default resolves from backend/)." + ) + parser.add_argument( + "--roi-dataset-dir", + default="data/roi_dataset", + help="ROI dataset root containing images/ and labels/." + ) + parser.add_argument( + "--out-dir", + default="data/digit_dataset", + help="Output dataset root for extracted windows." + ) + parser.add_argument( + "--expand-x", + type=float, + default=0.0, + help="Expand ROI horizontally by this ratio of ROI width on each side." + ) + parser.add_argument( + "--expand-y", + type=float, + default=0.0, + help="Expand ROI vertically by this ratio of ROI height on each side." + ) + parser.add_argument( + "--clean", + action="store_true", + help="Delete output directory before writing." + ) + return parser.parse_args() + + +def resolve(base_dir: Path, value: str) -> Path: + path = Path(value) + if path.is_absolute(): + return path + return (base_dir / path).resolve() + + +def clamp(value: float, low: float, high: float) -> float: + return max(low, min(high, value)) + + +def read_value_map(csv_path: Path) -> dict[str, str]: + mapping: dict[str, str] = {} + with csv_path.open("r", encoding="utf-8") as handle: + reader = csv.DictReader(handle) + for row in reader: + filename = (row.get("filename") or "").strip() + value = (row.get("value") or "").strip() + if filename and value: + mapping[filename] = value + return mapping + + +def load_roi_label(label_path: Path) -> tuple[float, float, float, float]: + raw = label_path.read_text(encoding="utf-8").strip().splitlines() + for line in raw: + parts = line.strip().split() + if len(parts) != 5: + continue + _, x_center, y_center, width, height = parts + return float(x_center), float(y_center), float(width), float(height) + raise ValueError(f"No valid YOLO row in label file: {label_path}") + + +def yolo_to_pixel_rect( + image_width: int, + image_height: int, + x_center: float, + y_center: float, + width: float, + height: float, + expand_x: float, + expand_y: float +) -> tuple[int, int, int, int]: + x = (x_center - width * 0.5) * image_width + y = (y_center - height * 0.5) * image_height + w = width * image_width + h = height * image_height + + x -= w * expand_x + y -= h * expand_y + w *= (1 + 2 * expand_x) + h *= (1 + 2 * expand_y) + + x0 = int(round(clamp(x, 0, image_width - 1))) + y0 = int(round(clamp(y, 0, image_height - 1))) + x1 = int(round(clamp(x + w, x0 + 1, image_width))) + y1 = int(round(clamp(y + h, y0 + 1, image_height))) + return x0, y0, x1 - x0, y1 - y0 + + +def write_csv(path: Path, rows: list[dict[str, str]], headers: list[str]) -> None: + with path.open("w", encoding="utf-8", newline="") as handle: + writer = csv.DictWriter(handle, fieldnames=headers) + writer.writeheader() + writer.writerows(rows) + + +def ensure_dirs(out_dir: Path) -> None: + (out_dir / "windows").mkdir(parents=True, exist_ok=True) + (out_dir / "manifests").mkdir(parents=True, exist_ok=True) + for split in ("train", "val", "test"): + (out_dir / "windows" / split).mkdir(parents=True, exist_ok=True) + + +def main() -> None: + args = parse_args() + base_dir = Path(__file__).resolve().parent + + csv_path = resolve(base_dir, args.csv) + roi_dataset_dir = resolve(base_dir, args.roi_dataset_dir) + out_dir = resolve(base_dir, args.out_dir) + + if not csv_path.exists(): + raise FileNotFoundError(f"CSV not found: {csv_path}") + if not roi_dataset_dir.exists(): + raise FileNotFoundError(f"ROI dataset dir not found: {roi_dataset_dir}") + + value_map = read_value_map(csv_path) + + if args.clean and out_dir.exists(): + shutil.rmtree(out_dir) + ensure_dirs(out_dir) + + rows: list[dict[str, str]] = [] + skipped: list[dict[str, str]] = [] + split_counts = {"train": 0, "val": 0, "test": 0} + missing_reading_count = 0 + + for split in ("train", "val", "test"): + image_dir = roi_dataset_dir / "images" / split + label_dir = roi_dataset_dir / "labels" / split + if not image_dir.exists(): + continue + + for image_path in sorted(image_dir.iterdir()): + if not image_path.is_file(): + continue + if image_path.suffix.lower() not in {".jpg", ".jpeg", ".png"}: + continue + + label_path = label_dir / f"{image_path.stem}.txt" + if not label_path.exists(): + skipped.append({"split": split, "filename": image_path.name, "reason": "missing-roi-label"}) + continue + + try: + x_center, y_center, width_norm, height_norm = load_roi_label(label_path) + except ValueError: + skipped.append({"split": split, "filename": image_path.name, "reason": "invalid-roi-label"}) + continue + + with Image.open(image_path) as source: + source_rgb = source.convert("RGB") + roi_x, roi_y, roi_w, roi_h = yolo_to_pixel_rect( + image_width=source_rgb.width, + image_height=source_rgb.height, + x_center=x_center, + y_center=y_center, + width=width_norm, + height=height_norm, + expand_x=args.expand_x, + expand_y=args.expand_y + ) + + window = source_rgb.crop((roi_x, roi_y, roi_x + roi_w, roi_y + roi_h)) + window_path = out_dir / "windows" / split / f"{image_path.stem}.png" + window.save(window_path) + + reading = (value_map.get(image_path.name) or "").strip() + if not reading: + missing_reading_count += 1 + + rows.append({ + "split": split, + "filename": image_path.name, + "window_path": str(window_path.relative_to(out_dir)), + "reading": reading, + "roi_x": str(roi_x), + "roi_y": str(roi_y), + "roi_w": str(roi_w), + "roi_h": str(roi_h) + }) + split_counts[split] += 1 + + write_csv( + out_dir / "manifests" / "windows.csv", + rows, + ["split", "filename", "window_path", "reading", "roi_x", "roi_y", "roi_w", "roi_h"] + ) + write_csv( + out_dir / "manifests" / "skipped.csv", + skipped, + ["split", "filename", "reason"] + ) + + summary = { + "rows_exported": len(rows), + "rows_skipped": len(skipped), + "splits": split_counts, + "missing_reading_count": missing_reading_count, + "expand_x": args.expand_x, + "expand_y": args.expand_y, + "csv": str(csv_path), + "roi_dataset_dir": str(roi_dataset_dir), + "out_dir": str(out_dir) + } + (out_dir / "manifests" / "summary.json").write_text(json.dumps(summary, indent=2), encoding="utf-8") + + print(f"Digit windows exported: {out_dir}") + print(f"Rows: {summary['rows_exported']} (skipped={summary['rows_skipped']})") + print( + "Split rows: " + f"train={split_counts['train']} val={split_counts['val']} test={split_counts['test']}" + ) + print(f"Missing readings: {missing_reading_count}") + print(f"Manifests: {out_dir / 'manifests'}") + + +if __name__ == "__main__": + main() diff --git a/backend/generate_synthetic_digit_dataset.py b/backend/generate_synthetic_digit_dataset.py new file mode 100644 index 0000000..f77886b --- /dev/null +++ b/backend/generate_synthetic_digit_dataset.py @@ -0,0 +1,423 @@ +from __future__ import annotations + +import argparse +import csv +import json +import random +import shutil +from dataclasses import dataclass +from pathlib import Path + +import numpy as np +from PIL import Image, ImageDraw, ImageEnhance, ImageFilter + +SUPPORTED_IMAGE_SUFFIXES = {".jpg", ".jpeg", ".png", ".bmp", ".webp"} +DEFAULT_DIGITS = [str(index) for index in range(10)] + + +@dataclass +class SourceSample: + path: Path + digit: str + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description=( + "Generate synthetic train-only digit sections from labeled train patches. " + "Val/test remain real-only." + ) + ) + parser.add_argument( + "--dataset-root", + default="data/digit_dataset/sections_labeled", + help="Root with labeled split folders (train/val/test)." + ) + parser.add_argument( + "--output-root", + default="data/digit_dataset/sections_synthetic", + help="Output root for synthetic train sections." + ) + parser.add_argument( + "--direct-per-real", + type=int, + default=6, + help="Number of directly-augmented synthetic cells per real train cell." + ) + parser.add_argument( + "--compose-window-count", + type=int, + default=180, + help=( + "Optional number of synthetic 4-digit windows to compose from train patches; " + "each window emits 4 equispaced cells." + ) + ) + parser.add_argument( + "--save-composed-windows", + action="store_true", + help="Persist synthetic composed windows for QA/debug under output-root/windows." + ) + parser.add_argument( + "--clean", + action="store_true", + help="Delete existing output-root before generation." + ) + parser.add_argument("--seed", type=int, default=42) + return parser.parse_args() + + +def resolve_path(base_dir: Path, raw: str) -> Path: + path = Path(raw) + if path.is_absolute(): + return path + return (base_dir / path).resolve() + + +def collect_train_samples(dataset_root: Path) -> list[SourceSample]: + split_dir = dataset_root / "train" + samples: list[SourceSample] = [] + for digit in DEFAULT_DIGITS: + digit_dir = split_dir / digit + if not digit_dir.exists(): + continue + for image_path in sorted(digit_dir.iterdir()): + if not image_path.is_file() or image_path.suffix.lower() not in SUPPORTED_IMAGE_SUFFIXES: + continue + samples.append(SourceSample(path=image_path, digit=digit)) + return samples + + +def ensure_output_structure(output_root: Path) -> None: + for digit in DEFAULT_DIGITS: + (output_root / "train" / digit).mkdir(parents=True, exist_ok=True) + (output_root / "manifests").mkdir(parents=True, exist_ok=True) + + +def apply_brightness_contrast(image: Image.Image, rng: random.Random) -> Image.Image: + image = ImageEnhance.Contrast(image).enhance(rng.uniform(0.8, 1.28)) + image = ImageEnhance.Brightness(image).enhance(rng.uniform(0.86, 1.18)) + return image + + +def apply_noise(image: Image.Image, rng: random.Random) -> Image.Image: + sigma = rng.uniform(2.0, 13.0) + pixels = np.asarray(image, dtype=np.float32) + noise = rng.gauss(0.0, sigma) + if sigma > 0: + noise = np.random.default_rng(rng.randint(0, 1_000_000)).normal( + loc=0.0, + scale=sigma, + size=pixels.shape + ) + noisy = np.clip(pixels + noise, 0, 255).astype(np.uint8) + return Image.fromarray(noisy, mode="L") + + +def apply_perspective_quad(image: Image.Image, rng: random.Random, jitter_ratio: float) -> Image.Image: + width, height = image.size + max_dx = max(1.0, width * jitter_ratio) + max_dy = max(1.0, height * jitter_ratio) + quad = ( + rng.uniform(-max_dx, max_dx), rng.uniform(-max_dy, max_dy), + width + rng.uniform(-max_dx, max_dx), rng.uniform(-max_dy, max_dy), + width + rng.uniform(-max_dx, max_dx), height + rng.uniform(-max_dy, max_dy), + rng.uniform(-max_dx, max_dx), height + rng.uniform(-max_dy, max_dy) + ) + if hasattr(Image, "Transform"): + return image.transform( + size=(width, height), + method=Image.Transform.QUAD, + data=quad, + resample=Image.Resampling.BILINEAR, + fillcolor=255 + ) + return image.transform( + size=(width, height), + method=Image.QUAD, + data=quad, + resample=Image.BILINEAR, + fillcolor=255 + ) + + +def apply_edge_clip(image: Image.Image, rng: random.Random) -> Image.Image: + width, height = image.size + clipped = image.copy() + draw = ImageDraw.Draw(clipped) + side = rng.choice(["left", "right", "top", "bottom"]) + if side in {"left", "right"}: + clip = max(1, int(round(width * rng.uniform(0.04, 0.16)))) + if side == "left": + draw.rectangle((0, 0, clip, height), fill=255) + else: + draw.rectangle((width - clip, 0, width, height), fill=255) + else: + clip = max(1, int(round(height * rng.uniform(0.04, 0.16)))) + if side == "top": + draw.rectangle((0, 0, width, clip), fill=255) + else: + draw.rectangle((0, height - clip, width, height), fill=255) + return clipped + + +def apply_partial_occlusion(image: Image.Image, rng: random.Random) -> Image.Image: + width, height = image.size + occ_w = max(1, int(round(width * rng.uniform(0.1, 0.34)))) + occ_h = max(1, int(round(height * rng.uniform(0.08, 0.3)))) + x0 = rng.randint(0, max(0, width - occ_w)) + y0 = rng.randint(0, max(0, height - occ_h)) + occluded = image.copy() + draw = ImageDraw.Draw(occluded) + draw.rectangle((x0, y0, x0 + occ_w, y0 + occ_h), fill=255) + return occluded + + +def fit_patch_into_cell( + patch: Image.Image, + cell_width: int, + cell_height: int, + rng: random.Random +) -> Image.Image: + source = patch.convert("L") + target = Image.new("L", (cell_width, cell_height), color=255) + scale_x = rng.uniform(0.68, 0.95) + scale_y = rng.uniform(0.68, 0.95) + max_w = max(8, int(round(cell_width * scale_x))) + max_h = max(8, int(round(cell_height * scale_y))) + ratio = min(max_w / max(1, source.width), max_h / max(1, source.height)) + resized_w = max(6, int(round(source.width * ratio))) + resized_h = max(6, int(round(source.height * ratio))) + resized = source.resize((resized_w, resized_h), Image.Resampling.BILINEAR) + + offset_x = (cell_width - resized_w) // 2 + rng.randint(-max(1, cell_width // 12), max(1, cell_width // 12)) + offset_y = (cell_height - resized_h) // 2 + rng.randint(-max(1, cell_height // 12), max(1, cell_height // 12)) + target.paste(resized, (offset_x, offset_y)) + return target + + +def augment_digit_cell(image: Image.Image, rng: random.Random, strong: bool = True) -> Image.Image: + source = image.convert("L") + width, height = source.size + + scale = rng.uniform(0.88, 1.12 if strong else 1.06) + scaled_w = max(8, int(round(width * scale))) + scaled_h = max(8, int(round(height * scale))) + scaled = source.resize((scaled_w, scaled_h), Image.Resampling.BILINEAR) + canvas = Image.new("L", (width, height), color=255) + jitter_x = rng.randint(-max(1, width // 11), max(1, width // 11)) + jitter_y = rng.randint(-max(1, height // 11), max(1, height // 11)) + paste_x = (width - scaled_w) // 2 + jitter_x + paste_y = (height - scaled_h) // 2 + jitter_y + canvas.paste(scaled, (paste_x, paste_y)) + + angle_limit = 8.5 if strong else 4.5 + rotated = canvas.rotate( + rng.uniform(-angle_limit, angle_limit), + resample=Image.Resampling.BILINEAR, + fillcolor=255 + ) + + warped = apply_perspective_quad(rotated, rng, jitter_ratio=0.075 if strong else 0.04) + adjusted = apply_brightness_contrast(warped, rng) + if rng.random() < (0.75 if strong else 0.45): + adjusted = apply_noise(adjusted, rng) + if rng.random() < (0.68 if strong else 0.35): + adjusted = adjusted.filter(ImageFilter.GaussianBlur(radius=rng.uniform(0.15, 1.1 if strong else 0.6))) + if rng.random() < (0.45 if strong else 0.2): + adjusted = apply_edge_clip(adjusted, rng) + if rng.random() < (0.3 if strong else 0.1): + adjusted = apply_partial_occlusion(adjusted, rng) + return adjusted + + +def split_equispaced_major(image: Image.Image, count: int) -> list[Image.Image]: + width, height = image.size + sections: list[Image.Image] = [] + if width >= height: + for index in range(count): + x0 = int(round((index * width) / count)) + x1 = int(round(((index + 1) * width) / count)) + x1 = max(x0 + 1, x1) + sections.append(image.crop((x0, 0, x1, height))) + else: + for index in range(count): + y0 = int(round((index * height) / count)) + y1 = int(round(((index + 1) * height) / count)) + y1 = max(y0 + 1, y1) + sections.append(image.crop((0, y0, width, y1))) + return sections + + +def compose_synthetic_window( + digit_pools: dict[str, list[SourceSample]], + rng: random.Random +) -> tuple[Image.Image, list[str], list[str]]: + available_digits = [digit for digit in DEFAULT_DIGITS if digit_pools.get(digit)] + if not available_digits: + raise RuntimeError("No source digits available for synthetic composition.") + + choices = rng.choices(available_digits, k=4) + source_paths: list[str] = [] + patches: list[Image.Image] = [] + for digit in choices: + sample = rng.choice(digit_pools[digit]) + source_paths.append(str(sample.path)) + with Image.open(sample.path) as image_file: + patch = image_file.convert("L") + patches.append(augment_digit_cell(patch, rng, strong=False)) + + cell_width = rng.randint(54, 94) + cell_height = rng.randint(62, 116) + window = Image.new("L", (cell_width * 4, cell_height), color=255) + + for index, patch in enumerate(patches): + cell = fit_patch_into_cell(patch, cell_width, cell_height, rng) + x0 = index * cell_width + window.paste(cell, (x0, 0)) + + window = apply_perspective_quad(window, rng, jitter_ratio=0.04) + window = apply_brightness_contrast(window, rng) + if rng.random() < 0.55: + window = window.filter(ImageFilter.GaussianBlur(radius=rng.uniform(0.1, 0.7))) + if rng.random() < 0.5: + window = apply_noise(window, rng) + if rng.random() < 0.22: + window = apply_edge_clip(window, rng) + + return window, choices, source_paths + + +def main() -> None: + args = parse_args() + base_dir = Path(__file__).resolve().parent + dataset_root = resolve_path(base_dir, args.dataset_root) + output_root = resolve_path(base_dir, args.output_root) + + if not dataset_root.exists(): + raise FileNotFoundError(f"Dataset root not found: {dataset_root}") + if args.direct_per_real < 0: + raise ValueError("--direct-per-real must be >= 0.") + if args.compose_window_count < 0: + raise ValueError("--compose-window-count must be >= 0.") + + if args.clean and output_root.exists(): + shutil.rmtree(output_root) + ensure_output_structure(output_root) + + rng = random.Random(args.seed) + np.random.seed(args.seed) + train_samples = collect_train_samples(dataset_root) + if not train_samples: + raise RuntimeError(f"No train samples found under: {dataset_root / 'train'}") + + pools: dict[str, list[SourceSample]] = {digit: [] for digit in DEFAULT_DIGITS} + for sample in train_samples: + pools[sample.digit].append(sample) + + windows_dir = output_root / "windows" if args.save_composed_windows else None + if windows_dir: + windows_dir.mkdir(parents=True, exist_ok=True) + + manifest_rows: list[dict[str, str]] = [] + direct_count = 0 + composed_count = 0 + digit_counts = {digit: 0 for digit in DEFAULT_DIGITS} + + for sample in train_samples: + with Image.open(sample.path) as image_file: + original = image_file.convert("L") + stem = sample.path.stem + for replicate in range(args.direct_per_real): + synthetic = augment_digit_cell(original, rng, strong=True) + file_name = f"{stem}__direct_r{replicate:02d}.png" + out_path = output_root / "train" / sample.digit / file_name + synthetic.save(out_path) + direct_count += 1 + digit_counts[sample.digit] += 1 + manifest_rows.append({ + "kind": "direct", + "digit": sample.digit, + "output_path": str(out_path.relative_to(output_root)), + "source_path": str(sample.path.relative_to(dataset_root)), + "window_id": "", + "cell_index": "", + "sequence": sample.digit + }) + + for window_index in range(args.compose_window_count): + window, sequence, source_paths = compose_synthetic_window(pools, rng) + sections = split_equispaced_major(window, 4) + if len(sections) != 4: + continue + + if windows_dir: + window_name = f"synthetic_window_{window_index:05d}.png" + window.save(windows_dir / window_name) + + for cell_index, section in enumerate(sections): + digit = sequence[cell_index] + file_name = f"synthetic_window_{window_index:05d}_s{cell_index}_d{digit}.png" + out_path = output_root / "train" / digit / file_name + section.save(out_path) + composed_count += 1 + digit_counts[digit] += 1 + manifest_rows.append({ + "kind": "composed", + "digit": digit, + "output_path": str(out_path.relative_to(output_root)), + "source_path": "|".join([ + str(Path(path).relative_to(dataset_root)) + for path in source_paths + ]), + "window_id": str(window_index), + "cell_index": str(cell_index), + "sequence": "".join(sequence) + }) + + manifest_path = output_root / "manifests" / "synthetic_cells.csv" + with manifest_path.open("w", newline="", encoding="utf-8") as manifest_file: + writer = csv.DictWriter( + manifest_file, + fieldnames=[ + "kind", + "digit", + "output_path", + "source_path", + "window_id", + "cell_index", + "sequence" + ] + ) + writer.writeheader() + writer.writerows(manifest_rows) + + summary = { + "dataset_root": str(dataset_root), + "output_root": str(output_root), + "seed": args.seed, + "train_source_count": len(train_samples), + "direct_per_real": args.direct_per_real, + "compose_window_count": args.compose_window_count, + "direct_cells": direct_count, + "composed_cells": composed_count, + "total_cells": direct_count + composed_count, + "digit_counts": digit_counts, + "manifest_path": str(manifest_path) + } + summary_path = output_root / "manifests" / "summary.json" + summary_path.write_text(json.dumps(summary, indent=2), encoding="utf-8") + + print(f"Train source cells: {len(train_samples)}") + print(f"Synthetic direct cells: {direct_count}") + print(f"Synthetic composed cells: {composed_count}") + print(f"Synthetic total cells: {direct_count + composed_count}") + print("Synthetic digit counts:") + for digit in DEFAULT_DIGITS: + print(f" {digit}: {digit_counts[digit]}") + print(f"Manifest: {manifest_path}") + print(f"Summary: {summary_path}") + + +if __name__ == "__main__": + main() diff --git a/backend/label_digit_sections.py b/backend/label_digit_sections.py new file mode 100644 index 0000000..456b0fd --- /dev/null +++ b/backend/label_digit_sections.py @@ -0,0 +1,222 @@ +from __future__ import annotations + +import argparse +import csv +import json +import shutil +from pathlib import Path + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description="Assign digit labels to canonical window sections from reading strings." + ) + parser.add_argument( + "--dataset-dir", + default="data/digit_dataset", + help="Dataset root containing manifests/sections.csv and sections/." + ) + parser.add_argument( + "--sections-manifest", + default="manifests/sections.csv", + help="Path to sections manifest, relative to --dataset-dir unless absolute." + ) + parser.add_argument( + "--section-count", + type=int, + default=4, + help="Expected section count/read length." + ) + parser.add_argument( + "--clean", + action="store_true", + help="Delete existing labeled section output before writing." + ) + return parser.parse_args() + + +def resolve(base: Path, value: str) -> Path: + path = Path(value) + if path.is_absolute(): + return path + return (base / path).resolve() + + +def ensure_dirs(dataset_dir: Path) -> None: + (dataset_dir / "sections_labeled").mkdir(parents=True, exist_ok=True) + (dataset_dir / "manifests").mkdir(parents=True, exist_ok=True) + for split in ("train", "val", "test"): + for digit in range(10): + (dataset_dir / "sections_labeled" / split / str(digit)).mkdir(parents=True, exist_ok=True) + + +def write_csv(path: Path, rows: list[dict[str, str]], headers: list[str]) -> None: + with path.open("w", encoding="utf-8", newline="") as handle: + writer = csv.DictWriter(handle, fieldnames=headers) + writer.writeheader() + writer.writerows(rows) + + +def parse_int(raw: str) -> int | None: + try: + return int(raw) + except (TypeError, ValueError): + return None + + +def main() -> None: + args = parse_args() + base_dir = Path(__file__).resolve().parent + + dataset_dir = resolve(base_dir, args.dataset_dir) + sections_manifest_path = resolve(dataset_dir, args.sections_manifest) + + if args.section_count <= 0: + raise ValueError("--section-count must be positive.") + if not dataset_dir.exists(): + raise FileNotFoundError(f"Dataset dir not found: {dataset_dir}") + if not sections_manifest_path.exists(): + raise FileNotFoundError(f"Sections manifest not found: {sections_manifest_path}") + + labeled_root = dataset_dir / "sections_labeled" + if args.clean and labeled_root.exists(): + shutil.rmtree(labeled_root) + ensure_dirs(dataset_dir) + + labels_rows: list[dict[str, str]] = [] + skipped_rows: list[dict[str, str]] = [] + split_counts = {"train": 0, "val": 0, "test": 0} + digit_counts = {str(d): 0 for d in range(10)} + + with sections_manifest_path.open("r", encoding="utf-8") as handle: + reader = csv.DictReader(handle) + for row in reader: + split = (row.get("split") or "").strip() + filename = (row.get("filename") or "").strip() + reading = (row.get("reading") or "").strip() + section_index_raw = (row.get("section_index") or "").strip() + section_rel = (row.get("section_path") or "").strip() + canonical_window_path = (row.get("canonical_window_path") or "").strip() + source_window_path = (row.get("source_window_path") or "").strip() + + if split not in {"train", "val", "test"}: + skipped_rows.append({ + "split": split, + "filename": filename, + "section_index": section_index_raw, + "section_path": section_rel, + "reason": "invalid-split" + }) + continue + + section_index = parse_int(section_index_raw) + if section_index is None: + skipped_rows.append({ + "split": split, + "filename": filename, + "section_index": section_index_raw, + "section_path": section_rel, + "reason": "invalid-section-index" + }) + continue + + if section_index < 0 or section_index >= args.section_count: + skipped_rows.append({ + "split": split, + "filename": filename, + "section_index": section_index_raw, + "section_path": section_rel, + "reason": "section-index-out-of-range" + }) + continue + + if len(reading) != args.section_count or not reading.isdigit(): + skipped_rows.append({ + "split": split, + "filename": filename, + "section_index": section_index_raw, + "section_path": section_rel, + "reason": "invalid-reading" + }) + continue + + section_path = dataset_dir / section_rel + if not section_path.exists(): + skipped_rows.append({ + "split": split, + "filename": filename, + "section_index": section_index_raw, + "section_path": section_rel, + "reason": "missing-section-file" + }) + continue + + digit = reading[section_index] + labeled_name = f"{section_path.stem}_d{digit}{section_path.suffix.lower() or '.png'}" + labeled_path = labeled_root / split / digit / labeled_name + shutil.copy2(section_path, labeled_path) + + labels_rows.append({ + "split": split, + "filename": filename, + "reading": reading, + "section_index": str(section_index), + "digit": digit, + "section_path": section_rel, + "labeled_path": str(labeled_path.relative_to(dataset_dir)), + "source_window_path": source_window_path, + "canonical_window_path": canonical_window_path + }) + split_counts[split] += 1 + digit_counts[digit] += 1 + + write_csv( + dataset_dir / "manifests" / "section_labels.csv", + labels_rows, + [ + "split", + "filename", + "reading", + "section_index", + "digit", + "section_path", + "labeled_path", + "source_window_path", + "canonical_window_path" + ] + ) + write_csv( + dataset_dir / "manifests" / "section_labels_skipped.csv", + skipped_rows, + ["split", "filename", "section_index", "section_path", "reason"] + ) + + summary = { + "labels_exported": len(labels_rows), + "labels_skipped": len(skipped_rows), + "section_count": args.section_count, + "split_counts": split_counts, + "digit_counts": digit_counts, + "dataset_dir": str(dataset_dir), + "sections_manifest": str(sections_manifest_path) + } + (dataset_dir / "manifests" / "section_labels_summary.json").write_text( + json.dumps(summary, indent=2), + encoding="utf-8" + ) + + print(f"Labeled sections exported under: {labeled_root}") + print(f"Labels exported: {summary['labels_exported']} (skipped={summary['labels_skipped']})") + print( + "Split labels: " + f"train={split_counts['train']} val={split_counts['val']} test={split_counts['test']}" + ) + print( + "Digit counts: " + + ", ".join([f"{digit}={digit_counts[digit]}" for digit in sorted(digit_counts.keys())]) + ) + print(f"Manifests: {dataset_dir / 'manifests'}") + + +if __name__ == "__main__": + main() diff --git a/backend/models/.gitkeep b/backend/models/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/backend/models/.gitkeep @@ -0,0 +1 @@ + diff --git a/backend/plan_digit_expansion.py b/backend/plan_digit_expansion.py new file mode 100644 index 0000000..b3cf85c --- /dev/null +++ b/backend/plan_digit_expansion.py @@ -0,0 +1,304 @@ +from __future__ import annotations + +import argparse +import csv +import json +from dataclasses import dataclass +from datetime import datetime, timezone +from pathlib import Path + +DEFAULT_DIGIT_COUNT = 10 +DEFAULT_READING_WIDTH = 4 + + +@dataclass +class DigitCounts: + train: int = 0 + val: int = 0 + test: int = 0 + + @property + def total(self) -> int: + return self.train + self.val + self.test + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description="Plan targeted dataset expansion for underrepresented digit classes." + ) + parser.add_argument( + "--cells-manifest", + default="data/digit_dataset/manifests/cells.csv", + help="Path to exported cells manifest." + ) + parser.add_argument( + "--readings-csv", + default="../assets/meter_readings.csv", + help="Path to readings CSV used for seed value suggestions." + ) + parser.add_argument( + "--target-train-per-digit", + type=int, + default=12, + help="Desired minimum number of train samples per digit." + ) + parser.add_argument( + "--priority-digits", + default="4,5,6,9", + help="Comma-separated digit classes to prioritize." + ) + parser.add_argument( + "--max-suggestions-per-digit", + type=int, + default=10, + help="Maximum suggested reading labels to emit per priority digit." + ) + parser.add_argument( + "--out-json", + default="data/digit_dataset/manifests/capture_plan.json", + help="Output JSON plan path." + ) + parser.add_argument( + "--out-md", + default="data/digit_dataset/manifests/capture_plan.md", + help="Output markdown checklist path." + ) + return parser.parse_args() + + +def resolve(base_dir: Path, value: str) -> Path: + path = Path(value) + if path.is_absolute(): + return path + return (base_dir / path).resolve() + + +def parse_priority_digits(raw: str) -> list[str]: + result: list[str] = [] + seen = set() + for token in (raw or "").split(","): + value = token.strip() + if not value: + continue + if len(value) != 1 or value < "0" or value > "9": + raise ValueError(f"Invalid priority digit: {value}") + if value in seen: + continue + seen.add(value) + result.append(value) + if not result: + raise ValueError("At least one priority digit is required.") + return result + + +def load_digit_counts(cells_manifest_path: Path) -> dict[str, DigitCounts]: + counts = {str(digit): DigitCounts() for digit in range(DEFAULT_DIGIT_COUNT)} + with cells_manifest_path.open("r", encoding="utf-8") as handle: + reader = csv.DictReader(handle) + for row in reader: + digit = (row.get("digit") or "").strip() + split = (row.get("split") or "").strip().lower() + if digit not in counts: + continue + if split == "train": + counts[digit].train += 1 + elif split == "val": + counts[digit].val += 1 + elif split == "test": + counts[digit].test += 1 + return counts + + +def load_seed_reading(readings_csv_path: Path) -> str: + best_value = None + with readings_csv_path.open("r", encoding="utf-8") as handle: + reader = csv.DictReader(handle) + for row in reader: + value = (row.get("value") or "").strip() + if len(value) != DEFAULT_READING_WIDTH or not value.isdigit(): + continue + parsed = int(value) + if best_value is None or parsed > best_value: + best_value = parsed + if best_value is None: + return "2300" + return f"{best_value:0{DEFAULT_READING_WIDTH}d}" + + +def generate_label_suggestions(seed_label: str, digit: str, limit: int) -> list[str]: + if len(seed_label) != DEFAULT_READING_WIDTH or not seed_label.isdigit(): + seed_label = "2300" + candidates: list[str] = [] + + # Single-position replacements. + for position in range(DEFAULT_READING_WIDTH): + chars = list(seed_label) + chars[position] = digit + candidates.append("".join(chars)) + + # Two-position replacements. + for left in range(DEFAULT_READING_WIDTH): + for right in range(left + 1, DEFAULT_READING_WIDTH): + chars = list(seed_label) + chars[left] = digit + chars[right] = digit + candidates.append("".join(chars)) + + candidates.append(digit * DEFAULT_READING_WIDTH) + deduped = [] + seen = set() + for value in candidates: + if value in seen: + continue + seen.add(value) + deduped.append(value) + if len(deduped) >= limit: + break + return deduped + + +def build_plan_payload( + counts: dict[str, DigitCounts], + target_train_per_digit: int, + priority_digits: list[str], + seed_label: str, + max_suggestions_per_digit: int, + cells_manifest_path: Path, + readings_csv_path: Path +) -> dict: + digits_summary = [] + for digit in map(str, range(DEFAULT_DIGIT_COUNT)): + item = counts[digit] + train_deficit = max(0, target_train_per_digit - item.train) + digits_summary.append({ + "digit": digit, + "train": item.train, + "val": item.val, + "test": item.test, + "total": item.total, + "train_deficit": train_deficit + }) + + priority_actions = [] + for rank, digit in enumerate(priority_digits, start=1): + item = counts[digit] + deficit = max(0, target_train_per_digit - item.train) + priority_actions.append({ + "priority_rank": rank, + "digit": digit, + "current_train": item.train, + "target_train": target_train_per_digit, + "train_deficit": deficit, + "suggested_labels": generate_label_suggestions(seed_label, digit, max_suggestions_per_digit) + }) + + return { + "generated_at": datetime.now(timezone.utc).isoformat(), + "inputs": { + "cells_manifest": str(cells_manifest_path), + "readings_csv": str(readings_csv_path), + "target_train_per_digit": target_train_per_digit, + "priority_digits": priority_digits, + "seed_label": seed_label + }, + "digits": digits_summary, + "priority_actions": priority_actions + } + + +def render_markdown(plan: dict) -> str: + lines = [] + lines.append("# Digit Capture Plan") + lines.append("") + lines.append(f"- Generated: `{plan['generated_at']}`") + lines.append(f"- Target train samples per digit: `{plan['inputs']['target_train_per_digit']}`") + lines.append(f"- Priority digits: `{','.join(plan['inputs']['priority_digits'])}`") + lines.append(f"- Seed label for examples: `{plan['inputs']['seed_label']}`") + lines.append("") + lines.append("## Coverage Snapshot") + lines.append("") + lines.append("| Digit | Train | Val | Test | Total | Train Deficit |") + lines.append("| --- | ---: | ---: | ---: | ---: | ---: |") + for item in plan["digits"]: + lines.append( + f"| {item['digit']} | {item['train']} | {item['val']} | {item['test']} | " + f"{item['total']} | {item['train_deficit']} |" + ) + lines.append("") + lines.append("## Priority Checklist") + lines.append("") + active_items = [item for item in plan["priority_actions"] if item["train_deficit"] > 0] + if not active_items: + lines.append("- No deficits for priority digits.") + lines.append("") + return "\n".join(lines) + + for item in active_items: + lines.append( + f"- [ ] Digit `{item['digit']}`: collect at least `{item['train_deficit']}` additional " + "train occurrences." + ) + lines.append( + f" Current train count: `{item['current_train']}`; " + f"target: `{item['target_train']}`." + ) + suggestions = ", ".join([f"`{value}`" for value in item["suggested_labels"]]) + lines.append(f" Suggested reading labels to target: {suggestions}") + lines.append("") + + lines.append("## QA Loop") + lines.append("") + lines.append( + "- After adding labels, rebuild dataset and run `python validate_digit_dataset.py` " + "before training." + ) + return "\n".join(lines) + + +def main() -> None: + args = parse_args() + base_dir = Path(__file__).resolve().parent + cells_manifest_path = resolve(base_dir, args.cells_manifest) + readings_csv_path = resolve(base_dir, args.readings_csv) + out_json_path = resolve(base_dir, args.out_json) + out_md_path = resolve(base_dir, args.out_md) + + if not cells_manifest_path.exists(): + raise FileNotFoundError(f"Cells manifest not found: {cells_manifest_path}") + if not readings_csv_path.exists(): + raise FileNotFoundError(f"Readings CSV not found: {readings_csv_path}") + if args.target_train_per_digit <= 0: + raise ValueError("--target-train-per-digit must be positive.") + if args.max_suggestions_per_digit <= 0: + raise ValueError("--max-suggestions-per-digit must be positive.") + + priority_digits = parse_priority_digits(args.priority_digits) + counts = load_digit_counts(cells_manifest_path) + seed_label = load_seed_reading(readings_csv_path) + plan = build_plan_payload( + counts=counts, + target_train_per_digit=args.target_train_per_digit, + priority_digits=priority_digits, + seed_label=seed_label, + max_suggestions_per_digit=args.max_suggestions_per_digit, + cells_manifest_path=cells_manifest_path, + readings_csv_path=readings_csv_path + ) + + out_json_path.parent.mkdir(parents=True, exist_ok=True) + out_md_path.parent.mkdir(parents=True, exist_ok=True) + out_json_path.write_text(json.dumps(plan, indent=2), encoding="utf-8") + out_md_path.write_text(render_markdown(plan), encoding="utf-8") + + print(f"Wrote JSON plan: {out_json_path}") + print(f"Wrote markdown checklist: {out_md_path}") + print("Priority deficits:") + for item in plan["priority_actions"]: + print( + f" digit {item['digit']}: current_train={item['current_train']} " + f"target={item['target_train']} deficit={item['train_deficit']}" + ) + + +if __name__ == "__main__": + main() diff --git a/backend/requirements-cpu.txt b/backend/requirements-cpu.txt new file mode 100644 index 0000000..d23326f --- /dev/null +++ b/backend/requirements-cpu.txt @@ -0,0 +1,9 @@ +--index-url https://download.pytorch.org/whl/cpu +--extra-index-url https://pypi.org/simple + +# Force CPU PyTorch wheels for serverless / no-GPU environments. +torch +torchvision +torchaudio + +-r requirements.txt diff --git a/backend/requirements.txt b/backend/requirements.txt new file mode 100644 index 0000000..83c4e1a --- /dev/null +++ b/backend/requirements.txt @@ -0,0 +1,6 @@ +fastapi>=0.111,<1 +uvicorn[standard]>=0.30,<1 +python-multipart>=0.0.9,<1 +pillow>=10,<12 +numpy>=1.26,<3 +ultralytics>=8.2,<9 diff --git a/backend/split_digit_windows.py b/backend/split_digit_windows.py new file mode 100644 index 0000000..181e3aa --- /dev/null +++ b/backend/split_digit_windows.py @@ -0,0 +1,370 @@ +from __future__ import annotations + +import argparse +import csv +import json +import shutil +from pathlib import Path + +from PIL import Image + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description="Split digit-window crops into equal sections along each window's major dimension." + ) + parser.add_argument( + "--dataset-dir", + default="data/digit_dataset", + help="Dataset root containing manifests/windows.csv and windows/." + ) + parser.add_argument( + "--windows-manifest", + default="manifests/windows.csv", + help="Path to windows manifest, relative to --dataset-dir unless absolute." + ) + parser.add_argument( + "--section-count", + type=int, + default=4, + help="Number of equal sections per window." + ) + parser.add_argument( + "--direction-overrides", + default="manifests/direction_overrides.csv", + help="Optional CSV with filename,flip180 (0/1) to override reading-direction flips." + ) + parser.add_argument( + "--clean", + action="store_true", + help="Delete existing sections and canonical windows output before writing." + ) + return parser.parse_args() + + +def resolve(base: Path, value: str) -> Path: + path = Path(value) + if path.is_absolute(): + return path + return (base / path).resolve() + + +def ensure_dirs(dataset_dir: Path) -> None: + (dataset_dir / "windows_canonical").mkdir(parents=True, exist_ok=True) + (dataset_dir / "sections").mkdir(parents=True, exist_ok=True) + (dataset_dir / "manifests").mkdir(parents=True, exist_ok=True) + for split in ("train", "val", "test"): + (dataset_dir / "windows_canonical" / split).mkdir(parents=True, exist_ok=True) + (dataset_dir / "sections" / split).mkdir(parents=True, exist_ok=True) + + +def write_csv(path: Path, rows: list[dict[str, str]], headers: list[str]) -> None: + with path.open("w", encoding="utf-8", newline="") as handle: + writer = csv.DictWriter(handle, fieldnames=headers) + writer.writeheader() + writer.writerows(rows) + + +def section_bounds(length: int, count: int, index: int) -> tuple[int, int]: + start = int(round(index * length / count)) + end = int(round((index + 1) * length / count)) + if end <= start: + end = min(length, start + 1) + return start, end + + +def major_axis(width: int, height: int) -> str: + return "x" if width >= height else "y" + + +def rotate_right_angle(image: Image.Image, angle: int) -> Image.Image: + normalized = angle % 360 + if normalized == 0: + return image.copy() + return image.rotate(normalized, expand=True) + + +def ink_lowerness_score(image: Image.Image) -> float: + gray = image.convert("L") + width, height = gray.size + pixels = gray.load() + denom_y = max(1, height - 1) + total = 0.0 + weighted = 0.0 + for y in range(height): + y_weight = y / denom_y + for x in range(width): + lum = pixels[x, y] + ink = max(0, 220 - int(lum)) + if ink <= 0: + continue + total += ink + weighted += ink * y_weight + if total <= 0: + return 0.5 + return weighted / total + + +def parse_bool_token(raw: str) -> bool: + value = (raw or "").strip().lower() + return value in {"1", "true", "yes", "y", "flip"} + + +def load_direction_overrides(path: Path) -> dict[str, bool]: + if not path.exists(): + return {} + mapping: dict[str, bool] = {} + with path.open("r", encoding="utf-8") as handle: + reader = csv.DictReader(handle) + for row in reader: + filename = (row.get("filename") or "").strip() + if not filename: + continue + flip_raw = row.get("flip180") + if flip_raw is None: + flip_raw = row.get("flip") + mapping[filename] = parse_bool_token(flip_raw or "") + return mapping + + +def canonicalize_window_orientation( + source: Image.Image, + filename: str, + direction_overrides: dict[str, bool] +) -> tuple[Image.Image, dict[str, str]]: + source_major_axis = major_axis(source.width, source.height) + # For vertical windows, rotate clockwise to horizontal first. + axis_rotation = 0 if source_major_axis == "x" else 270 + + primary = rotate_right_angle(source, axis_rotation) + flipped = rotate_right_angle(primary, 180) + + primary_score = ink_lowerness_score(primary) + flipped_score = ink_lowerness_score(flipped) + + direction_flip = bool(direction_overrides.get(filename, False)) + direction_source = "override" if filename in direction_overrides else "default" + if direction_flip: + canonical = flipped + applied_rotation = (axis_rotation + 180) % 360 + else: + canonical = primary + applied_rotation = axis_rotation + + metadata = { + "source_major_axis": source_major_axis, + "canonical_major_axis": major_axis(canonical.width, canonical.height), + "axis_rotation": str(axis_rotation), + "direction_flip": "1" if direction_flip else "0", + "direction_source": direction_source, + "applied_rotation": str(applied_rotation), + "primary_lowerness": f"{primary_score:.6f}", + "flipped_lowerness": f"{flipped_score:.6f}" + } + return canonical, metadata + + +def main() -> None: + args = parse_args() + base_dir = Path(__file__).resolve().parent + + dataset_dir = resolve(base_dir, args.dataset_dir) + manifest_path = resolve(dataset_dir, args.windows_manifest) + direction_overrides_path = resolve(dataset_dir, args.direction_overrides) + + if args.section_count <= 0: + raise ValueError("--section-count must be positive.") + if not dataset_dir.exists(): + raise FileNotFoundError(f"Dataset dir not found: {dataset_dir}") + if not manifest_path.exists(): + raise FileNotFoundError(f"Windows manifest not found: {manifest_path}") + + sections_dir = dataset_dir / "sections" + canonical_dir = dataset_dir / "windows_canonical" + if args.clean and sections_dir.exists(): + shutil.rmtree(sections_dir) + if args.clean and canonical_dir.exists(): + shutil.rmtree(canonical_dir) + ensure_dirs(dataset_dir) + direction_overrides = load_direction_overrides(direction_overrides_path) + + canonical_rows: list[dict[str, str]] = [] + rows: list[dict[str, str]] = [] + skipped: list[dict[str, str]] = [] + split_counts = {"train": 0, "val": 0, "test": 0} + source_axis_counts = {"x": 0, "y": 0} + canonical_axis_counts = {"x": 0, "y": 0} + rotation_counts = {"0": 0, "90": 0, "180": 0, "270": 0} + + with manifest_path.open("r", encoding="utf-8") as handle: + reader = csv.DictReader(handle) + for row in reader: + split = (row.get("split") or "").strip() + filename = (row.get("filename") or "").strip() + reading = (row.get("reading") or "").strip() + window_rel = (row.get("window_path") or "").strip() + if split not in {"train", "val", "test"}: + skipped.append({"split": split, "filename": filename, "reason": "invalid-split"}) + continue + if not window_rel: + skipped.append({"split": split, "filename": filename, "reason": "missing-window-path"}) + continue + + window_path = dataset_dir / window_rel + if not window_path.exists(): + skipped.append({"split": split, "filename": filename, "reason": "missing-window-file"}) + continue + + with Image.open(window_path) as image: + source = image.convert("RGB") + canonical, orientation_meta = canonicalize_window_orientation( + source, + filename, + direction_overrides + ) + width, height = canonical.size + source_major_axis = orientation_meta["source_major_axis"] + canonical_major_axis = orientation_meta["canonical_major_axis"] + source_axis_counts[source_major_axis] += 1 + canonical_axis_counts[canonical_major_axis] += 1 + applied_rotation = orientation_meta["applied_rotation"] + if applied_rotation in rotation_counts: + rotation_counts[applied_rotation] += 1 + + canonical_path = dataset_dir / "windows_canonical" / split / f"{Path(window_path).stem}.png" + canonical.save(canonical_path) + canonical_rel = str(canonical_path.relative_to(dataset_dir)) + + canonical_rows.append({ + "split": split, + "filename": filename, + "reading": reading, + "source_window_path": window_rel, + "canonical_window_path": canonical_rel, + "source_width": str(source.width), + "source_height": str(source.height), + "canonical_width": str(width), + "canonical_height": str(height), + "source_major_axis": source_major_axis, + "canonical_major_axis": canonical_major_axis, + "axis_rotation": orientation_meta["axis_rotation"], + "direction_flip": orientation_meta["direction_flip"], + "direction_source": orientation_meta["direction_source"], + "applied_rotation": applied_rotation, + "primary_lowerness": orientation_meta["primary_lowerness"], + "flipped_lowerness": orientation_meta["flipped_lowerness"] + }) + + for section_index in range(args.section_count): + x0, x1 = section_bounds(width, args.section_count, section_index) + y0, y1 = 0, height + + section = canonical.crop((x0, y0, x1, y1)) + section_name = f"{Path(window_path).stem}_s{section_index}.png" + section_path = dataset_dir / "sections" / split / section_name + section.save(section_path) + + rows.append({ + "split": split, + "filename": filename, + "reading": reading, + "source_window_path": window_rel, + "canonical_window_path": canonical_rel, + "section_index": str(section_index), + "major_axis": canonical_major_axis, + "applied_rotation": applied_rotation, + "x0": str(x0), + "y0": str(y0), + "x1": str(x1), + "y1": str(y1), + "section_path": str(section_path.relative_to(dataset_dir)) + }) + + split_counts[split] += 1 + + write_csv( + dataset_dir / "manifests" / "canonical_windows.csv", + canonical_rows, + [ + "split", + "filename", + "reading", + "source_window_path", + "canonical_window_path", + "source_width", + "source_height", + "canonical_width", + "canonical_height", + "source_major_axis", + "canonical_major_axis", + "axis_rotation", + "direction_flip", + "direction_source", + "applied_rotation", + "primary_lowerness", + "flipped_lowerness" + ] + ) + write_csv( + dataset_dir / "manifests" / "sections.csv", + rows, + [ + "split", + "filename", + "reading", + "source_window_path", + "canonical_window_path", + "section_index", + "major_axis", + "applied_rotation", + "x0", + "y0", + "x1", + "y1", + "section_path" + ] + ) + write_csv( + dataset_dir / "manifests" / "sections_skipped.csv", + skipped, + ["split", "filename", "reason"] + ) + + summary = { + "windows_processed": sum(split_counts.values()), + "sections_exported": len(rows), + "windows_skipped": len(skipped), + "section_count": args.section_count, + "splits": split_counts, + "source_axis_counts": source_axis_counts, + "canonical_axis_counts": canonical_axis_counts, + "applied_rotation_counts": rotation_counts, + "dataset_dir": str(dataset_dir), + "windows_manifest": str(manifest_path), + "direction_overrides": str(direction_overrides_path), + "direction_override_count": len(direction_overrides) + } + (dataset_dir / "manifests" / "sections_summary.json").write_text( + json.dumps(summary, indent=2), + encoding="utf-8" + ) + + print(f"Sections exported under: {dataset_dir / 'sections'}") + print(f"Windows processed: {summary['windows_processed']} (skipped={summary['windows_skipped']})") + print(f"Sections exported: {summary['sections_exported']}") + print( + "Split windows: " + f"train={split_counts['train']} val={split_counts['val']} test={split_counts['test']}" + ) + print(f"Source major axis counts: x={source_axis_counts['x']} y={source_axis_counts['y']}") + print(f"Canonical major axis counts: x={canonical_axis_counts['x']} y={canonical_axis_counts['y']}") + print( + "Applied rotations: " + f"0={rotation_counts['0']} 90={rotation_counts['90']} " + f"180={rotation_counts['180']} 270={rotation_counts['270']}" + ) + print(f"Manifests: {dataset_dir / 'manifests'}") + + +if __name__ == "__main__": + main() diff --git a/backend/train_digit_classifier.py b/backend/train_digit_classifier.py new file mode 100644 index 0000000..438bfd9 --- /dev/null +++ b/backend/train_digit_classifier.py @@ -0,0 +1,626 @@ +from __future__ import annotations + +import argparse +import json +import random +import time +from dataclasses import dataclass +from pathlib import Path + +import numpy as np +import torch +from PIL import Image, ImageEnhance +from torch import nn +from torch.utils.data import DataLoader, Dataset, WeightedRandomSampler + +try: + from .digit_model import ( + DEFAULT_CLASS_NAMES, + DEFAULT_NUM_CLASSES, + DEFAULT_IMAGE_SIZE, + build_digit_cnn, + prepare_digit_tensor + ) +except ImportError: + from digit_model import ( + DEFAULT_CLASS_NAMES, + DEFAULT_NUM_CLASSES, + DEFAULT_IMAGE_SIZE, + build_digit_cnn, + prepare_digit_tensor + ) + +SUPPORTED_IMAGE_SUFFIXES = {".jpg", ".jpeg", ".png", ".bmp", ".webp"} + + +@dataclass +class Sample: + path: Path + label: int + + +class DigitCellDataset(Dataset): + def __init__( + self, + samples: list[Sample], + image_size: int, + augment: bool = False, + split_jitter_x: float = 0.0, + split_jitter_y: float = 0.0, + split_jitter_prob: float = 0.0 + ): + self.samples = samples + self.image_size = image_size + self.augment = augment + self.split_jitter_x = max(0.0, float(split_jitter_x)) + self.split_jitter_y = max(0.0, float(split_jitter_y)) + self.split_jitter_prob = min(1.0, max(0.0, float(split_jitter_prob))) + + def __len__(self) -> int: + return len(self.samples) + + def _augment_image(self, image: Image.Image) -> Image.Image: + if not self.augment: + return image + if self.split_jitter_prob > 0 and random.random() < self.split_jitter_prob: + max_dx = int(round(image.width * self.split_jitter_x)) + max_dy = int(round(image.height * self.split_jitter_y)) + if max_dx > 0 or max_dy > 0: + dx = random.randint(-max_dx, max_dx) if max_dx > 0 else 0 + dy = random.randint(-max_dy, max_dy) if max_dy > 0 else 0 + shifted = Image.new("L", image.size, color=255) + src_x0 = max(0, -dx) + src_y0 = max(0, -dy) + src_x1 = min(image.width, image.width - dx) if dx >= 0 else image.width + src_y1 = min(image.height, image.height - dy) if dy >= 0 else image.height + if src_x1 > src_x0 and src_y1 > src_y0: + patch = image.crop((src_x0, src_y0, src_x1, src_y1)) + dst_x = max(0, dx) + dst_y = max(0, dy) + shifted.paste(patch, (dst_x, dst_y)) + image = shifted + if random.random() < 0.9: + if hasattr(Image, "Resampling"): + image = image.rotate( + random.uniform(-6.5, 6.5), + resample=Image.Resampling.BILINEAR, + fillcolor=255 + ) + else: + image = image.rotate( + random.uniform(-6.5, 6.5), + resample=Image.BILINEAR, + fillcolor=255 + ) + if random.random() < 0.6: + image = ImageEnhance.Contrast(image).enhance(random.uniform(0.85, 1.3)) + if random.random() < 0.5: + image = ImageEnhance.Brightness(image).enhance(random.uniform(0.88, 1.15)) + return image + + def __getitem__(self, index: int): + sample = self.samples[index] + with Image.open(sample.path) as source: + image = source.convert("L") + image = self._augment_image(image) + tensor = prepare_digit_tensor(image, self.image_size) + return tensor, sample.label + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description="Train a per-cell digit classifier for the Jarvis OCR pipeline." + ) + parser.add_argument( + "--dataset-root", + default="data/digit_dataset/sections_labeled", + help="Dataset root with split folders (train/val/test)." + ) + parser.add_argument("--epochs", type=int, default=180) + parser.add_argument("--batch-size", type=int, default=16) + parser.add_argument("--learning-rate", type=float, default=1.2e-3) + parser.add_argument("--weight-decay", type=float, default=1.0e-4) + parser.add_argument("--patience", type=int, default=40) + parser.add_argument("--image-size", type=int, default=DEFAULT_IMAGE_SIZE) + parser.add_argument("--epoch-sample-multiplier", type=int, default=6) + parser.add_argument("--num-workers", type=int, default=0) + parser.add_argument( + "--split-jitter-x", + type=float, + default=0.08, + help="Max horizontal translation as a fraction of sample width for split-jitter augmentation." + ) + parser.add_argument( + "--split-jitter-y", + type=float, + default=0.08, + help="Max vertical translation as a fraction of sample height for split-jitter augmentation." + ) + parser.add_argument( + "--split-jitter-prob", + type=float, + default=0.85, + help="Probability of applying split-jitter augmentation on each train sample." + ) + parser.add_argument( + "--synthetic-root", + default="", + help=( + "Optional synthetic dataset root with train/ folders. " + "When provided, synthetic samples are mixed into train only." + ) + ) + parser.add_argument( + "--synthetic-target-ratio", + type=float, + default=0.0, + help=( + "Target synthetic-to-real ratio for effective training set (train split only). " + "Example: 2.0 means up to 2 synthetic samples per real sample." + ) + ) + parser.add_argument( + "--synthetic-seed", + type=int, + default=42, + help="Sampling seed used when selecting synthetic samples for the requested ratio." + ) + parser.add_argument("--seed", type=int, default=42) + parser.add_argument( + "--device", + default="auto", + help="Training device: auto, cpu, cuda, cuda:0, or GPU index." + ) + parser.add_argument("--project", default="runs") + parser.add_argument("--name", default="digit-classifier") + parser.add_argument("--copy-to", default="models/digit_classifier.pt") + return parser.parse_args() + + +def resolve_path(base_dir: Path, value: str) -> Path: + path = Path(value) + if path.is_absolute(): + return path + return (base_dir / path).resolve() + + +def resolve_device(raw: str) -> torch.device: + normalized = (raw or "").strip().lower() + if not normalized or normalized == "auto": + return torch.device("cuda:0" if torch.cuda.is_available() else "cpu") + if normalized in {"cpu", "mps"}: + return torch.device(normalized) + if normalized.isdigit(): + return torch.device(f"cuda:{normalized}") + return torch.device(normalized) + + +def set_seed(seed: int) -> None: + random.seed(seed) + np.random.seed(seed) + torch.manual_seed(seed) + if torch.cuda.is_available(): + torch.cuda.manual_seed_all(seed) + + +def collect_split_samples(dataset_root: Path, split: str) -> list[Sample]: + split_dir = dataset_root / split + if not split_dir.exists(): + return [] + + samples: list[Sample] = [] + for digit in range(DEFAULT_NUM_CLASSES): + digit_dir = split_dir / str(digit) + if not digit_dir.exists(): + continue + for image_path in sorted(digit_dir.iterdir()): + if not image_path.is_file() or image_path.suffix.lower() not in SUPPORTED_IMAGE_SUFFIXES: + continue + samples.append(Sample(path=image_path, label=digit)) + return samples + + +def count_labels(samples: list[Sample]) -> dict[str, int]: + counts = {str(index): 0 for index in range(DEFAULT_NUM_CLASSES)} + for sample in samples: + counts[str(sample.label)] += 1 + return counts + + +def format_counts(counts: dict[str, int]) -> str: + return ", ".join([f"{digit}:{counts[str(digit)]}" for digit in range(DEFAULT_NUM_CLASSES)]) + + +def select_synthetic_samples( + real_samples: list[Sample], + synthetic_samples: list[Sample], + target_ratio: float, + seed: int +) -> list[Sample]: + if not real_samples or not synthetic_samples: + return [] + if target_ratio <= 0: + return [] + + desired_total = int(round(len(real_samples) * target_ratio)) + if desired_total <= 0: + return [] + desired_total = min(desired_total, len(synthetic_samples)) + + rng = random.Random(seed) + real_counts = count_labels(real_samples) + synthetic_by_label: dict[int, list[Sample]] = {index: [] for index in range(DEFAULT_NUM_CLASSES)} + for sample in synthetic_samples: + synthetic_by_label[sample.label].append(sample) + + for bucket in synthetic_by_label.values(): + rng.shuffle(bucket) + + total_real = max(1, len(real_samples)) + selected: list[Sample] = [] + leftovers: list[Sample] = [] + for label in range(DEFAULT_NUM_CLASSES): + pool = synthetic_by_label[label] + if not pool: + continue + quota = int(round(desired_total * (real_counts[str(label)] / total_real))) + quota = min(quota, len(pool)) + if quota > 0: + selected.extend(pool[:quota]) + leftovers.extend(pool[quota:]) + else: + leftovers.extend(pool) + + if len(selected) > desired_total: + rng.shuffle(selected) + selected = selected[:desired_total] + return selected + + if len(selected) < desired_total and leftovers: + rng.shuffle(leftovers) + needed = desired_total - len(selected) + selected.extend(leftovers[:needed]) + + return selected + + +def make_class_weights(train_samples: list[Sample], device: torch.device) -> torch.Tensor: + label_counts = count_labels(train_samples) + class_weights = torch.zeros(DEFAULT_NUM_CLASSES, dtype=torch.float32, device=device) + non_zero_counts = [count for count in label_counts.values() if count > 0] + if not non_zero_counts: + return class_weights + 1.0 + + mean_count = float(sum(non_zero_counts)) / len(non_zero_counts) + for class_index in range(DEFAULT_NUM_CLASSES): + count = label_counts[str(class_index)] + if count <= 0: + class_weights[class_index] = 0.0 + continue + class_weights[class_index] = mean_count / float(count) + + return class_weights + + +def make_train_sampler(train_samples: list[Sample], multiplier: int) -> WeightedRandomSampler | None: + if multiplier <= 1: + return None + + label_counts = count_labels(train_samples) + sample_weights: list[float] = [] + for sample in train_samples: + count = label_counts[str(sample.label)] + sample_weights.append(1.0 / float(max(count, 1))) + + base_count = len(train_samples) + sampled_count = max(base_count * multiplier, base_count) + return WeightedRandomSampler( + weights=torch.tensor(sample_weights, dtype=torch.double), + num_samples=sampled_count, + replacement=True + ) + + +def run_epoch( + model: nn.Module, + loader: DataLoader, + criterion: nn.Module, + optimizer: torch.optim.Optimizer | None, + device: torch.device +) -> dict[str, float]: + training = optimizer is not None + if training: + model.train() + else: + model.eval() + + total_loss = 0.0 + total_count = 0 + correct = 0 + + for inputs, targets in loader: + inputs = inputs.to(device, non_blocking=True) + targets = targets.to(device, non_blocking=True) + if training: + optimizer.zero_grad(set_to_none=True) + with torch.set_grad_enabled(training): + logits = model(inputs) + loss = criterion(logits, targets) + if training: + loss.backward() + optimizer.step() + + batch_size = targets.shape[0] + total_loss += float(loss.item()) * batch_size + total_count += batch_size + predictions = torch.argmax(logits, dim=1) + correct += int((predictions == targets).sum().item()) + + if total_count == 0: + return {"loss": 0.0, "accuracy": 0.0} + + return { + "loss": total_loss / total_count, + "accuracy": correct / total_count + } + + +def state_dict_to_cpu(model: nn.Module) -> dict: + return { + key: value.detach().cpu().clone() + for key, value in model.state_dict().items() + } + + +def main() -> None: + args = parse_args() + base_dir = Path(__file__).resolve().parent + dataset_root = resolve_path(base_dir, args.dataset_root) + synthetic_root = resolve_path(base_dir, args.synthetic_root) if args.synthetic_root else None + project_root = resolve_path(base_dir, args.project) + output_path = resolve_path(base_dir, args.copy_to) + run_dir = project_root / args.name + run_dir.mkdir(parents=True, exist_ok=True) + + if not dataset_root.exists(): + raise FileNotFoundError(f"Dataset root not found: {dataset_root}") + if synthetic_root and not synthetic_root.exists(): + raise FileNotFoundError(f"Synthetic root not found: {synthetic_root}") + if args.image_size <= 0: + raise ValueError("--image-size must be positive.") + if args.batch_size <= 0: + raise ValueError("--batch-size must be positive.") + if args.epochs <= 0: + raise ValueError("--epochs must be positive.") + if args.split_jitter_x < 0: + raise ValueError("--split-jitter-x must be >= 0.") + if args.split_jitter_y < 0: + raise ValueError("--split-jitter-y must be >= 0.") + if args.split_jitter_prob < 0 or args.split_jitter_prob > 1: + raise ValueError("--split-jitter-prob must be in [0, 1].") + if args.synthetic_target_ratio < 0: + raise ValueError("--synthetic-target-ratio must be >= 0.") + + set_seed(args.seed) + device = resolve_device(args.device) + if device.type.startswith("cuda") and not torch.cuda.is_available(): + raise RuntimeError("CUDA device requested but CUDA is not available.") + + real_train_samples = collect_split_samples(dataset_root, "train") + val_samples = collect_split_samples(dataset_root, "val") + test_samples = collect_split_samples(dataset_root, "test") + if not real_train_samples: + raise RuntimeError(f"No train samples found under {dataset_root / 'train'}") + + synthetic_train_all = ( + collect_split_samples(synthetic_root, "train") + if synthetic_root is not None + else [] + ) + synthetic_train_selected = select_synthetic_samples( + real_samples=real_train_samples, + synthetic_samples=synthetic_train_all, + target_ratio=args.synthetic_target_ratio, + seed=args.synthetic_seed + ) + train_samples = [*real_train_samples, *synthetic_train_selected] + + train_counts = count_labels(train_samples) + real_train_counts = count_labels(real_train_samples) + synthetic_train_counts = count_labels(synthetic_train_selected) + val_counts = count_labels(val_samples) + test_counts = count_labels(test_samples) + print(f"Dataset: {dataset_root}") + if synthetic_root is not None: + print(f"Synthetic root: {synthetic_root}") + print( + f"Train real samples: {len(real_train_samples)} " + f"({format_counts(real_train_counts)})" + ) + print( + f"Train synthetic selected: {len(synthetic_train_selected)} / {len(synthetic_train_all)} " + f"(target_ratio={args.synthetic_target_ratio:.2f}, seed={args.synthetic_seed}) " + f"({format_counts(synthetic_train_counts)})" + ) + print(f"Train effective samples: {len(train_samples)} ({format_counts(train_counts)})") + print(f"Val samples: {len(val_samples)} ({format_counts(val_counts)})") + print(f"Test samples: {len(test_samples)} ({format_counts(test_counts)})") + + train_dataset = DigitCellDataset( + train_samples, + image_size=args.image_size, + augment=True, + split_jitter_x=args.split_jitter_x, + split_jitter_y=args.split_jitter_y, + split_jitter_prob=args.split_jitter_prob + ) + val_dataset = DigitCellDataset(val_samples, image_size=args.image_size, augment=False) + test_dataset = DigitCellDataset(test_samples, image_size=args.image_size, augment=False) + + train_sampler = make_train_sampler(train_samples, args.epoch_sample_multiplier) + train_loader = DataLoader( + train_dataset, + batch_size=args.batch_size, + sampler=train_sampler, + shuffle=train_sampler is None, + num_workers=args.num_workers, + pin_memory=device.type == "cuda" + ) + val_loader = DataLoader( + val_dataset, + batch_size=args.batch_size, + shuffle=False, + num_workers=args.num_workers, + pin_memory=device.type == "cuda" + ) if len(val_dataset) else None + test_loader = DataLoader( + test_dataset, + batch_size=args.batch_size, + shuffle=False, + num_workers=args.num_workers, + pin_memory=device.type == "cuda" + ) if len(test_dataset) else None + + model = build_digit_cnn(DEFAULT_NUM_CLASSES).to(device) + class_weights = make_class_weights(train_samples, device) + criterion = nn.CrossEntropyLoss(weight=class_weights) + optimizer = torch.optim.AdamW( + model.parameters(), + lr=args.learning_rate, + weight_decay=args.weight_decay + ) + scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau( + optimizer, + mode="min", + factor=0.5, + patience=8, + threshold=1.0e-4, + min_lr=1.0e-5 + ) + + best_epoch = 0 + best_metric = float("inf") + best_state = state_dict_to_cpu(model) + epochs_without_improvement = 0 + history: list[dict[str, float | int]] = [] + start_time = time.perf_counter() + + for epoch in range(1, args.epochs + 1): + train_metrics = run_epoch(model, train_loader, criterion, optimizer, device) + val_metrics = run_epoch(model, val_loader, criterion, None, device) if val_loader else None + monitored_loss = val_metrics["loss"] if val_metrics else train_metrics["loss"] + scheduler.step(monitored_loss) + + improved = monitored_loss < (best_metric - 1.0e-4) + if improved: + best_metric = monitored_loss + best_epoch = epoch + best_state = state_dict_to_cpu(model) + epochs_without_improvement = 0 + else: + epochs_without_improvement += 1 + + current_lr = optimizer.param_groups[0]["lr"] + epoch_row = { + "epoch": epoch, + "learning_rate": current_lr, + "train_loss": train_metrics["loss"], + "train_accuracy": train_metrics["accuracy"], + "val_loss": val_metrics["loss"] if val_metrics else None, + "val_accuracy": val_metrics["accuracy"] if val_metrics else None + } + history.append(epoch_row) + + train_acc = train_metrics["accuracy"] * 100 + train_loss = train_metrics["loss"] + if val_metrics: + val_acc = val_metrics["accuracy"] * 100 + val_loss = val_metrics["loss"] + print( + f"[{epoch:03d}] lr={current_lr:.6f} " + f"train_loss={train_loss:.4f} train_acc={train_acc:.1f}% " + f"val_loss={val_loss:.4f} val_acc={val_acc:.1f}%" + ) + else: + print( + f"[{epoch:03d}] lr={current_lr:.6f} " + f"train_loss={train_loss:.4f} train_acc={train_acc:.1f}%" + ) + + if val_loader and epochs_without_improvement >= args.patience: + print(f"Early stopping at epoch {epoch} (patience={args.patience}).") + break + + model.load_state_dict(best_state) + model.to(device) + final_train = run_epoch(model, train_loader, criterion, None, device) + final_val = run_epoch(model, val_loader, criterion, None, device) if val_loader else None + final_test = run_epoch(model, test_loader, criterion, None, device) if test_loader else None + elapsed_s = time.perf_counter() - start_time + + output_path.parent.mkdir(parents=True, exist_ok=True) + checkpoint = { + "state_dict": best_state, + "class_names": DEFAULT_CLASS_NAMES, + "image_size": args.image_size, + "best_epoch": best_epoch, + "device": str(device), + "train_real_counts": real_train_counts, + "train_synthetic_counts": synthetic_train_counts, + "train_counts": train_counts, + "val_counts": val_counts, + "test_counts": test_counts, + "final_metrics": { + "train": final_train, + "val": final_val, + "test": final_test + }, + "history": history, + "training_seconds": elapsed_s, + "args": vars(args) + } + torch.save(checkpoint, output_path) + + summary_path = run_dir / "digit_classifier_summary.json" + summary_payload = { + "output_path": str(output_path), + "best_epoch": best_epoch, + "training_seconds": elapsed_s, + "device": str(device), + "class_names": DEFAULT_CLASS_NAMES, + "synthetic_root": str(synthetic_root) if synthetic_root is not None else None, + "synthetic_target_ratio": args.synthetic_target_ratio, + "synthetic_seed": args.synthetic_seed, + "train_real_counts": real_train_counts, + "train_synthetic_counts": synthetic_train_counts, + "train_counts": train_counts, + "val_counts": val_counts, + "test_counts": test_counts, + "final_metrics": { + "train": final_train, + "val": final_val, + "test": final_test + }, + "history": history + } + summary_path.write_text(json.dumps(summary_payload, indent=2), encoding="utf-8") + + print(f"Best epoch: {best_epoch}") + print(f"Saved classifier to: {output_path}") + print(f"Saved summary to: {summary_path}") + if final_val: + print( + "Validation: " + f"loss={final_val['loss']:.4f} " + f"acc={final_val['accuracy'] * 100:.1f}%" + ) + if final_test: + print( + "Test: " + f"loss={final_test['loss']:.4f} " + f"acc={final_test['accuracy'] * 100:.1f}%" + ) + + +if __name__ == "__main__": + main() diff --git a/backend/train_roi.py b/backend/train_roi.py new file mode 100644 index 0000000..b22f942 --- /dev/null +++ b/backend/train_roi.py @@ -0,0 +1,370 @@ +from __future__ import annotations + +import argparse +import shutil +import tempfile +from pathlib import Path + +import yaml + +SUPPORTED_IMAGE_SUFFIXES = {".jpg", ".jpeg", ".png", ".bmp", ".webp"} +DEFAULT_ROTATION_ANGLES = "90,180,270,360" +REQUIRED_ROTATION_ANGLES = {90, 180, 270, 360} +HEAVY_AUGMENT_KWARGS = { + "degrees": 180.0, + "translate": 0.2, + "scale": 0.5, + "shear": 15.0, + "perspective": 0.001, + "flipud": 0.5, + "fliplr": 0.5, + "mosaic": 1.0, + "mixup": 0.2 +} + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description="Fine-tune a pretrained YOLO model to detect the water meter digit window." + ) + parser.add_argument( + "--data", + default="data/roi_dataset.yaml", + help="Path to YOLO dataset yaml (relative to backend/ by default)." + ) + parser.add_argument( + "--base-model", + default="yolov8n.pt", + help="Pretrained model checkpoint to fine-tune (e.g. yolov8n.pt, yolov8s.pt)." + ) + parser.add_argument("--epochs", type=int, default=120) + parser.add_argument("--imgsz", type=int, default=960) + parser.add_argument("--batch", type=int, default=8) + parser.add_argument("--patience", type=int, default=30) + parser.add_argument("--workers", type=int, default=4) + parser.add_argument( + "--device", + default="auto", + help="Training device: cpu, auto, 0, or cuda:0 (default: auto)." + ) + parser.add_argument( + "--rotation-angles", + default=DEFAULT_ROTATION_ANGLES, + help=( + "Comma-separated clockwise right-angle rotations to materialize in the train split " + "(allowed: 0,90,180,270,360). Default: 90,180,270,360" + ) + ) + parser.add_argument( + "--heavy-augment", + dest="heavy_augment", + action="store_true", + default=True, + help="Enable aggressive online augmentation (rotate/translate/scale/shear/flip/mosaic/mixup)." + ) + parser.add_argument( + "--no-heavy-augment", + dest="heavy_augment", + action="store_false", + help="Disable heavy online augmentation (requires --allow-no-augment-policy)." + ) + parser.add_argument( + "--allow-no-augment-policy", + action="store_true", + help=( + "Allow training without the enforced augmentation policy. " + "Use only for explicit ablations." + ) + ) + parser.add_argument("--project", default="runs") + parser.add_argument("--name", default="roi-finetune") + parser.add_argument("--copy-to", default="models/roi.pt", help="Where to copy best.pt after training.") + return parser.parse_args() + + +def resolve_path(base_dir: Path, value: str) -> Path: + path = Path(value) + if path.is_absolute(): + return path + return (base_dir / path).resolve() + + +def resolve_dataset_yaml(data_path: Path) -> Path: + payload = yaml.safe_load(data_path.read_text(encoding="utf-8")) or {} + if not isinstance(payload, dict): + return data_path + + dataset_root = payload.get("path") + if not dataset_root or not isinstance(dataset_root, str): + return data_path + + root_path = Path(dataset_root) + if root_path.is_absolute(): + return data_path + + payload["path"] = str((data_path.parent / root_path).resolve()) + with tempfile.NamedTemporaryFile("w", suffix=".yaml", delete=False, encoding="utf-8") as handle: + yaml.safe_dump(payload, handle, sort_keys=False) + return Path(handle.name) + + +def resolve_device(device: str) -> str | None: + normalized = device.strip() + if not normalized: + return None + if normalized.lower() == "auto": + return None + return normalized + + +def clamp01(value: float) -> float: + return max(0.0, min(1.0, value)) + + +def parse_rotation_angles(raw: str) -> list[tuple[int, int]]: + value = (raw or "").strip() + if not value: + return [] + allowed = {0, 90, 180, 270, 360} + result: list[tuple[int, int]] = [] + seen: set[int] = set() + for token in value.split(","): + angle_text = token.strip() + if not angle_text: + continue + try: + requested = int(angle_text) + except ValueError as error: + raise ValueError(f"Invalid angle: {angle_text}") from error + if requested not in allowed: + raise ValueError(f"Unsupported angle {requested}. Allowed: 0,90,180,270,360") + if requested in seen: + continue + seen.add(requested) + result.append((requested, requested % 360)) + return result + + +def rotate_yolo_bbox(xc: float, yc: float, width: float, height: float, angle_norm: int) -> tuple[float, float, float, float]: + if angle_norm == 0: + return xc, yc, width, height + if angle_norm == 90: + return 1 - yc, xc, height, width + if angle_norm == 180: + return 1 - xc, 1 - yc, width, height + if angle_norm == 270: + return yc, 1 - xc, height, width + raise ValueError(f"Unexpected normalized angle: {angle_norm}") + + +def write_rotated_label(source_label: Path, target_label: Path, angle_norm: int) -> None: + output_lines: list[str] = [] + for line in source_label.read_text(encoding="utf-8").splitlines(): + stripped = line.strip() + if not stripped: + continue + parts = stripped.split() + if len(parts) != 5: + raise ValueError( + f"Only box labels with 5 columns are supported for rotation augmentation: {source_label}" + ) + class_id = parts[0] + xc, yc, width, height = (float(parts[i]) for i in range(1, 5)) + rot_xc, rot_yc, rot_w, rot_h = rotate_yolo_bbox( + clamp01(xc), + clamp01(yc), + clamp01(width), + clamp01(height), + angle_norm + ) + output_lines.append( + f"{class_id} {clamp01(rot_xc):.6f} {clamp01(rot_yc):.6f} {clamp01(rot_w):.6f} {clamp01(rot_h):.6f}" + ) + target_label.write_text("\n".join(output_lines) + "\n", encoding="utf-8") + + +def rotate_and_save_image(source_image: Path, target_image: Path, angle_norm: int) -> None: + try: + from PIL import Image + except ImportError as error: + raise RuntimeError("Pillow is required for --rotation-angles augmentation.") from error + + with Image.open(source_image) as image: + if angle_norm == 0: + rotated = image.copy() + else: + rotated = image.rotate(-angle_norm, expand=True) + if target_image.suffix.lower() in {".jpg", ".jpeg"}: + rotated.save(target_image, quality=95) + else: + rotated.save(target_image) + + +def build_rotated_dataset(resolved_data_path: Path, rotations: list[tuple[int, int]]) -> tuple[Path, Path, int]: + payload = yaml.safe_load(resolved_data_path.read_text(encoding="utf-8")) or {} + if not isinstance(payload, dict): + raise ValueError(f"Invalid dataset yaml: {resolved_data_path}") + + dataset_root_raw = payload.get("path") + train_images_raw = payload.get("train") + if not isinstance(dataset_root_raw, str) or not dataset_root_raw.strip(): + raise ValueError(f"Dataset yaml is missing a valid 'path': {resolved_data_path}") + if not isinstance(train_images_raw, str) or not train_images_raw.strip(): + raise ValueError(f"Dataset yaml is missing a valid 'train' entry: {resolved_data_path}") + + dataset_root = Path(dataset_root_raw) + if not dataset_root.is_absolute(): + dataset_root = (resolved_data_path.parent / dataset_root).resolve() + if not dataset_root.exists(): + raise FileNotFoundError(f"Dataset root not found: {dataset_root}") + + train_images_rel = Path(train_images_raw) + train_images_dir = (dataset_root / train_images_rel).resolve() + split_name = train_images_rel.name + train_labels_dir = (dataset_root / "labels" / split_name).resolve() + if not train_images_dir.exists(): + raise FileNotFoundError(f"Train image directory not found: {train_images_dir}") + if not train_labels_dir.exists(): + raise FileNotFoundError(f"Train label directory not found: {train_labels_dir}") + + temp_root = Path(tempfile.mkdtemp(prefix="roi_rot_aug_")) + try: + augmented_dataset_root = temp_root / dataset_root.name + shutil.copytree(dataset_root, augmented_dataset_root) + + augmented_images_dir = (augmented_dataset_root / train_images_rel).resolve() + augmented_labels_dir = (augmented_dataset_root / "labels" / split_name).resolve() + base_images = sorted( + path + for path in augmented_images_dir.iterdir() + if path.is_file() and path.suffix.lower() in SUPPORTED_IMAGE_SUFFIXES + ) + + generated_count = 0 + for image_path in base_images: + label_path = augmented_labels_dir / f"{image_path.stem}.txt" + if not label_path.exists(): + raise FileNotFoundError(f"Missing label for train image: {label_path}") + for requested_angle, normalized_angle in rotations: + suffix = f"_rot{requested_angle}" + rotated_image = image_path.with_name(f"{image_path.stem}{suffix}{image_path.suffix}") + rotated_label = label_path.with_name(f"{label_path.stem}{suffix}.txt") + rotate_and_save_image(image_path, rotated_image, normalized_angle) + write_rotated_label(label_path, rotated_label, normalized_angle) + generated_count += 1 + + payload["path"] = str(augmented_dataset_root.resolve()) + augmented_yaml_path = temp_root / "dataset_rot_aug.yaml" + augmented_yaml_path.write_text(yaml.safe_dump(payload, sort_keys=False), encoding="utf-8") + return augmented_yaml_path, temp_root, generated_count + except Exception: + shutil.rmtree(temp_root, ignore_errors=True) + raise + + +def validate_augmentation_policy( + heavy_augment: bool, + rotations: list[tuple[int, int]], + allow_no_augment_policy: bool +) -> None: + configured_angles = {requested for requested, _ in rotations} + missing = sorted(REQUIRED_ROTATION_ANGLES - configured_angles) + policy_ok = heavy_augment and not missing + if policy_ok: + return + if allow_no_augment_policy: + print( + "WARNING: augmentation policy override enabled " + f"(heavy_augment={heavy_augment}, missing_rotations={missing})." + ) + return + raise ValueError( + "ROI training requires heavy augmentation and rotation angles " + f"{sorted(REQUIRED_ROTATION_ANGLES)}. " + "Use --allow-no-augment-policy only for explicit ablation runs." + ) + + +def main() -> None: + args = parse_args() + base_dir = Path(__file__).resolve().parent + data_path = resolve_path(base_dir, args.data) + project_path = resolve_path(base_dir, args.project) + copy_to_path = resolve_path(base_dir, args.copy_to) + device = resolve_device(args.device) + rotations = parse_rotation_angles(args.rotation_angles) + validate_augmentation_policy(args.heavy_augment, rotations, args.allow_no_augment_policy) + + if not data_path.exists(): + raise FileNotFoundError(f"Dataset yaml not found: {data_path}") + + temporary_files: list[Path] = [] + temporary_dirs: list[Path] = [] + try: + resolved_data_path = resolve_dataset_yaml(data_path) + if resolved_data_path != data_path: + temporary_files.append(resolved_data_path) + train_data_path = resolved_data_path + if rotations: + train_data_path, augmented_root, generated = build_rotated_dataset(resolved_data_path, rotations) + temporary_files.append(train_data_path) + temporary_dirs.append(augmented_root) + print( + f"Rotation augmentation enabled: base train images expanded with {generated} generated variants " + f"for angles {[requested for requested, _ in rotations]}" + ) + + try: + from ultralytics import YOLO + except ImportError as error: + raise RuntimeError("ultralytics is required. Install backend/requirements.txt first.") from error + + model = YOLO(args.base_model) + train_kwargs = dict(HEAVY_AUGMENT_KWARGS) if args.heavy_augment else {} + if args.heavy_augment: + print(f"Heavy online augmentation enabled: {train_kwargs}") + + model.train( + data=str(train_data_path), + epochs=args.epochs, + imgsz=args.imgsz, + batch=args.batch, + patience=args.patience, + workers=args.workers, + project=str(project_path), + name=args.name, + device=device, + **train_kwargs + ) + + trainer = getattr(model, "trainer", None) + if trainer is None: + raise RuntimeError("Training completed but model.trainer is unavailable; cannot locate best checkpoint.") + + best_checkpoint = getattr(trainer, "best", None) + if best_checkpoint: + best_path = Path(best_checkpoint) + else: + save_dir = getattr(trainer, "save_dir", None) + if not save_dir: + raise RuntimeError("Training completed but trainer metadata does not include a checkpoint path.") + best_path = Path(save_dir) / "weights" / "best.pt" + + if not best_path.exists(): + raise FileNotFoundError(f"Training completed but no best.pt found at {best_path}") + + copy_to_path.parent.mkdir(parents=True, exist_ok=True) + shutil.copy2(best_path, copy_to_path) + print(f"Best model: {best_path}") + print(f"Copied to: {copy_to_path}") + finally: + for path in temporary_files: + if path.exists(): + path.unlink(missing_ok=True) + for directory in temporary_dirs: + if directory.exists(): + shutil.rmtree(directory, ignore_errors=True) + + +if __name__ == "__main__": + main() diff --git a/backend/validate_digit_dataset.py b/backend/validate_digit_dataset.py new file mode 100644 index 0000000..de4377e --- /dev/null +++ b/backend/validate_digit_dataset.py @@ -0,0 +1,180 @@ +from __future__ import annotations + +import argparse +import csv +from pathlib import Path + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description="Validate digit dataset manifests, files, and QA preview coverage." + ) + parser.add_argument( + "--dataset-root", + default="data/digit_dataset", + help="Digit dataset root." + ) + parser.add_argument( + "--cell-count", + type=int, + default=4, + help="Expected number of cell crops per strip row." + ) + return parser.parse_args() + + +def resolve(base_dir: Path, value: str) -> Path: + path = Path(value) + if path.is_absolute(): + return path + return (base_dir / path).resolve() + + +def load_csv_rows(path: Path) -> list[dict[str, str]]: + rows: list[dict[str, str]] = [] + with path.open("r", encoding="utf-8") as handle: + reader = csv.DictReader(handle) + for row in reader: + rows.append({key: (value or "").strip() for key, value in row.items()}) + return rows + + +def main() -> None: + args = parse_args() + base_dir = Path(__file__).resolve().parent + dataset_root = resolve(base_dir, args.dataset_root) + manifests_dir = dataset_root / "manifests" + strips_csv = manifests_dir / "strips.csv" + cells_csv = manifests_dir / "cells.csv" + + if args.cell_count <= 0: + raise ValueError("--cell-count must be positive.") + if not dataset_root.exists(): + raise FileNotFoundError(f"Dataset root not found: {dataset_root}") + if not strips_csv.exists(): + raise FileNotFoundError(f"Strips manifest not found: {strips_csv}") + if not cells_csv.exists(): + raise FileNotFoundError(f"Cells manifest not found: {cells_csv}") + + strip_rows = load_csv_rows(strips_csv) + cell_rows = load_csv_rows(cells_csv) + if not strip_rows: + raise RuntimeError(f"No rows found in strips manifest: {strips_csv}") + if not cell_rows: + raise RuntimeError(f"No rows found in cells manifest: {cells_csv}") + + errors: list[str] = [] + warnings: list[str] = [] + cells_by_strip: dict[tuple[str, str], list[dict[str, str]]] = {} + split_counts = {"train": 0, "val": 0, "test": 0} + digit_counts = {str(digit): 0 for digit in range(10)} + + for row in cell_rows: + split = row.get("split", "") + filename = row.get("filename", "") + digit = row.get("digit", "") + cell_index_text = row.get("cell_index", "") + cell_path_rel = row.get("cell_path", "") + + if split not in split_counts: + errors.append(f"Unexpected split in cells.csv: {split}") + continue + if digit not in digit_counts: + errors.append(f"Unexpected digit in cells.csv: {digit}") + continue + split_counts[split] += 1 + digit_counts[digit] += 1 + + try: + cell_index = int(cell_index_text) + except ValueError: + errors.append(f"Invalid cell_index for {filename}: {cell_index_text}") + continue + if cell_index < 0 or cell_index >= args.cell_count: + errors.append(f"Out-of-range cell_index for {filename}: {cell_index}") + + key = (split, filename) + cells_by_strip.setdefault(key, []).append(row) + + cell_path = dataset_root / cell_path_rel + if not cell_path.exists(): + errors.append(f"Missing cell file: {cell_path}") + else: + # Enforce file routing by split/digit to avoid silent mislabeled moves. + expected_parent = dataset_root / "cells" / split / digit + if cell_path.parent != expected_parent: + errors.append( + f"Cell path not in expected split/digit folder: {cell_path} " + f"(expected parent {expected_parent})" + ) + + for row in strip_rows: + split = row.get("split", "") + filename = row.get("filename", "") + strip_path_rel = row.get("strip_path", "") + label_path_rel = row.get("label_path", "") + + if split not in {"train", "val", "test"}: + errors.append(f"Unexpected split in strips.csv: {split}") + continue + stem = Path(filename).stem + key = (split, filename) + matching_cells = cells_by_strip.get(key, []) + if len(matching_cells) != args.cell_count: + errors.append( + f"Expected {args.cell_count} cells for {split}/{filename}, found {len(matching_cells)}." + ) + else: + index_values = sorted( + int(cell_row["cell_index"]) + for cell_row in matching_cells + if cell_row.get("cell_index", "").isdigit() + ) + if index_values != list(range(args.cell_count)): + errors.append( + f"Unexpected cell_index set for {split}/{filename}: {index_values}" + ) + + strip_path = dataset_root / strip_path_rel + label_path = dataset_root / label_path_rel + qa_preview_path = dataset_root / "qa_previews" / split / f"{stem}_qa.jpg" + if not strip_path.exists(): + errors.append(f"Missing strip file: {strip_path}") + if not label_path.exists(): + errors.append(f"Missing strip label file: {label_path}") + if not qa_preview_path.exists(): + errors.append(f"Missing QA preview image: {qa_preview_path}") + + strip_keys = {(row.get("split", ""), row.get("filename", "")) for row in strip_rows} + for key in cells_by_strip.keys(): + if key not in strip_keys: + warnings.append(f"Cell rows exist without strip row: split={key[0]} filename={key[1]}") + + print(f"Dataset root: {dataset_root}") + print(f"Strip rows: {len(strip_rows)}") + print(f"Cell rows: {len(cell_rows)}") + print( + "Cell rows by split: " + f"train={split_counts['train']} val={split_counts['val']} test={split_counts['test']}" + ) + print( + "Cell rows by digit: " + + ", ".join([f"{digit}:{digit_counts[digit]}" for digit in map(str, range(10))]) + ) + + if warnings: + print("Warnings:") + for message in warnings: + print(f" - {message}") + + if errors: + print("Errors:") + for message in errors: + print(f" - {message}") + raise SystemExit(1) + + print("Validation passed.") + + +if __name__ == "__main__": + main() diff --git a/backend/visualize_roi_labels.py b/backend/visualize_roi_labels.py new file mode 100644 index 0000000..7e8766e --- /dev/null +++ b/backend/visualize_roi_labels.py @@ -0,0 +1,197 @@ +from __future__ import annotations + +import argparse +from dataclasses import dataclass +from pathlib import Path + + +SUPPORTED_IMAGE_EXTENSIONS = (".jpg", ".jpeg", ".png", ".bmp", ".webp") + + +@dataclass +class RenderedPreview: + split: str + image_path: Path + preview_path: Path + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description="Render YOLO label overlays for quick visual QA." + ) + parser.add_argument( + "--dataset-root", + default="data/roi_dataset", + help="YOLO dataset root (relative to backend/ by default)." + ) + parser.add_argument( + "--out-dir", + default="data/roi_dataset/qa_previews", + help="Directory to write per-image previews." + ) + parser.add_argument( + "--skip-contact-sheet", + action="store_true", + help="Skip generating the combined contact sheet." + ) + return parser.parse_args() + + +def resolve_path(base_dir: Path, value: str) -> Path: + path = Path(value) + if path.is_absolute(): + return path + return (base_dir / path).resolve() + + +def find_image_for_label(image_dir: Path, stem: str) -> Path | None: + for extension in SUPPORTED_IMAGE_EXTENSIONS: + candidate = image_dir / f"{stem}{extension}" + if candidate.exists(): + return candidate + + lower_lookup = {path.stem.lower(): path for path in image_dir.iterdir() if path.is_file()} + return lower_lookup.get(stem.lower()) + + +def parse_yolo_labels(label_path: Path) -> list[tuple[int, float, float, float, float]]: + labels: list[tuple[int, float, float, float, float]] = [] + raw = label_path.read_text(encoding="utf-8").strip() + if not raw: + return labels + + for line in raw.splitlines(): + tokens = line.split() + if len(tokens) != 5: + raise ValueError(f"Invalid YOLO line in {label_path}: {line}") + class_id = int(tokens[0]) + xc, yc, width, height = map(float, tokens[1:]) + labels.append((class_id, xc, yc, width, height)) + + return labels + + +def draw_preview(image_path: Path, labels: list[tuple[int, float, float, float, float]], out_path: Path) -> None: + try: + from PIL import Image, ImageDraw + except ImportError as error: + raise RuntimeError("Pillow is required. Install backend/requirements.txt first.") from error + + with Image.open(image_path).convert("RGB") as image: + width, height = image.size + draw = ImageDraw.Draw(image) + line_width = max(2, width // 350) + + for class_id, xc, yc, box_w, box_h in labels: + pixel_w = box_w * width + pixel_h = box_h * height + x1 = (xc * width) - (pixel_w * 0.5) + y1 = (yc * height) - (pixel_h * 0.5) + x2 = x1 + pixel_w + y2 = y1 + pixel_h + + draw.rectangle((x1, y1, x2, y2), outline=(0, 255, 255), width=line_width) + draw.text((x1 + 4, max(2, y1 - 14)), f"class {class_id}", fill=(0, 255, 255)) + + out_path.parent.mkdir(parents=True, exist_ok=True) + image.save(out_path, quality=95) + + +def build_contact_sheet(previews: list[RenderedPreview], output_path: Path) -> None: + if not previews: + return + + try: + from PIL import Image, ImageDraw, ImageOps + except ImportError as error: + raise RuntimeError("Pillow is required. Install backend/requirements.txt first.") from error + + thumb_width = 420 + thumb_height = 320 + label_height = 40 + padding = 16 + columns = 3 + rows = (len(previews) + columns - 1) // columns + + canvas_width = padding + columns * (thumb_width + padding) + canvas_height = padding + rows * (thumb_height + label_height + padding) + canvas = Image.new("RGB", (canvas_width, canvas_height), (22, 22, 24)) + draw = ImageDraw.Draw(canvas) + + for index, preview in enumerate(previews): + row = index // columns + column = index % columns + x = padding + column * (thumb_width + padding) + y = padding + row * (thumb_height + label_height + padding) + + with Image.open(preview.preview_path).convert("RGB") as image: + thumb = ImageOps.contain(image, (thumb_width, thumb_height)) + frame = Image.new("RGB", (thumb_width, thumb_height), (40, 40, 44)) + offset_x = (thumb_width - thumb.width) // 2 + offset_y = (thumb_height - thumb.height) // 2 + frame.paste(thumb, (offset_x, offset_y)) + + canvas.paste(frame, (x, y)) + label = f"{preview.split}/{preview.image_path.name}" + draw.text((x, y + thumb_height + 10), label, fill=(230, 230, 230)) + + output_path.parent.mkdir(parents=True, exist_ok=True) + canvas.save(output_path, quality=95) + + +def main() -> None: + args = parse_args() + base_dir = Path(__file__).resolve().parent + dataset_root = resolve_path(base_dir, args.dataset_root) + out_dir = resolve_path(base_dir, args.out_dir) + + if not dataset_root.exists(): + raise FileNotFoundError(f"Dataset root not found: {dataset_root}") + + previews: list[RenderedPreview] = [] + missing_images: list[str] = [] + empty_labels: list[str] = [] + + for split in ("train", "val", "test"): + label_dir = dataset_root / "labels" / split + image_dir = dataset_root / "images" / split + split_out = out_dir / split + + if not label_dir.exists(): + continue + + for label_path in sorted(label_dir.glob("*.txt")): + image_path = find_image_for_label(image_dir, label_path.stem) + if image_path is None: + missing_images.append(f"{split}/{label_path.name}") + continue + + labels = parse_yolo_labels(label_path) + if not labels: + empty_labels.append(f"{split}/{label_path.name}") + continue + + preview_path = split_out / f"{label_path.stem}_qa.jpg" + draw_preview(image_path=image_path, labels=labels, out_path=preview_path) + previews.append( + RenderedPreview(split=split, image_path=image_path, preview_path=preview_path) + ) + + if not args.skip_contact_sheet and previews: + contact_sheet_path = out_dir / "qa_contact_sheet.jpg" + build_contact_sheet(previews, contact_sheet_path) + print(f"Contact sheet: {contact_sheet_path}") + + print(f"Rendered previews: {len(previews)} in {out_dir}") + if missing_images: + print("Missing source images:") + for item in missing_images: + print(f" - {item}") + if empty_labels: + print("Empty label files:") + for item in empty_labels: + print(f" - {item}") + + +if __name__ == "__main__": + main() diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..48a80c1 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,9 @@ +# Jarvis Docs + +This folder holds focused documentation that would make the top-level `README.md` too long. + +## Contents + +- [App Logic](./app-logic.md): End-to-end OCR flow, decision points, and failure paths. +- [Backend API](./backend-api.md): Local FastAPI endpoints, request/response contract, and runtime configuration. +- [OCR Tuning Playbook](./ocr-tuning-playbook.md): Iteration workflow for improving OCR quality, checkpoint diffing, and fallback ON/OFF benchmarking gates. diff --git a/docs/app-logic-flow.png b/docs/app-logic-flow.png new file mode 100644 index 0000000..5d12caf Binary files /dev/null and b/docs/app-logic-flow.png differ diff --git a/docs/app-logic.md b/docs/app-logic.md new file mode 100644 index 0000000..c12cbf4 --- /dev/null +++ b/docs/app-logic.md @@ -0,0 +1,74 @@ +# App Logic + +This document describes the current Jarvis OCR execution path, including neural ROI gating and the optional gated classifier fallback. + +## End-to-End OCR Flow + +```mermaid +flowchart TD + A["UI: Read meter"] --> B["runMeterOcr(file)"] + B --> C["Load image + start debug session"] + C --> D["detectNeuralRoi(file, config)"] + D --> E{"ROI probe ok?"} + + E -- "No" --> F["ROI fail reason
disabled | http-error | invalid-json | no-detection | invalid-bbox | invalid-confidence | low-confidence | invalid-geometry (center/area/aspect)"] + F --> G["Show miss in debug + ask manual entry"] + + E -- "Yes" --> H["Expand + crop ROI"] + H --> I["Build ROI candidates
(rotations + optional edge crops)"] + I --> J{"Candidates available?"} + J -- "No" --> K["Ask manual entry"] + J -- "Yes" --> L["Word-pass OCR per candidate
(SINGLE_WORD, digits only)"] + + L --> M{"Best 4-digit reading found?"} + M -- "No" --> N["Sparse scan OCR on ROI crop
(SPARSE_TEXT, soft)"] + M -- "Yes" --> O["finalizeSelection()
evidence ranking + word-pass support guardrail"] + N --> N2{"Still no accepted reading?"} + N2 -- "No" --> O + N2 -- "Yes" --> N3{"Classifier fallback enabled
and no-digits rejects seen?"} + N3 -- "No" --> O + N3 -- "Yes" --> N4["Digit-classifier fallback
(4 cells from ROI candidate)"] + N4 --> O + + O --> P{"Final selection exists?"} + P -- "Yes" --> Q["Return reading + fill UI input"] + P -- "No" --> R["Return null + ask manual entry"] + + O --> S["Push selection log
window.__jarvisOcrSelectionLogs
(includes selected source/method/mode)"] + S --> T["Run test set => failure/reject histograms"] +``` + +![App Logic Flow Render](./app-logic-flow.png) + +## Main Decision Gates + +1. Neural ROI gate + - OCR does not continue unless `detectNeuralRoi` returns `ok: true`. + - Geometry sanity checks (`centerX`, `centerY`, `area`, `aspect`) are applied before accepting ROI. + +2. Candidate availability gate + - If ROI crop cannot produce valid OCR candidates, the app falls back to manual input. + +3. OCR acceptance gate + - Word-pass result is preferred. + - Sparse scan is attempted if no word-pass result is available. + - Optional classifier fallback runs only when enabled and the branch has `ocr-no-digits` rejects. + - `finalizeSelection` ranks evidence across OCR passes and applies the active word-pass support guardrail (`hits` / `topHits` vs `minWordPassHits`) before returning a value. + - Edge-only winners are rejected unless corroborated by non-edge evidence or very strong per-cell confidence. + - Default config keeps classifier fallback disabled (`digitClassifier.enabled=false`) because current benchmark shows no MAE gain without exact-match/no-read guardrail safety. + +## What Gets Logged + +- Per-image selection logs are appended to `window.__jarvisOcrSelectionLogs`. +- `selected` metadata includes `sourceLabel`, `method`, and `preprocessMode` for each accepted reading. +- The test-set runner reads those logs to build: + - `Failure Reason` values (`mismatch`, `ocr-no-digits`, etc.) + - Reject histograms from OCR branch reject reasons. + +## Source Files + +- OCR orchestration: `src/ocr/pipeline.js` +- Neural ROI probe and sanity checks: `src/ocr/neural-roi.js` +- Candidate generation: `src/ocr/alignment.js` +- OCR ranking/reading extraction: `src/ocr/recognition.js` +- Test-set analysis and histograms: `src/testset/run-test-set.js` diff --git a/docs/backend-api.md b/docs/backend-api.md new file mode 100644 index 0000000..6d38734 --- /dev/null +++ b/docs/backend-api.md @@ -0,0 +1,144 @@ +# Backend API + +Jarvis includes an optional local FastAPI backend for ROI detection and digit classification. + +## Run + +From repo root: + +```bash +cd backend +source .venv/bin/activate +uvicorn app:app --host 127.0.0.1 --port 8001 --reload +``` + +Default local base URL: `http://127.0.0.1:8001` + +## Endpoints + +### `GET /health` + +Reports backend readiness and resolved model/runtime configuration. + +Example: + +```bash +curl -s http://127.0.0.1:8001/health +``` + +Key fields: + +- `ready` / `roi_ready`: ROI detector is loadable. +- `digit_ready`: digit classifier is loadable. +- `model_path`, `model_source`, `device`: ROI model/runtime selection. +- `digit_model_path`, `digit_device`: digit model/runtime selection. +- `default_confidence`, `default_iou`, `default_imgsz`: ROI inference defaults. +- `digit_min_confidence`, `digit_top_k`: classifier acceptance defaults. + +### `POST /roi/detect` + +Detects the ROI box for the 4-digit black register. + +Request: + +- Form-data field: `image` (required) + +Example: + +```bash +curl -s -X POST http://127.0.0.1:8001/roi/detect \ + -F "image=@assets/meter_02272026.JPEG" +``` + +Success response (shape): + +```json +{ + "ok": true, + "model": "roi-rotaug-e30-640.pt", + "device": "cpu", + "bbox_norm": { "x": 0.41, "y": 0.40, "width": 0.08, "height": 0.15 }, + "confidence": 0.49, + "class_id": 0, + "class_name": "digit_window", + "image_size": { "width": 1536, "height": 2048 } +} +``` + +No-detection response: + +```json +{ + "ok": false, + "bbox_norm": null, + "confidence": 0.0, + "class_id": null, + "class_name": null +} +``` + +### `POST /digit/predict` + +Classifies one digit image. + +Request: + +- Form-data field: `image` (required) + +Response includes: + +- `predicted_digit`, `confidence` +- `accepted` / `ok` based on `DIGIT_MIN_CONFIDENCE` +- `digit` is null when prediction is not accepted + +### `POST /digit/predict-cells` + +Batch classifier endpoint for up to 16 images. + +Request: + +- Form-data field name: `images` (repeat for each file) + +Example: + +```bash +curl -s -X POST http://127.0.0.1:8001/digit/predict-cells \ + -F "images=@/tmp/cell0.jpg" \ + -F "images=@/tmp/cell1.jpg" +``` + +Response includes: + +- `accepted_count`, `total` +- `predictions[]` with `predicted_digit`, `confidence`, `accepted` +- Per-item `error: "empty-upload"` when a file item is empty + +## Error Semantics + +- `400`: bad input (empty upload, unsupported image, too many images, etc.) +- `503`: model/dependency unavailable +- `200` with `ok: false` (ROI endpoint): inference ran but no ROI detected + +## Environment Variables + +ROI detector: + +- `ROI_MODEL_PATH` +- `ROI_DEFAULT_CONFIDENCE` (default `0.05`) +- `ROI_DEFAULT_IOU` (default `0.5`) +- `ROI_DEFAULT_IMGSZ` (default `960`) +- `ROI_CLASS_INDEX` (optional class filter) +- `ROI_DEVICE` (`cpu`, `cuda`, `auto`) + +Digit classifier: + +- `DIGIT_MODEL_PATH` +- `DIGIT_MIN_CONFIDENCE` (default `0.0`) +- `DIGIT_TOP_K` (default `3`) +- `DIGIT_DEVICE` (`cpu`, `cuda`, `auto`) + +## Source + +- API entrypoint: `backend/app.py` +- ROI detector wrapper: `backend/detector.py` +- Digit classifier wrapper: `backend/digit_classifier.py` diff --git a/docs/ocr-tuning-playbook.md b/docs/ocr-tuning-playbook.md new file mode 100644 index 0000000..374ceb2 --- /dev/null +++ b/docs/ocr-tuning-playbook.md @@ -0,0 +1,187 @@ +# OCR Tuning Playbook + +This playbook documents the practical loop used to improve OCR quality in Jarvis. + +Current baseline notes (March 2, 2026, fallback `OFF`, historical exact-match snapshot): + +- Test set: `0/14` exact-match (`Correct`) +- Pinned model (`roi-rotaug-e30-640.pt`): `mismatch` 6, `ocr-no-digits` 7, `no-detection` 1 +- Challenger (`roi.pt`): `mismatch` 4, `ocr-no-digits` 10, `no-detection` 0 +- Gated fallback experiment (`JARVIS_DIGIT_FALLBACK=1`) reduced `ocr-no-digits` but increased `mismatch` with no exact-match gain, so fallback remains disabled. +- Evaluation now uses `MAE` as the primary promotion signal; exact-match and no-read rates are guardrails. +- The active local test-set CSV now has `15` images; keep historical `0/14` snapshots only for trend context. + +Critical blocker note (March 4, 2026): + +- Digit-classifier training cells are currently unreliable due to strip orientation/cell-split issues in dataset export. +- `build_digit_dataset.py` splits cells assuming left-to-right horizontal strips; vertical strips become thin non-digit slices. +- Some horizontal strips are 180-deg inverted, so cell index to reading-digit assignment is reversed. +- Concrete examples from manual QA: + - `meter_07012020_c3_4.png` appears as digit `1` in context. + - `meter_01122026_c2_0.png` is a thin register slice, not a usable digit crop. + +## Goals + +1. Reduce `mismatch` (wrong 4-digit value returned). +2. Reduce `ocr-no-digits` (no accepted 4-digit candidate). +3. Preserve neural-ROI-only policy and strip-only OCR path. + +## Standard Iteration Loop + +1. Run baseline checks + +```bash +npm run test:e2e +``` + +- In UI (`http://localhost:8000`): run **Run test set** with debug overlay enabled. +- Record failure histogram and reject histogram. + +2. Inspect hard failures + +- Prioritize: + - `meter_02202026.JPEG` + - `meter_02192026.JPEG` + - `meter_07012020.JPEG` + - `meter_02242026.JPEG` +- Inspect debug stages: + - `0. neural roi detection` + - `0b. neural roi crop` + - `5. detected strip crop` + - `6. OCR input candidate` +- Inspect selection logs in `window.__jarvisOcrSelectionLogs`. + +3. Apply one narrow change + +- Make a single hypothesis-driven change (config or scoring/candidate logic). +- Avoid bundled edits that make regression attribution unclear. + +4. Re-run and compare + +- Re-run UI test set. +- Compare movement in: + - `mismatch` + - `ocr-no-digits` + - ROI no-detection +- Re-run: + +```bash +npm run test:e2e +``` + +5. Keep or revert + +- Keep only changes with clear net improvement. +- Revert changes that shift failures without improving `MAE`. + +## Automated Checkpoint Diff + +Use the scripted checkpoint comparison to produce a per-image report between the pinned baseline and a challenger model: + +```bash +npm run benchmark:roi-diff +``` + +For the gated classifier fallback experiment (fallback only after `ocr-no-digits`), run: + +```bash +JARVIS_DIGIT_FALLBACK=1 npm run benchmark:roi-diff +``` + +Compare fallback `ON` vs `OFF` with the same promotion gates; do not accept `ocr-no-digits` reductions that simply convert into `mismatch`. + +Artifacts are saved to: + +- `output/roi-checkpoint-diff//roi-diff-report.md` +- `output/roi-checkpoint-diff//roi-diff-report.json` +- `output/roi-checkpoint-diff//{baseline,challenger}/stages/*` (stage `5` and `6` snapshots) + +The report includes: + +- Per-image `Detected`, `Failure Reason`, and top reject reason. +- Per-image selected metadata (`sourceLabel`, `method`, `preprocessMode`) from `window.__jarvisOcrSelectionLogs`. +- Side-by-side stage `5. detected strip crop` and `6. OCR input candidate`. +- Stage `6` export uses the last `6. OCR input candidate` frame from each debug session. +- Summary deltas for `MAE`, guardrail rates (`Exact Match`, `No-read`), `mismatch`, `ocr-no-digits`, and `no-detection`. + +## Checkpoint Promotion Gates + +Promote a challenger checkpoint only if all gates pass on the same test-set run: + +1. **No-detection gate**: challenger `no-detection` count must be less than or equal to baseline. +2. **MAE gate**: challenger `MAE` must be less than or equal to baseline. +3. **Exact-match guardrail**: challenger `Exact Match` rate must be greater than or equal to baseline. +4. **No-read guardrail**: challenger `No-read` rate must be less than or equal to baseline. +5. **OCR no-digits gate**: challenger `ocr-no-digits` count must be less than or equal to baseline. + +If any gate fails, keep `roi-rotaug-e30-640.pt` as default and continue tuning extraction/selection. + +Fallback-specific rule: + +- Keep `digitClassifier.enabled=false` by default until fallback `ON` beats fallback `OFF` on `MAE` and does not regress exact-match/no-read guardrails or increase `mismatch`. + +## High-Impact Tuning Areas + +### 1) Candidate Generation (`ocr-no-digits`) + +File: `src/ocr/alignment.js` + +Focus: + +- Rotation variant quality +- Edge-window extraction stability +- Normalization width and crop quality for strip readability + +Signal to watch: + +- Empty `topCandidates` in selection logs +- Debug stage `6` visually clear but still no accepted candidate + +### 2) Word-pass Input Modes (`mismatch` vs `ocr-no-digits`) + +File: `src/ocr/config.js` (`OCR_CONFIG.roiDeterministic.wordPassModes`) + +Examples: + +- `['raw']` (current default): conservative +- `['raw', 'soft']`: can reduce no-read but may increase mismatches +- `['raw', 'soft', 'binary']`: often increases wrong confident reads + +Use temporary experiments first, then codify only if net-positive. + +### 3) Acceptance/Support Guardrails + +Files: + +- `src/ocr/pipeline.js` (`finalizeSelection`, evidence ranking) +- `src/ocr/recognition.js` (candidate scoring) + +Focus: + +- Balance strictness (avoid false positives) vs recall (avoid no-read). +- Validate with histogram movement, not single-image anecdotes. +- Active guardrails in current pipeline: word-pass support (`hits` / `topHits` vs `minWordPassHits`) plus edge-candidate corroboration/cell-strength checks. + +### 4) ROI Sanity Gates (usually not primary blocker) + +Files: + +- `src/ocr/neural-roi.js` +- `src/ocr/config.js` (`neuralRoi.sanity`) + +Use only if evidence shows valid ROI boxes are being rejected. +Recent test-set verification showed no `invalid-geometry` failures. + +## Useful Runtime Artifacts + +- Browser selection logs: + - `window.__jarvisOcrSelectionLogs` +- Last run histogram: + - `window.__jarvisLastTestSetHistogram` + +## Commit Checklist (OCR Changes) + +1. `npm run test:e2e` passes. +2. UI test-set rerun completed. +3. `MAE` and guardrail deltas documented (`Exact Match`, `No-read`, and improved/regressed image counts). +4. Any tuning knob changes are explained in PR notes. diff --git a/index.html b/index.html index dd0a638..bebea56 100644 --- a/index.html +++ b/index.html @@ -137,12 +137,40 @@

3. Review the email

Tip: open Gmail draft, review once more, then press send.

+ +
+
+

4. Debug the test set

+

Runs the local assets test set and reports MAE, exact-match/no-read guardrails, and failure reasons.

+
+
+ +

Test set idle.

+
+
+
+
+

Alignment debug overlay

+

Shows detected meter face, chosen rotation, detected digit strip, and final OCR input crop for each run.

+
+
+ + +
+
+

No debug frames yet.

+
+
+
- + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..76dd5b7 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,67 @@ +{ + "name": "jarvis", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "jarvis", + "version": "0.1.0", + "devDependencies": { + "@playwright/test": "1.58.1" + } + }, + "node_modules/@playwright/test": { + "version": "1.58.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.1.tgz", + "integrity": "sha512-6LdVIUERWxQMmUSSQi0I53GgCBYgM2RpGngCPY7hSeju+VrKjq3lvs7HpJoPbDiY5QM5EYRtRX5fvrinnMAz3w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.58.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright": { + "version": "1.58.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.1.tgz", + "integrity": "sha512-+2uTZHxSCcxjvGc5C891LrS1/NlxglGxzrC4seZiVjcYVQfUa87wBL6rTDqzGjuoWNjnBzRqKmF6zRYGMvQUaQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.58.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.58.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.1.tgz", + "integrity": "sha512-bcWzOaTxcW+VOOGBCQgnaKToLJ65d6AqfLVKEWvexyS3AS6rbXl+xdpYRMGSRBClPvyj44njOWoxjNdL/H9UNg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "dev": true, + "optional": true + } + } +} diff --git a/package.json b/package.json index 9053274..b01ffe6 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,12 @@ "description": "Jarvis personal assistant web app", "scripts": { "serve": "python3 -m http.server 8000", - "dev": "python3 -m http.server 8000" + "dev": "python3 -m http.server 8000", + "test:e2e": "playwright test", + "test:e2e:headed": "playwright test --headed", + "benchmark:roi-diff": "node scripts/compare-roi-checkpoints.cjs" + }, + "devDependencies": { + "@playwright/test": "1.58.1" } } diff --git a/playwright.config.js b/playwright.config.js new file mode 100644 index 0000000..c91337e --- /dev/null +++ b/playwright.config.js @@ -0,0 +1,32 @@ +const { defineConfig, devices } = require('@playwright/test'); + +module.exports = defineConfig({ + testDir: './tests/e2e', + fullyParallel: false, + timeout: 30_000, + expect: { + timeout: 10_000 + }, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 1 : 0, + workers: process.env.CI ? 1 : undefined, + reporter: process.env.CI + ? [['github'], ['html', { open: 'never' }]] + : 'list', + use: { + baseURL: 'http://127.0.0.1:8000', + trace: 'on-first-retry' + }, + webServer: { + command: 'npm run serve', + url: 'http://127.0.0.1:8000', + reuseExistingServer: !process.env.CI, + timeout: 120_000 + }, + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] } + } + ] +}); diff --git a/scripts/compare-roi-checkpoints.cjs b/scripts/compare-roi-checkpoints.cjs new file mode 100644 index 0000000..8afb4c1 --- /dev/null +++ b/scripts/compare-roi-checkpoints.cjs @@ -0,0 +1,926 @@ +#!/usr/bin/env node + +const fs = require('node:fs'); +const fsp = require('node:fs/promises'); +const path = require('node:path'); +const http = require('node:http'); +const { spawn } = require('node:child_process'); +const { chromium } = require('playwright'); + +const ROOT_DIR = path.resolve(__dirname, '..'); +const OUTPUT_ROOT = path.join(ROOT_DIR, 'output', 'roi-checkpoint-diff'); +const FRONTEND_URL = process.env.JARVIS_FRONTEND_URL || 'http://127.0.0.1:8000'; +const BACKEND_URL = process.env.JARVIS_BACKEND_URL || 'http://127.0.0.1:8001'; +const BACKEND_HEALTH_URL = `${BACKEND_URL}/health`; +const ENABLE_DIGIT_FALLBACK = process.env.JARVIS_DIGIT_FALLBACK === '1'; + +const parseHttpUrl = (raw, fallbackRaw) => { + try { + return new URL(raw); + } catch { + return new URL(fallbackRaw); + } +}; + +const toBindConfig = (urlValue, fallbackRaw, fallbackPort) => { + const parsed = parseHttpUrl(urlValue, fallbackRaw); + const host = parsed.hostname || '127.0.0.1'; + const parsedPort = Number.parseInt(parsed.port, 10); + const port = Number.isFinite(parsedPort) ? parsedPort : fallbackPort; + return { + host, + port + }; +}; + +const FRONTEND_BIND = toBindConfig(FRONTEND_URL, 'http://127.0.0.1:8000', 8000); +const BACKEND_BIND = toBindConfig(BACKEND_URL, 'http://127.0.0.1:8001', 8001); + +const MODEL_RUNS = [ + { + id: 'baseline', + label: 'roi-rotaug-e30-640.pt', + modelPath: path.join(ROOT_DIR, 'backend', 'models', 'roi-rotaug-e30-640.pt') + }, + { + id: 'challenger', + label: 'roi.pt', + modelPath: path.join(ROOT_DIR, 'backend', 'models', 'roi.pt') + } +]; + +const STAGE_NAMES = { + strip: '5. detected strip crop', + ocr: '6. OCR input candidate' +}; + +const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); + +const timestampId = () => { + const now = new Date(); + const parts = [ + now.getFullYear(), + String(now.getMonth() + 1).padStart(2, '0'), + String(now.getDate()).padStart(2, '0'), + String(now.getHours()).padStart(2, '0'), + String(now.getMinutes()).padStart(2, '0'), + String(now.getSeconds()).padStart(2, '0') + ]; + return `${parts[0]}${parts[1]}${parts[2]}-${parts[3]}${parts[4]}${parts[5]}`; +}; + +const sanitizeFileToken = (input) => { + return String(input || '') + .trim() + .replace(/[^a-zA-Z0-9._-]+/g, '_') + .replace(/^_+|_+$/g, '') + || 'unknown'; +}; + +const stripFileExt = (input) => String(input || '').replace(/\.[^.]+$/, ''); + +const toRelativeFromRoot = (absPath) => path.relative(ROOT_DIR, absPath).replace(/\\/g, '/'); + +const parseDataUrl = (dataUrl) => { + if (typeof dataUrl !== 'string') { + return null; + } + const match = dataUrl.match(/^data:(image\/[a-zA-Z0-9.+-]+);base64,([A-Za-z0-9+/=]+)$/); + if (!match) { + return null; + } + const mime = match[1]; + const base64 = match[2]; + let extension = 'bin'; + if (mime === 'image/jpeg') { + extension = 'jpg'; + } else if (mime === 'image/png') { + extension = 'png'; + } else if (mime === 'image/webp') { + extension = 'webp'; + } + return { + mime, + extension, + buffer: Buffer.from(base64, 'base64') + }; +}; + +const tailText = (value, maxLength = 5000) => { + const text = String(value || ''); + return text.length <= maxLength ? text : text.slice(text.length - maxLength); +}; + +const spawnTrackedProcess = (command, args, options = {}) => { + const child = spawn(command, args, { + cwd: options.cwd || ROOT_DIR, + env: options.env || process.env, + stdio: ['ignore', 'pipe', 'pipe'] + }); + let stdout = ''; + let stderr = ''; + const appendOutput = (chunk, target) => { + if (!chunk) { + return target; + } + return tailText(`${target}${chunk.toString()}`); + }; + child.stdout.on('data', (chunk) => { + stdout = appendOutput(chunk, stdout); + }); + child.stderr.on('data', (chunk) => { + stderr = appendOutput(chunk, stderr); + }); + return { + child, + getStdout: () => stdout, + getStderr: () => stderr, + stop: async () => { + if (child.exitCode !== null || child.signalCode !== null) { + return; + } + child.kill('SIGTERM'); + for (let i = 0; i < 30; i += 1) { + if (child.exitCode !== null || child.signalCode !== null) { + return; + } + await sleep(100); + } + if (child.exitCode === null && child.signalCode === null) { + child.kill('SIGKILL'); + } + } + }; +}; + +const requestJson = (url) => { + return new Promise((resolve, reject) => { + const req = http.get(url, (res) => { + let body = ''; + res.setEncoding('utf8'); + res.on('data', (chunk) => { + body += chunk; + }); + res.on('end', () => { + const statusCode = res.statusCode || 0; + if (statusCode < 200 || statusCode >= 300) { + reject(new Error(`HTTP ${statusCode}`)); + return; + } + try { + resolve(JSON.parse(body)); + } catch (error) { + reject(error); + } + }); + }); + req.on('error', reject); + req.setTimeout(2500, () => { + req.destroy(new Error('timeout')); + }); + }); +}; + +const requestOk = (url) => { + return new Promise((resolve, reject) => { + const req = http.get(url, (res) => { + const statusCode = res.statusCode || 0; + res.resume(); + if (statusCode >= 200 && statusCode < 300) { + resolve(true); + return; + } + reject(new Error(`HTTP ${statusCode}`)); + }); + req.on('error', reject); + req.setTimeout(2500, () => { + req.destroy(new Error('timeout')); + }); + }); +}; + +const waitForBackendReady = async (expectedModelPath, timeoutMs, trackedBackend) => { + const deadline = Date.now() + timeoutMs; + const expectedResolved = path.resolve(expectedModelPath); + let lastError = null; + while (Date.now() < deadline) { + if (trackedBackend.child.exitCode !== null) { + throw new Error( + [ + 'Backend exited unexpectedly before /health was ready.', + `stdout:\n${trackedBackend.getStdout()}`, + `stderr:\n${trackedBackend.getStderr()}` + ].join('\n') + ); + } + try { + const health = await requestJson(BACKEND_HEALTH_URL); + const modelPath = typeof health.model_path === 'string' ? path.resolve(health.model_path) : null; + const ready = !!health.roi_ready; + if (ready && modelPath === expectedResolved) { + return health; + } + lastError = new Error(`health not ready for expected model (${modelPath || 'unknown'})`); + } catch (error) { + lastError = error; + } + await sleep(500); + } + throw new Error( + [ + `Timed out waiting for backend /health to serve ${expectedResolved}.`, + `Last error: ${lastError ? String(lastError.message || lastError) : 'unknown'}`, + `stdout:\n${trackedBackend.getStdout()}`, + `stderr:\n${trackedBackend.getStderr()}` + ].join('\n') + ); +}; + +const ensureFrontendAvailable = async (timeoutMs = 120000) => { + try { + await requestOk(FRONTEND_URL); + return { + reused: true, + process: null + }; + } catch { + // Start local server below. + } + + const tracked = spawnTrackedProcess( + 'python3', + ['-m', 'http.server', String(FRONTEND_BIND.port), '--bind', FRONTEND_BIND.host], + { + cwd: ROOT_DIR + } + ); + + const deadline = Date.now() + timeoutMs; + let lastError = null; + while (Date.now() < deadline) { + if (tracked.child.exitCode !== null) { + throw new Error( + [ + 'Frontend server failed to start.', + `stdout:\n${tracked.getStdout()}`, + `stderr:\n${tracked.getStderr()}` + ].join('\n') + ); + } + try { + await requestOk(FRONTEND_URL); + return { + reused: false, + process: tracked + }; + } catch (error) { + lastError = error; + } + await sleep(400); + } + + await tracked.stop(); + throw new Error(`Timed out waiting for frontend server: ${String(lastError || 'unknown error')}`); +}; + +const computeTopRejectReason = (selectionLog) => { + if (!selectionLog || !Array.isArray(selectionLog.rejectSummary) || !selectionLog.rejectSummary.length) { + return null; + } + const top = selectionLog.rejectSummary[0]; + if (!top || !top.reason) { + return null; + } + return String(top.reason); +}; + +const runUiTestSet = async () => { + const browser = await chromium.launch({ headless: true }); + const page = await browser.newPage(); + + try { + if (ENABLE_DIGIT_FALLBACK) { + await page.addInitScript(() => { + window.__JARVIS_OCR_CONFIG_OVERRIDE__ = { + digitClassifier: { + enabled: true, + fallbackOnNoDigitsOnly: true + } + }; + }); + } + await page.goto(FRONTEND_URL, { waitUntil: 'networkidle' }); + await page.waitForSelector('#run-test-btn', { timeout: 30000 }); + await page.evaluate(() => { + window.__jarvisOcrSelectionLogs = []; + window.__jarvisLastTestSetHistogram = null; + const debugToggle = document.getElementById('debug-overlay-toggle'); + if (debugToggle && !debugToggle.checked) { + debugToggle.checked = true; + debugToggle.dispatchEvent(new Event('change', { bubbles: true })); + } + const clearButton = document.getElementById('clear-debug-btn'); + if (clearButton) { + clearButton.click(); + } + }); + + await page.click('#run-test-btn'); + await page.waitForFunction(() => { + const statusEl = document.getElementById('test-status'); + if (!statusEl) { + return false; + } + const text = (statusEl.textContent || '').trim(); + return text.startsWith('Done.'); + }, undefined, { timeout: 600000 }); + + const payload = await page.evaluate(() => { + const text = (node) => (node && node.textContent ? node.textContent.trim() : ''); + const tableRows = []; + const table = document.querySelector('#test-results table'); + if (table) { + const rows = Array.from(table.querySelectorAll('tr')).slice(1); + rows.forEach((row) => { + const cells = Array.from(row.querySelectorAll('td')).map((cell) => text(cell)); + if (cells.length < 6) { + return; + } + tableRows.push({ + filename: cells[0], + expected: cells[1], + detected: cells[2] === '—' ? '' : cells[2], + absoluteError: cells[3] === '—' ? '' : cells[3], + failureReason: cells[4] === '—' ? '' : cells[4], + result: cells[5] + }); + }); + } + + const sessions = Array.from(document.querySelectorAll('.debug-session')).map((session) => { + const label = text(session.querySelector('.debug-session-title')); + const stages = Array.from(session.querySelectorAll('.debug-stage')).map((stage) => ({ + name: text(stage.querySelector('.debug-stage-name')), + dataUrl: stage.querySelector('img') ? stage.querySelector('img').src : '' + })); + return { label, stages }; + }); + + return { + status: text(document.getElementById('test-status')), + rows: tableRows, + histogram: window.__jarvisLastTestSetHistogram || null, + selectionLogs: Array.isArray(window.__jarvisOcrSelectionLogs) ? window.__jarvisOcrSelectionLogs : [], + sessions + }; + }); + return payload; + } finally { + await page.close(); + await browser.close(); + } +}; + +const exportStageImages = async (sessions, runOutputDir, runId) => { + const stageDir = path.join(runOutputDir, 'stages'); + await fsp.mkdir(stageDir, { recursive: true }); + const stageIndex = new Map(); + + const exportStage = async (stage, stageSlug, nameBase) => { + if (!stage || !stage.dataUrl) { + return null; + } + const parsed = parseDataUrl(stage.dataUrl); + if (!parsed) { + return null; + } + const fileName = `${nameBase}_${stageSlug}_${runId}.${parsed.extension}`; + const absPath = path.join(stageDir, fileName); + await fsp.writeFile(absPath, parsed.buffer); + return toRelativeFromRoot(absPath); + }; + + for (const session of sessions || []) { + const label = String(session && session.label ? session.label : '').trim(); + if (!label) { + continue; + } + const key = label; + const nameBase = sanitizeFileToken(stripFileExt(label)); + const stages = Array.isArray(session.stages) ? session.stages : []; + const entry = {}; + const stripStage = stages.find((stage) => stage && stage.name === STAGE_NAMES.strip) || null; + const ocrStages = stages.filter((stage) => stage && stage.name === STAGE_NAMES.ocr); + const lastOcrStage = ocrStages.length ? ocrStages[ocrStages.length - 1] : null; + const stripRelPath = await exportStage(stripStage, 'stage5_strip', nameBase); + const ocrRelPath = await exportStage(lastOcrStage, 'stage6_ocr', nameBase); + if (stripRelPath) { + entry.stage5 = stripRelPath; + } + if (ocrRelPath) { + entry.stage6 = ocrRelPath; + } + stageIndex.set(key, entry); + } + return stageIndex; +}; + +const rowsByFilename = (rows) => { + const map = new Map(); + for (const row of rows || []) { + if (!row || !row.filename) { + continue; + } + map.set(String(row.filename), row); + } + return map; +}; + +const logsByImage = (logs) => { + const map = new Map(); + for (const log of logs || []) { + if (!log || !log.image) { + continue; + } + map.set(String(log.image), log); + } + return map; +}; + +const classifyReason = (reason) => { + const text = String(reason || '').toLowerCase(); + if (!text || text === '—') { + return null; + } + if (text === 'mismatch') { + return 'mismatch'; + } + if (text.includes('ocr-no-digits')) { + return 'ocr-no-digits'; + } + if (text.includes('no-detection')) { + return 'no-detection'; + } + return text; +}; + +const parseMeterValue = (value) => { + const digits = String(value || '').replace(/\D/g, ''); + if (!digits) { + return null; + } + const parsed = Number.parseInt(digits, 10); + return Number.isFinite(parsed) ? parsed : null; +}; + +const computeRowAbsoluteError = (row) => { + if (!row) { + return null; + } + const expectedValue = parseMeterValue(row.expected); + const detectedValue = parseMeterValue(row.detected); + if (!Number.isFinite(expectedValue) || !Number.isFinite(detectedValue)) { + return null; + } + return Math.abs(expectedValue - detectedValue); +}; + +const comparisonErrorValue = (row) => { + const absoluteError = computeRowAbsoluteError(row); + return Number.isFinite(absoluteError) ? absoluteError : Number.POSITIVE_INFINITY; +}; + +const buildFailureHistogram = (rows) => { + const counts = new Map(); + for (const row of rows || []) { + if (!row || row.result === 'Pass') { + continue; + } + const reason = row.failureReason || 'unknown'; + counts.set(reason, (counts.get(reason) || 0) + 1); + } + return [...counts.entries()] + .map(([reason, count]) => ({ reason, count })) + .sort((a, b) => b.count - a.count || a.reason.localeCompare(b.reason)); +}; + +const computeMetrics = (rows) => { + const total = rows.length; + let exactMatchCount = 0; + let noReadCount = 0; + let maeSum = 0; + let maeRows = 0; + let mismatch = 0; + let ocrNoDigits = 0; + let noDetection = 0; + + for (const row of rows) { + const hasDetectedValue = Boolean(String(row.detected || '').trim()); + if (!hasDetectedValue) { + noReadCount += 1; + } + + if (row.result === 'Pass') { + exactMatchCount += 1; + } else { + const reasonClass = classifyReason(row.failureReason); + if (reasonClass === 'mismatch') { + mismatch += 1; + } + if (reasonClass === 'ocr-no-digits') { + ocrNoDigits += 1; + } + if (reasonClass === 'no-detection') { + noDetection += 1; + } + } + + const absoluteError = computeRowAbsoluteError(row); + if (Number.isFinite(absoluteError)) { + maeSum += absoluteError; + maeRows += 1; + } + } + + const mae = maeRows ? maeSum / maeRows : null; + const exactMatchRate = total ? exactMatchCount / total : 0; + const noReadRate = total ? noReadCount / total : 0; + + return { + total, + correct: exactMatchCount, + failed: total - exactMatchCount, + accuracy: exactMatchRate, + exactMatchCount, + exactMatchRate, + noReadCount, + noReadRate, + mae, + maeRows, + maeSum, + mismatch, + ocrNoDigits, + noDetection, + failureReasons: buildFailureHistogram(rows) + }; +}; + +const formatPct = (value) => `${(value * 100).toFixed(1)}%`; +const formatSigned = (value, digits = 3) => `${value >= 0 ? '+' : ''}${value.toFixed(digits)}`; +const formatMae = (value) => (Number.isFinite(value) ? value.toFixed(2) : 'n/a'); + +const markdownEscape = (value) => String(value || '').replace(/\|/g, '\\|'); +const resolveSelectedMetadata = (selectionLog) => { + const selected = selectionLog && selectionLog.selected && typeof selectionLog.selected === 'object' + ? selectionLog.selected + : null; + if (!selected) { + return { + selectedSourceLabel: null, + selectedMethod: null, + selectedPreprocessMode: null + }; + } + return { + selectedSourceLabel: selected.sourceLabel ? String(selected.sourceLabel) : null, + selectedMethod: selected.method ? String(selected.method) : null, + selectedPreprocessMode: selected.preprocessMode ? String(selected.preprocessMode) : null + }; +}; + +const buildComparison = (baselineRun, challengerRun) => { + const baselineRows = rowsByFilename(baselineRun.rows); + const challengerRows = rowsByFilename(challengerRun.rows); + const baselineLogs = logsByImage(baselineRun.selectionLogs); + const challengerLogs = logsByImage(challengerRun.selectionLogs); + + const names = [...new Set([...baselineRows.keys(), ...challengerRows.keys()])].sort((a, b) => a.localeCompare(b)); + const rows = names.map((filename) => { + const base = baselineRows.get(filename) || null; + const next = challengerRows.get(filename) || null; + const baseLog = baselineLogs.get(filename) || null; + const nextLog = challengerLogs.get(filename) || null; + return { + filename, + expected: (base && base.expected) || (next && next.expected) || '', + baseline: base ? { + detected: base.detected || '', + failureReason: base.failureReason || '', + result: base.result || '', + absoluteError: base.absoluteError || '', + topRejectReason: computeTopRejectReason(baseLog), + ...resolveSelectedMetadata(baseLog) + } : null, + challenger: next ? { + detected: next.detected || '', + failureReason: next.failureReason || '', + result: next.result || '', + absoluteError: next.absoluteError || '', + topRejectReason: computeTopRejectReason(nextLog), + ...resolveSelectedMetadata(nextLog) + } : null + }; + }); + + const EPSILON = 1e-9; + const improved = rows.filter((row) => { + if (!row.baseline || !row.challenger) { + return false; + } + const baselineError = comparisonErrorValue({ + expected: row.expected, + detected: row.baseline.detected + }); + const challengerError = comparisonErrorValue({ + expected: row.expected, + detected: row.challenger.detected + }); + return challengerError < baselineError - EPSILON; + }).length; + const regressed = rows.filter((row) => { + if (!row.baseline || !row.challenger) { + return false; + } + const baselineError = comparisonErrorValue({ + expected: row.expected, + detected: row.baseline.detected + }); + const challengerError = comparisonErrorValue({ + expected: row.expected, + detected: row.challenger.detected + }); + return challengerError > baselineError + EPSILON; + }).length; + + return { + rows, + improved, + regressed + }; +}; + +const renderMarkdownReport = ({ + generatedAt, + fallbackEnabled, + baselineRun, + challengerRun, + baselineMetrics, + challengerMetrics, + comparisonRows, + improved, + regressed, + outputDir +}) => { + const lines = []; + lines.push('# ROI Checkpoint Comparison'); + lines.push(''); + lines.push(`- Generated: ${generatedAt}`); + lines.push(`- Baseline model: \`${baselineRun.model.label}\` (\`${baselineRun.health.model_path}\`)`); + lines.push(`- Challenger model: \`${challengerRun.model.label}\` (\`${challengerRun.health.model_path}\`)`); + lines.push(`- Digit classifier fallback: \`${fallbackEnabled ? 'enabled' : 'disabled'}\``); + lines.push(`- Output directory: \`${toRelativeFromRoot(outputDir)}\``); + lines.push(''); + lines.push('## Summary'); + lines.push(''); + lines.push('| Metric | Baseline | Challenger | Delta |'); + lines.push('| --- | ---: | ---: | ---: |'); + lines.push(`| MAE (lower is better) | ${formatMae(baselineMetrics.mae)} | ${formatMae(challengerMetrics.mae)} | ${Number.isFinite(baselineMetrics.mae) && Number.isFinite(challengerMetrics.mae) ? formatSigned(challengerMetrics.mae - baselineMetrics.mae, 2) : 'n/a'} |`); + lines.push(`| MAE rows | ${baselineMetrics.maeRows}/${baselineMetrics.total} | ${challengerMetrics.maeRows}/${challengerMetrics.total} | ${challengerMetrics.maeRows - baselineMetrics.maeRows} |`); + lines.push(`| Exact Match (guardrail) | ${baselineMetrics.exactMatchCount}/${baselineMetrics.total} (${formatPct(baselineMetrics.exactMatchRate)}) | ${challengerMetrics.exactMatchCount}/${challengerMetrics.total} (${formatPct(challengerMetrics.exactMatchRate)}) | ${formatSigned((challengerMetrics.exactMatchRate - baselineMetrics.exactMatchRate) * 100, 1)}% |`); + lines.push(`| No-read (guardrail, lower is better) | ${baselineMetrics.noReadCount}/${baselineMetrics.total} (${formatPct(baselineMetrics.noReadRate)}) | ${challengerMetrics.noReadCount}/${challengerMetrics.total} (${formatPct(challengerMetrics.noReadRate)}) | ${formatSigned((challengerMetrics.noReadRate - baselineMetrics.noReadRate) * 100, 1)}% |`); + lines.push(`| mismatch | ${baselineMetrics.mismatch} | ${challengerMetrics.mismatch} | ${challengerMetrics.mismatch - baselineMetrics.mismatch} |`); + lines.push(`| ocr-no-digits | ${baselineMetrics.ocrNoDigits} | ${challengerMetrics.ocrNoDigits} | ${challengerMetrics.ocrNoDigits - baselineMetrics.ocrNoDigits} |`); + lines.push(`| no-detection | ${baselineMetrics.noDetection} | ${challengerMetrics.noDetection} | ${challengerMetrics.noDetection - baselineMetrics.noDetection} |`); + lines.push(`| Improved images (by absolute error) | - | ${improved} | - |`); + lines.push(`| Regressed images (by absolute error) | - | ${regressed} | - |`); + lines.push(''); + lines.push('## Failure Histogram'); + lines.push(''); + lines.push('### Baseline'); + lines.push(''); + lines.push('| Reason | Count |'); + lines.push('| --- | ---: |'); + baselineMetrics.failureReasons.forEach((entry) => { + lines.push(`| ${markdownEscape(entry.reason)} | ${entry.count} |`); + }); + lines.push(''); + lines.push('### Challenger'); + lines.push(''); + lines.push('| Reason | Count |'); + lines.push('| --- | ---: |'); + challengerMetrics.failureReasons.forEach((entry) => { + lines.push(`| ${markdownEscape(entry.reason)} | ${entry.count} |`); + }); + lines.push(''); + lines.push('## Per-image Diff'); + lines.push(''); + lines.push('| File | Expected | Baseline detected | Challenger detected | Baseline abs error | Challenger abs error | Baseline reason | Challenger reason | Baseline reject | Challenger reject | Baseline selected source | Challenger selected source | Baseline selected method | Challenger selected method | Baseline selected preprocess | Challenger selected preprocess | Baseline stage5 | Challenger stage5 | Baseline stage6 | Challenger stage6 |'); + lines.push('| --- | ---: | ---: | ---: | ---: | ---: | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |'); + + comparisonRows.forEach((row) => { + const base = row.baseline || {}; + const next = row.challenger || {}; + const baseStage = baselineRun.stageIndex.get(row.filename) || {}; + const nextStage = challengerRun.stageIndex.get(row.filename) || {}; + const baseAbsoluteError = computeRowAbsoluteError({ + expected: row.expected, + detected: base.detected || '' + }); + const nextAbsoluteError = computeRowAbsoluteError({ + expected: row.expected, + detected: next.detected || '' + }); + const cols = [ + markdownEscape(row.filename), + markdownEscape(row.expected), + markdownEscape(base.detected || '—'), + markdownEscape(next.detected || '—'), + markdownEscape(Number.isFinite(baseAbsoluteError) ? String(baseAbsoluteError) : '—'), + markdownEscape(Number.isFinite(nextAbsoluteError) ? String(nextAbsoluteError) : '—'), + markdownEscape(base.failureReason || '—'), + markdownEscape(next.failureReason || '—'), + markdownEscape(base.topRejectReason || '—'), + markdownEscape(next.topRejectReason || '—'), + markdownEscape(base.selectedSourceLabel || '—'), + markdownEscape(next.selectedSourceLabel || '—'), + markdownEscape(base.selectedMethod || '—'), + markdownEscape(next.selectedMethod || '—'), + markdownEscape(base.selectedPreprocessMode || '—'), + markdownEscape(next.selectedPreprocessMode || '—'), + markdownEscape(baseStage.stage5 || '—'), + markdownEscape(nextStage.stage5 || '—'), + markdownEscape(baseStage.stage6 || '—'), + markdownEscape(nextStage.stage6 || '—') + ]; + lines.push(`| ${cols.join(' | ')} |`); + }); + + lines.push(''); + lines.push('## Notes'); + lines.push(''); + lines.push('- `topRejectReason` comes from each image selection log (`rejectSummary[0].reason`).'); + lines.push('- `selected` columns come from each image selection log (`selected.sourceLabel`, `selected.method`, `selected.preprocessMode`).'); + lines.push('- Stage `6. OCR input candidate` snapshot explicitly exports the last stage-6 frame from each debug session.'); + lines.push('- Stage snapshot paths are written relative to repository root and saved from debug overlay image data.'); + lines.push(''); + + return `${lines.join('\n')}\n`; +}; + +const runCheckpoint = async (model, runDir) => { + const backendProc = spawnTrackedProcess( + './.venv/bin/uvicorn', + ['app:app', '--host', BACKEND_BIND.host, '--port', String(BACKEND_BIND.port)], + { + cwd: path.join(ROOT_DIR, 'backend'), + env: { + ...process.env, + ROI_MODEL_PATH: model.modelPath + } + } + ); + + try { + const health = await waitForBackendReady(model.modelPath, 120000, backendProc); + const uiResult = await runUiTestSet(); + const stageIndex = await exportStageImages(uiResult.sessions || [], runDir, model.id); + return { + model, + health, + rows: uiResult.rows || [], + histogram: uiResult.histogram || null, + selectionLogs: uiResult.selectionLogs || [], + status: uiResult.status || '', + stageIndex, + backendStdoutTail: backendProc.getStdout(), + backendStderrTail: backendProc.getStderr() + }; + } finally { + await backendProc.stop(); + } +}; + +const writeJson = async (filePath, value) => { + await fsp.writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf8'); +}; + +const ensureModelFilesExist = () => { + const missing = MODEL_RUNS.filter((run) => !fs.existsSync(run.modelPath)); + if (!missing.length) { + return; + } + const list = missing.map((item) => `- ${item.modelPath}`).join('\n'); + throw new Error(`Missing required model files:\n${list}`); +}; + +const run = async () => { + ensureModelFilesExist(); + const runId = timestampId(); + const modeSuffix = ENABLE_DIGIT_FALLBACK ? 'fallback-on' : 'fallback-off'; + const outputDir = path.join(OUTPUT_ROOT, `${runId}-${modeSuffix}`); + await fsp.mkdir(outputDir, { recursive: true }); + + const frontend = await ensureFrontendAvailable(); + + try { + const baselineDir = path.join(outputDir, MODEL_RUNS[0].id); + const challengerDir = path.join(outputDir, MODEL_RUNS[1].id); + await fsp.mkdir(baselineDir, { recursive: true }); + await fsp.mkdir(challengerDir, { recursive: true }); + + const baselineRun = await runCheckpoint(MODEL_RUNS[0], baselineDir); + const challengerRun = await runCheckpoint(MODEL_RUNS[1], challengerDir); + + const baselineMetrics = computeMetrics(baselineRun.rows); + const challengerMetrics = computeMetrics(challengerRun.rows); + const comparison = buildComparison(baselineRun, challengerRun); + + const generatedAt = new Date().toISOString(); + const reportJson = { + generatedAt, + fallbackEnabled: ENABLE_DIGIT_FALLBACK, + outputDir: toRelativeFromRoot(outputDir), + baseline: { + model: baselineRun.model, + health: baselineRun.health, + status: baselineRun.status, + metrics: baselineMetrics, + rows: baselineRun.rows + }, + challenger: { + model: challengerRun.model, + health: challengerRun.health, + status: challengerRun.status, + metrics: challengerMetrics, + rows: challengerRun.rows + }, + comparison: { + improved: comparison.improved, + regressed: comparison.regressed, + rows: comparison.rows + } + }; + + const markdown = renderMarkdownReport({ + generatedAt, + fallbackEnabled: ENABLE_DIGIT_FALLBACK, + baselineRun, + challengerRun, + baselineMetrics, + challengerMetrics, + comparisonRows: comparison.rows, + improved: comparison.improved, + regressed: comparison.regressed, + outputDir + }); + + const jsonPath = path.join(outputDir, 'roi-diff-report.json'); + const mdPath = path.join(outputDir, 'roi-diff-report.md'); + await writeJson(jsonPath, reportJson); + await fsp.writeFile(mdPath, markdown, 'utf8'); + + const consoleSummary = { + outputDir: toRelativeFromRoot(outputDir), + fallbackEnabled: ENABLE_DIGIT_FALLBACK, + markdownReport: toRelativeFromRoot(mdPath), + jsonReport: toRelativeFromRoot(jsonPath), + baselinePrimary: { + mae: Number.isFinite(baselineMetrics.mae) ? Number(baselineMetrics.mae.toFixed(4)) : null, + maeRows: baselineMetrics.maeRows + }, + challengerPrimary: { + mae: Number.isFinite(challengerMetrics.mae) ? Number(challengerMetrics.mae.toFixed(4)) : null, + maeRows: challengerMetrics.maeRows + }, + guardrails: { + baselineExactMatchRate: Number((baselineMetrics.exactMatchRate * 100).toFixed(2)), + challengerExactMatchRate: Number((challengerMetrics.exactMatchRate * 100).toFixed(2)), + baselineNoReadRate: Number((baselineMetrics.noReadRate * 100).toFixed(2)), + challengerNoReadRate: Number((challengerMetrics.noReadRate * 100).toFixed(2)) + }, + baselineFailureMix: { + mismatch: baselineMetrics.mismatch, + ocrNoDigits: baselineMetrics.ocrNoDigits, + noDetection: baselineMetrics.noDetection + }, + challengerFailureMix: { + mismatch: challengerMetrics.mismatch, + ocrNoDigits: challengerMetrics.ocrNoDigits, + noDetection: challengerMetrics.noDetection + }, + improved: comparison.improved, + regressed: comparison.regressed + }; + + process.stdout.write(`${JSON.stringify(consoleSummary, null, 2)}\n`); + } finally { + if (frontend.process) { + await frontend.process.stop(); + } + } +}; + +run().catch((error) => { + process.stderr.write(`${error && error.stack ? error.stack : String(error)}\n`); + process.exitCode = 1; +}); diff --git a/src/debug/overlay.js b/src/debug/overlay.js new file mode 100644 index 0000000..4eacd91 --- /dev/null +++ b/src/debug/overlay.js @@ -0,0 +1,123 @@ +const scaleCanvasForPreview = (source, targetWidth) => { + if (source.width <= targetWidth) { + return source; + } + const scale = targetWidth / source.width; + const canvas = document.createElement('canvas'); + canvas.width = Math.round(source.width * scale); + canvas.height = Math.round(source.height * scale); + const ctx = canvas.getContext('2d'); + ctx.imageSmoothingEnabled = true; + ctx.drawImage(source, 0, 0, canvas.width, canvas.height); + return canvas; +}; + +const toDebugDataUrl = (source, previewWidth) => { + const preview = scaleCanvasForPreview(source, previewWidth); + return preview.toDataURL('image/jpeg', 0.9); +}; + +const renderDebugSessions = (resultsEl, sessions) => { + if (!resultsEl) { + return; + } + + resultsEl.innerHTML = ''; + if (!sessions.length) { + const empty = document.createElement('p'); + empty.className = 'muted'; + empty.textContent = 'No debug frames yet.'; + resultsEl.appendChild(empty); + return; + } + + sessions.forEach((session) => { + const wrapper = document.createElement('article'); + wrapper.className = 'debug-session'; + + const title = document.createElement('p'); + title.className = 'debug-session-title'; + title.textContent = session.label; + wrapper.appendChild(title); + + const grid = document.createElement('div'); + grid.className = 'debug-stage-grid'; + session.stages.forEach((stage) => { + const card = document.createElement('article'); + card.className = 'debug-stage'; + + const img = document.createElement('img'); + img.src = stage.dataUrl; + img.alt = `${session.label} ${stage.name}`; + card.appendChild(img); + + const caption = document.createElement('p'); + caption.className = 'debug-stage-name'; + caption.textContent = stage.name; + card.appendChild(caption); + + grid.appendChild(card); + }); + + wrapper.appendChild(grid); + resultsEl.appendChild(wrapper); + }); +}; + +const createDebugOverlayManager = ({ + toggleEl, + resultsEl, + maxSessions = 14, + previewWidth = 260 +}) => { + const sessions = []; + + const render = () => { + renderDebugSessions(resultsEl, sessions); + }; + + const clear = () => { + sessions.length = 0; + render(); + }; + + const startSession = (label) => { + if (!toggleEl || !toggleEl.checked) { + return null; + } + return { label, stages: [] }; + }; + + const addStage = (session, name, canvas) => { + if (!session || !canvas) { + return; + } + session.stages.push({ + name, + dataUrl: toDebugDataUrl(canvas, previewWidth) + }); + }; + + const commitSession = (session) => { + if (!session || !session.stages.length) { + return; + } + sessions.unshift(session); + if (sessions.length > maxSessions) { + sessions.length = maxSessions; + } + render(); + }; + + return { + ocrHooks: { + startSession, + addStage, + commitSession + }, + clear, + render + }; +}; + +export { createDebugOverlayManager }; diff --git a/src/email/draft.js b/src/email/draft.js new file mode 100644 index 0000000..710bc2f --- /dev/null +++ b/src/email/draft.js @@ -0,0 +1,129 @@ +const DEFAULT_SUBJECT = 'Lettura acqua da F9C397'; +const DEFAULT_CUSTOMER_DETAILS = { + name: 'Andrea Panizza', + address: 'Via delle cinque giornate 15 piano 1 e 1/2', + city: 'Firenze FI', + code: 'F9C397' +}; + +const formatItalianDate = (dateValue) => { + if (!dateValue) { + return ''; + } + const [year, month, day] = dateValue.split('-').map(Number); + if (!year || !month || !day) { + return ''; + } + const localDate = new Date(year, month - 1, day); + return new Intl.DateTimeFormat('it-IT').format(localDate); +}; + +const buildBodyTemplate = (customerDetails, dateDisplay, readingValue) => { + const safeReading = readingValue || '____'; + return [ + `Intestatario: ${customerDetails.name}`, + customerDetails.address, + customerDetails.city, + `Codice Utente: ${customerDetails.code}`, + `Data: ${dateDisplay}`, + `Lettura: ${safeReading}` + ].join('\n'); +}; + +const replaceLine = (body, label, value) => { + if (!body.trim()) { + return ''; + } + const lines = body.split('\n'); + const index = lines.findIndex((line) => line.trim().startsWith(label)); + const nextLine = `${label} ${value}`; + if (index >= 0) { + lines[index] = nextLine; + } else { + lines.push(nextLine); + } + return lines.join('\n'); +}; + +const createEmailDraftController = ({ + subjectInput, + bodyInput, + toInput, + mailtoLink, + sendBtn, + readingInput, + dateInput, + defaultSubject = DEFAULT_SUBJECT, + customerDetails = DEFAULT_CUSTOMER_DETAILS +}) => { + let bodyTouched = false; + let subjectTouched = false; + + const buildEmailDraft = () => { + return { + to: toInput.value.trim(), + subject: subjectInput.value.trim() || defaultSubject, + body: bodyInput.value.trim() + }; + }; + + const updateMailLinks = () => { + const { to, subject, body } = buildEmailDraft(); + const gmailUrl = `https://mail.google.com/mail/?view=cm&fs=1&to=${encodeURIComponent(to)}&su=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`; + sendBtn.dataset.gmailUrl = gmailUrl; + mailtoLink.href = `mailto:${encodeURIComponent(to)}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`; + }; + + const updateSubject = ({ force = false } = {}) => { + if (force || !subjectTouched) { + subjectInput.value = defaultSubject; + subjectTouched = false; + } + updateMailLinks(); + }; + + const updateBody = ({ force = false } = {}) => { + const dateDisplay = formatItalianDate(dateInput.value); + const readingValue = readingInput.value.trim(); + + if (force || !bodyTouched) { + bodyInput.value = buildBodyTemplate(customerDetails, dateDisplay, readingValue); + bodyTouched = false; + } else { + let updated = bodyInput.value; + updated = replaceLine(updated, 'Data:', dateDisplay); + updated = replaceLine(updated, 'Lettura:', readingValue || '____'); + bodyInput.value = updated || buildBodyTemplate(customerDetails, dateDisplay, readingValue); + } + + updateMailLinks(); + }; + + const markSubjectTouched = () => { + subjectTouched = true; + }; + + const markBodyTouched = () => { + bodyTouched = true; + }; + + const resetTouched = () => { + subjectTouched = false; + bodyTouched = false; + }; + + return { + updateMailLinks, + updateSubject, + updateBody, + markSubjectTouched, + markBodyTouched, + resetTouched + }; +}; + +export { + DEFAULT_SUBJECT, + DEFAULT_CUSTOMER_DETAILS, + createEmailDraftController +}; diff --git a/src/main.js b/src/main.js new file mode 100644 index 0000000..72efb48 --- /dev/null +++ b/src/main.js @@ -0,0 +1,214 @@ +import { runMeterOcr, setOcrDebugHooks } from './ocr/pipeline.js'; +import { createDebugOverlayManager } from './debug/overlay.js'; +import { createTestSetRunner } from './testset/run-test-set.js'; +import { + DEFAULT_SUBJECT, + DEFAULT_CUSTOMER_DETAILS, + createEmailDraftController +} from './email/draft.js'; + +const photoInput = document.getElementById('photo-input'); +const photoPreview = document.getElementById('photo-preview'); +const readBtn = document.getElementById('read-btn'); +const ocrStatus = document.getElementById('ocr-status'); +const readingInput = document.getElementById('reading-input'); +const dateInput = document.getElementById('date-input'); +const fromInput = document.getElementById('from-input'); +const toInput = document.getElementById('to-input'); +const subjectInput = document.getElementById('subject-input'); +const bodyInput = document.getElementById('body-input'); +const regenBtn = document.getElementById('regen-btn'); +const sendBtn = document.getElementById('send-btn'); +const mailtoLink = document.getElementById('mailto-link'); +const runTestBtn = document.getElementById('run-test-btn'); +const testStatus = document.getElementById('test-status'); +const testResults = document.getElementById('test-results'); +const debugOverlayToggle = document.getElementById('debug-overlay-toggle'); +const clearDebugBtn = document.getElementById('clear-debug-btn'); +const debugResults = document.getElementById('debug-results'); + +const DEBUG_CONFIG = { + maxSessions: 14, + previewWidth: 260 +}; + +let currentPhotoFile = null; + +const debugOverlayManager = createDebugOverlayManager({ + toggleEl: debugOverlayToggle, + resultsEl: debugResults, + maxSessions: DEBUG_CONFIG.maxSessions, + previewWidth: DEBUG_CONFIG.previewWidth +}); + +setOcrDebugHooks(debugOverlayManager.ocrHooks); + +const emailDraftController = createEmailDraftController({ + subjectInput, + bodyInput, + toInput, + mailtoLink, + sendBtn, + readingInput, + dateInput, + defaultSubject: DEFAULT_SUBJECT, + customerDetails: DEFAULT_CUSTOMER_DETAILS +}); + +const testSetRunner = createTestSetRunner({ + runButton: runTestBtn, + statusEl: testStatus, + resultsEl: testResults, + runMeterOcr +}); + +const setStatus = (message) => { + ocrStatus.textContent = message; +}; + +photoInput.addEventListener('click', () => { + photoInput.value = ''; +}); + +photoInput.addEventListener('change', () => { + const file = photoInput.files && photoInput.files[0]; + if (!file) { + currentPhotoFile = null; + photoPreview.innerHTML = '

No photo loaded yet.

'; + setStatus('Waiting for a photo.'); + return; + } + + currentPhotoFile = file; + const reader = new FileReader(); + reader.onload = () => { + photoPreview.innerHTML = ''; + const img = document.createElement('img'); + img.src = reader.result; + img.alt = 'Water meter preview'; + photoPreview.appendChild(img); + }; + reader.onerror = () => { + photoPreview.innerHTML = '

Preview unavailable.

'; + }; + reader.readAsDataURL(file); + setStatus('Photo ready. Click "Read meter".'); +}); + +readBtn.addEventListener('click', async () => { + if (!currentPhotoFile) { + setStatus('Upload a photo first.'); + return; + } + + if (!window.Tesseract) { + setStatus('OCR library not available. Check your connection and try again.'); + return; + } + + readBtn.disabled = true; + setStatus('Reading image...'); + + try { + const result = await runMeterOcr(currentPhotoFile, (message) => setStatus(message)); + if (result && result.value) { + readingInput.value = result.value; + setStatus(`Reading detected: ${result.value}. Review if needed.`); + emailDraftController.updateBody(); + } else { + setStatus('No clear reading detected. Enter it manually.'); + } + } catch (error) { + console.error(error); + const message = error instanceof Error && error.message + ? error.message + : 'Neural ROI failed. Enter the reading manually.'; + setStatus(message); + } finally { + readBtn.disabled = false; + } +}); + +readingInput.addEventListener('input', () => { + const sanitized = readingInput.value.replace(/\D/g, ''); + if (sanitized !== readingInput.value) { + readingInput.value = sanitized; + } + emailDraftController.updateBody(); +}); + +readingInput.addEventListener('blur', () => { + emailDraftController.updateBody(); +}); + +dateInput.addEventListener('change', () => { + emailDraftController.updateBody(); +}); + +toInput.addEventListener('input', () => { + emailDraftController.updateMailLinks(); +}); + +subjectInput.addEventListener('input', () => { + emailDraftController.markSubjectTouched(); + emailDraftController.updateMailLinks(); +}); + +bodyInput.addEventListener('input', () => { + emailDraftController.markBodyTouched(); + emailDraftController.updateMailLinks(); +}); + +if (runTestBtn) { + runTestBtn.addEventListener('click', () => { + testSetRunner.runTestSet(); + }); +} + +if (clearDebugBtn) { + clearDebugBtn.addEventListener('click', () => { + debugOverlayManager.clear(); + }); +} + +if (debugOverlayToggle) { + debugOverlayToggle.addEventListener('change', () => { + if (!debugOverlayToggle.checked) { + debugOverlayManager.clear(); + return; + } + debugOverlayManager.render(); + }); +} + +regenBtn.addEventListener('click', () => { + emailDraftController.resetTouched(); + emailDraftController.updateSubject({ force: true }); + emailDraftController.updateBody({ force: true }); +}); + +sendBtn.addEventListener('click', () => { + if (!sendBtn.dataset.gmailUrl) { + emailDraftController.updateMailLinks(); + } + window.open(sendBtn.dataset.gmailUrl, '_blank', 'noopener'); +}); + +const init = () => { + const now = new Date(); + const isoDate = [ + now.getFullYear(), + String(now.getMonth() + 1).padStart(2, '0'), + String(now.getDate()).padStart(2, '0') + ].join('-'); + + dateInput.value = isoDate; + fromInput.value = fromInput.value || 'andrea.panizza75@gmail.com'; + + emailDraftController.updateSubject({ force: true }); + emailDraftController.updateBody({ force: true }); + emailDraftController.updateMailLinks(); + debugOverlayManager.render(); +}; + +init(); diff --git a/src/ocr/alignment.js b/src/ocr/alignment.js new file mode 100644 index 0000000..3d6687a --- /dev/null +++ b/src/ocr/alignment.js @@ -0,0 +1,76 @@ +import { OCR_CONFIG } from './config.js'; +import { + rotateCanvas, + scaleCanvas, + cropCanvas, + findDigitWindowByEdges, + preprocessCanvas +} from './canvas-utils.js'; + +const buildNeuralRoiCandidates = (source, debugSession, addDebugStageFn = () => {}) => { + const roiDeterministic = OCR_CONFIG.roiDeterministic || {}; + const normalizeWidth = Number.isFinite(roiDeterministic.normalizeWidth) + ? roiDeterministic.normalizeWidth + : OCR_CONFIG.minScaleWidth; + const useEdgeCandidates = roiDeterministic.useEdgeCandidates !== false; + const debugWordMode = Array.isArray(roiDeterministic.wordPassModes) && roiDeterministic.wordPassModes.length + ? roiDeterministic.wordPassModes.find((mode) => mode === 'soft' || mode === 'binary' || mode === 'raw') || 'raw' + : 'raw'; + + const angles = source.height > source.width + ? [90, 270, 0, 180] + : [0, 180, 90, 270]; + const candidates = []; + let debugStripSource = null; + + const pushCandidate = (canvas, label) => { + if (!canvas) { + return; + } + const normalized = scaleCanvas(canvas, normalizeWidth); + if (!normalized || normalized.width < 24 || normalized.height < 16) { + return; + } + candidates.push({ canvas: normalized, label }); + if (!debugStripSource) { + debugStripSource = canvas; + } + }; + + angles.forEach((angle) => { + const rotated = angle === 0 ? source : rotateCanvas(source, angle); + + if (useEdgeCandidates) { + const edgeRect = findDigitWindowByEdges(rotated); + if (edgeRect) { + const edgeCrop = cropCanvas(rotated, edgeRect); + pushCandidate(edgeCrop, `roi-${angle}-edge`); + } + } + + pushCandidate(rotated, `roi-${angle}-base`); + }); + + if (!candidates.length) { + const fallback = scaleCanvas(source, normalizeWidth); + candidates.push({ canvas: fallback, label: 'roi-base-fallback' }); + debugStripSource = source; + } + + if (debugSession) { + const stripPreview = preprocessCanvas(debugStripSource || source, 'soft'); + addDebugStageFn(debugSession, '5. detected strip crop', stripPreview); + const ocrPreview = debugWordMode === 'raw' + ? candidates[0].canvas + : preprocessCanvas(candidates[0].canvas, debugWordMode); + addDebugStageFn(debugSession, '6. OCR input candidate', ocrPreview); + } + + return candidates; +}; + +const buildDigitCandidates = (source, debugSession = null, addDebugStageFn = () => {}) => { + return buildNeuralRoiCandidates(source, debugSession, addDebugStageFn); +}; + +export { buildDigitCandidates }; diff --git a/src/ocr/canvas-utils.js b/src/ocr/canvas-utils.js new file mode 100644 index 0000000..70ee372 --- /dev/null +++ b/src/ocr/canvas-utils.js @@ -0,0 +1,478 @@ +import { DEBUG_CONFIG } from './config.js'; + +const clamp = (value, min, max) => Math.min(max, Math.max(min, value)); + +const normalizeAngle = (angle) => { + const value = Number(angle); + if (!Number.isFinite(value)) { + return Number.NaN; + } + return ((value % 360) + 360) % 360; +}; + +const cloneCanvas = (source) => { + const canvas = document.createElement('canvas'); + canvas.width = source.width; + canvas.height = source.height; + const ctx = canvas.getContext('2d'); + ctx.drawImage(source, 0, 0); + return canvas; +}; + +const getRectFromNormalized = (canvas, rect) => ({ + x: canvas.width * rect.x, + y: canvas.height * rect.y, + width: canvas.width * rect.width, + height: canvas.height * rect.height +}); + +const drawOverlayCanvas = (source, shapes = []) => { + const canvas = cloneCanvas(source); + const ctx = canvas.getContext('2d'); + const strokeWidth = Math.max(2, Math.round(canvas.width / 180)); + const fontSize = Math.max(10, Math.round(canvas.width / 28)); + ctx.lineWidth = strokeWidth; + ctx.font = `600 ${fontSize}px Manrope, sans-serif`; + ctx.textBaseline = 'top'; + + shapes.forEach((shape, index) => { + const color = shape.color || DEBUG_CONFIG.colors[index % DEBUG_CONFIG.colors.length]; + ctx.strokeStyle = color; + ctx.fillStyle = color; + if (shape.type === 'circle') { + ctx.beginPath(); + ctx.arc(shape.cx, shape.cy, shape.radius, 0, Math.PI * 2); + ctx.stroke(); + } else { + ctx.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + if (shape.label) { + const paddingX = 6; + const paddingY = 4; + const labelWidth = Math.ceil(ctx.measureText(shape.label).width) + paddingX * 2; + const labelHeight = fontSize + paddingY * 2; + const baseX = shape.type === 'circle' ? shape.cx - shape.radius : shape.x; + const baseY = shape.type === 'circle' ? shape.cy - shape.radius : shape.y; + const labelX = clamp(baseX, 0, Math.max(0, canvas.width - labelWidth)); + const labelY = clamp(baseY - labelHeight - 2, 0, Math.max(0, canvas.height - labelHeight)); + ctx.fillStyle = 'rgba(15, 23, 42, 0.75)'; + ctx.fillRect(labelX, labelY, labelWidth, labelHeight); + ctx.fillStyle = '#f8fafc'; + ctx.fillText(shape.label, labelX + paddingX, labelY + paddingY); + ctx.fillStyle = color; + } + }); + + return canvas; +}; +const loadImageBitmap = async (file) => { + if ('createImageBitmap' in window) { + try { + return await createImageBitmap(file, { imageOrientation: 'from-image' }); + } catch (error) { + console.warn('createImageBitmap failed, falling back to Image.', error); + } + } + + return new Promise((resolve, reject) => { + const img = new Image(); + const objectUrl = URL.createObjectURL(file); + img.onload = () => { + URL.revokeObjectURL(objectUrl); + resolve(img); + }; + img.onerror = (error) => { + URL.revokeObjectURL(objectUrl); + reject(error); + }; + img.src = objectUrl; + }); +}; + +const drawImageToCanvas = (image, maxDimension) => { + const canvas = document.createElement('canvas'); + const maxSide = Math.max(image.width, image.height); + const scale = maxSide > maxDimension ? maxDimension / maxSide : 1; + canvas.width = Math.round(image.width * scale); + canvas.height = Math.round(image.height * scale); + const ctx = canvas.getContext('2d', { willReadFrequently: true }); + ctx.imageSmoothingEnabled = true; + ctx.drawImage(image, 0, 0, canvas.width, canvas.height); + return canvas; +}; + +const cropCanvas = (source, rect) => { + const safeX = clamp(rect.x, 0, source.width - 1); + const safeY = clamp(rect.y, 0, source.height - 1); + const safeRect = { + x: safeX, + y: safeY, + width: clamp(rect.width, 1, source.width - safeX), + height: clamp(rect.height, 1, source.height - safeY) + }; + const canvas = document.createElement('canvas'); + canvas.width = Math.round(safeRect.width); + canvas.height = Math.round(safeRect.height); + const ctx = canvas.getContext('2d', { willReadFrequently: true }); + ctx.imageSmoothingEnabled = true; + ctx.drawImage( + source, + safeRect.x, + safeRect.y, + safeRect.width, + safeRect.height, + 0, + 0, + canvas.width, + canvas.height + ); + return canvas; +}; + +const cropCenterSquare = (source, scale) => { + const size = Math.round(Math.min(source.width, source.height) * scale); + const x = Math.round((source.width - size) / 2); + const y = Math.round((source.height - size) / 2); + return cropCanvas(source, { x, y, width: size, height: size }); +}; + +const rotateCanvas = (source, angle) => { + const normalized = ((angle % 360) + 360) % 360; + if (normalized === 0) { + return source; + } + const canvas = document.createElement('canvas'); + const swap = normalized === 90 || normalized === 270; + canvas.width = swap ? source.height : source.width; + canvas.height = swap ? source.width : source.height; + const ctx = canvas.getContext('2d'); + ctx.translate(canvas.width / 2, canvas.height / 2); + ctx.rotate((normalized * Math.PI) / 180); + ctx.drawImage(source, -source.width / 2, -source.height / 2); + return canvas; +}; + +const scaleCanvas = (source, targetWidth) => { + if (source.width >= targetWidth) { + return source; + } + const scale = targetWidth / source.width; + const canvas = document.createElement('canvas'); + canvas.width = Math.round(source.width * scale); + canvas.height = Math.round(source.height * scale); + const ctx = canvas.getContext('2d'); + ctx.imageSmoothingEnabled = true; + ctx.drawImage(source, 0, 0, canvas.width, canvas.height); + return canvas; +}; + +const splitIntoCells = (source, count, overlapRatio) => { + const cells = []; + const cellWidth = source.width / count; + const overlap = cellWidth * overlapRatio; + for (let i = 0; i < count; i += 1) { + const x = cellWidth * i - overlap; + const width = cellWidth + overlap * 2; + cells.push(cropCanvas(source, { x, y: 0, width, height: source.height })); + } + return cells; +}; + +const computeOtsuThreshold = (data, contrast, brightness) => { + const histogram = new Array(256).fill(0); + for (let i = 0; i < data.length; i += 4) { + const lum = data[i] * 0.2126 + data[i + 1] * 0.7152 + data[i + 2] * 0.0722; + const adjusted = clamp((lum - 128) * contrast + 128 + brightness, 0, 255); + histogram[adjusted | 0] += 1; + } + + const total = data.length / 4; + let sum = 0; + for (let i = 0; i < 256; i += 1) { + sum += i * histogram[i]; + } + + let sumB = 0; + let weightB = 0; + let weightF = 0; + let maxVariance = 0; + let threshold = 128; + + for (let i = 0; i < 256; i += 1) { + weightB += histogram[i]; + if (!weightB) { + continue; + } + weightF = total - weightB; + if (!weightF) { + break; + } + sumB += i * histogram[i]; + const meanB = sumB / weightB; + const meanF = (sum - sumB) / weightF; + const variance = weightB * weightF * (meanB - meanF) ** 2; + if (variance > maxVariance) { + maxVariance = variance; + threshold = i; + } + } + + return threshold; +}; + +const preprocessCanvas = (source, mode) => { + const contrast = mode === 'binary' ? 1.6 : 1.25; + const brightness = mode === 'binary' ? 6 : 0; + const canvas = document.createElement('canvas'); + canvas.width = source.width; + canvas.height = source.height; + const ctx = canvas.getContext('2d'); + ctx.drawImage(source, 0, 0); + const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); + const data = imageData.data; + const threshold = mode === 'binary' ? computeOtsuThreshold(data, contrast, brightness) : null; + + for (let i = 0; i < data.length; i += 4) { + const lum = data[i] * 0.2126 + data[i + 1] * 0.7152 + data[i + 2] * 0.0722; + let adjusted = clamp((lum - 128) * contrast + 128 + brightness, 0, 255); + if (mode === 'binary') { + adjusted = adjusted > threshold ? 255 : 0; + } + data[i] = adjusted; + data[i + 1] = adjusted; + data[i + 2] = adjusted; + data[i + 3] = 255; + } + + ctx.putImageData(imageData, 0, 0); + return canvas; +}; + +const tightenCropByInk = (source, minAreaRatio = 0.15) => { + const ctx = source.getContext('2d', { willReadFrequently: true }); + const { width, height } = source; + const data = ctx.getImageData(0, 0, width, height).data; + const cols = new Array(width).fill(0); + const rows = new Array(height).fill(0); + + for (let y = 0; y < height; y += 1) { + for (let x = 0; x < width; x += 1) { + const idx = (y * width + x) * 4; + const lum = data[idx] * 0.2126 + data[idx + 1] * 0.7152 + data[idx + 2] * 0.0722; + const dark = 255 - lum; + cols[x] += dark; + rows[y] += dark; + } + } + + const sumCols = cols.reduce((acc, value) => acc + value, 0); + const sumRows = rows.reduce((acc, value) => acc + value, 0); + const meanCols = sumCols / width; + const meanRows = sumRows / height; + const maxCols = Math.max(...cols); + const maxRows = Math.max(...rows); + const colThreshold = meanCols + (maxCols - meanCols) * 0.25; + const rowThreshold = meanRows + (maxRows - meanRows) * 0.25; + + let left = cols.findIndex((value) => value > colThreshold); + let right = cols.length - 1 - [...cols].reverse().findIndex((value) => value > colThreshold); + let top = rows.findIndex((value) => value > rowThreshold); + let bottom = rows.length - 1 - [...rows].reverse().findIndex((value) => value > rowThreshold); + + if (left < 0 || right <= left || top < 0 || bottom <= top) { + return source; + } + + const paddingX = Math.round((right - left) * 0.08); + const paddingY = Math.round((bottom - top) * 0.15); + left = clamp(left - paddingX, 0, width - 1); + right = clamp(right + paddingX, 1, width); + top = clamp(top - paddingY, 0, height - 1); + bottom = clamp(bottom + paddingY, 1, height); + + const cropWidth = right - left; + const cropHeight = bottom - top; + const areaRatio = (cropWidth * cropHeight) / (width * height); + if (areaRatio < minAreaRatio || areaRatio > 0.95) { + return source; + } + + return cropCanvas(source, { x: left, y: top, width: cropWidth, height: cropHeight }); +}; + +const findDigitWindowByEdges = (source) => { + const ctx = source.getContext('2d', { willReadFrequently: true }); + const { width, height } = source; + const data = ctx.getImageData(0, 0, width, height).data; + const cols = new Array(width).fill(0); + const rows = new Array(height).fill(0); + const maxRow = Math.floor(height * 0.65); + + const lumAt = (x, y) => { + const idx = (y * width + x) * 4; + return data[idx] * 0.2126 + data[idx + 1] * 0.7152 + data[idx + 2] * 0.0722; + }; + + for (let y = 1; y < maxRow - 1; y += 1) { + for (let x = 1; x < width - 1; x += 1) { + const edge = Math.abs(lumAt(x + 1, y) - lumAt(x - 1, y)); + cols[x] += edge; + rows[y] += edge; + } + } + + const meanCols = cols.reduce((acc, value) => acc + value, 0) / width; + const meanRows = rows.reduce((acc, value) => acc + value, 0) / height; + const maxCols = Math.max(...cols); + const maxRows = Math.max(...rows); + const colThreshold = meanCols + (maxCols - meanCols) * 0.35; + const rowThreshold = meanRows + (maxRows - meanRows) * 0.35; + + let left = cols.findIndex((value) => value > colThreshold); + let right = cols.length - 1 - [...cols].reverse().findIndex((value) => value > colThreshold); + let top = rows.findIndex((value) => value > rowThreshold); + let bottom = rows.length - 1 - [...rows].reverse().findIndex((value) => value > rowThreshold); + + if (left < 0 || right <= left || top < 0 || bottom <= top) { + return null; + } + + const paddingX = Math.round((right - left) * 0.08); + const paddingY = Math.round((bottom - top) * 0.2); + left = clamp(left - paddingX, 0, width - 1); + right = clamp(right + paddingX, 1, width); + top = clamp(top - paddingY, 0, height - 1); + bottom = clamp(bottom + paddingY, 1, height); + + const cropWidth = right - left; + const cropHeight = bottom - top; + const areaRatio = (cropWidth * cropHeight) / (width * height); + if (areaRatio < 0.08 || areaRatio > 0.7) { + return null; + } + + return { x: left, y: top, width: cropWidth, height: cropHeight }; +}; + +const hasInk = (canvas) => { + const ctx = canvas.getContext('2d', { willReadFrequently: true }); + const { width, height } = canvas; + const data = ctx.getImageData(0, 0, width, height).data; + let dark = 0; + const total = width * height; + for (let i = 0; i < data.length; i += 4) { + const lum = data[i] * 0.2126 + data[i + 1] * 0.7152 + data[i + 2] * 0.0722; + if (lum < 140) { + dark += 1; + } + } + const ratio = dark / total; + return ratio > 0.01 && ratio < 0.4; +}; + +const getLuminanceAt = (data, width, height, x, y) => { + const clampedX = clamp(Math.round(x), 0, width - 1); + const clampedY = clamp(Math.round(y), 0, height - 1); + const idx = (clampedY * width + clampedX) * 4; + return data[idx] * 0.2126 + data[idx + 1] * 0.7152 + data[idx + 2] * 0.0722; +}; + +const analyzeRegion = (data, width, height, rect) => { + const x0 = clamp(Math.floor(rect.x), 0, width - 1); + const y0 = clamp(Math.floor(rect.y), 0, height - 1); + const x1 = clamp(Math.ceil(rect.x + rect.width), x0 + 1, width); + const y1 = clamp(Math.ceil(rect.y + rect.height), y0 + 1, height); + let samples = 0; + let dark = 0; + let red = 0; + let edgeX = 0; + let edgeY = 0; + let lumTotal = 0; + let prevRow = null; + + for (let y = y0; y < y1; y += 2) { + let prevLum = null; + const row = []; + for (let x = x0; x < x1; x += 2) { + const idx = (y * width + x) * 4; + const r = data[idx]; + const g = data[idx + 1]; + const b = data[idx + 2]; + const lum = r * 0.2126 + g * 0.7152 + b * 0.0722; + lumTotal += lum; + if (lum < 130) { + dark += 1; + } + if (r > 80 && r > g * 1.15 && r > b * 1.15) { + red += 1; + } + if (prevLum !== null) { + edgeX += Math.abs(lum - prevLum); + } + prevLum = lum; + row.push(lum); + samples += 1; + } + if (prevRow) { + for (let i = 0; i < row.length; i += 1) { + edgeY += Math.abs(row[i] - prevRow[i]); + } + } + prevRow = row; + } + + const safeSamples = Math.max(1, samples); + return { + darkRatio: dark / safeSamples, + redRatio: red / safeSamples, + edgeXRatio: edgeX / (safeSamples * 255), + edgeYRatio: edgeY / (safeSamples * 255), + meanLum: lumTotal / safeSamples + }; +}; + +const normalizeRectToCanvas = (canvas, rect) => { + const x = clamp(rect.x, 0, canvas.width - 1); + const y = clamp(rect.y, 0, canvas.height - 1); + const width = clamp(rect.width, 1, canvas.width - x); + const height = clamp(rect.height, 1, canvas.height - y); + return { x, y, width, height }; +}; + +const adjustRectAroundCenter = (canvas, baseRect, settings) => { + const centerX = baseRect.x + baseRect.width * 0.5; + const centerY = baseRect.y + baseRect.height * 0.5; + const width = baseRect.width * settings.scaleX; + const height = baseRect.height * settings.scaleY; + const shiftedCenterX = centerX + baseRect.width * settings.shiftX; + const shiftedCenterY = centerY + baseRect.height * settings.shiftY; + return normalizeRectToCanvas(canvas, { + x: shiftedCenterX - width * 0.5, + y: shiftedCenterY - height * 0.5, + width, + height + }); +}; + +export { + clamp, + normalizeAngle, + cloneCanvas, + getRectFromNormalized, + drawOverlayCanvas, + loadImageBitmap, + drawImageToCanvas, + cropCanvas, + cropCenterSquare, + rotateCanvas, + scaleCanvas, + splitIntoCells, + preprocessCanvas, + tightenCropByInk, + findDigitWindowByEdges, + hasInk, + getLuminanceAt, + analyzeRegion, + normalizeRectToCanvas, + adjustRectAroundCenter +}; diff --git a/src/ocr/config.js b/src/ocr/config.js new file mode 100644 index 0000000..f0a0c01 --- /dev/null +++ b/src/ocr/config.js @@ -0,0 +1,140 @@ +const OCR_CONFIG = { + maxDimension: 1400, + meterCropScale: 0.95, + digitCrops: [ + { name: 'top-wide', x: 0.04, y: 0.02, width: 0.92, height: 0.32 }, + { name: 'top-left', x: 0.0, y: 0.05, width: 0.7, height: 0.4 }, + { name: 'left-tall', x: 0.0, y: 0.12, width: 0.55, height: 0.58 }, + { name: 'upper-band', x: 0.1, y: 0.08, width: 0.8, height: 0.28 } + ], + preferredDigits: 4, + digitCellCount: 4, + digitCellOverlap: 0.08, + minDigitWidth: 96, + minDigits: 3, + earlyStopScore: 0.84, + fallbackScoreThreshold: 0.72, + fallbackCandidates: 6, + minScaleWidth: 480, + geometry: { + minCandidateWidth: 120, + minCandidateHeight: 28, + minCandidateAspect: 0.12, + maxCandidateAspect: 18, + minStripAspect: 1.15, + maxStripAspect: 12, + minCellWidth: 20, + minCellHeight: 24 + }, + roiDeterministic: { + tightenInk: 0.08, + cellOverlap: 0.03, + requireAllCells: true, + wordPassModes: ['raw'], + minWordPassHits: 1, + minStripAspect: 1.45, + maxStripAspect: 8.2, + normalizeWidth: 520, + deskewMaxAngle: 8, + deskewStep: 2, + useEdgeCandidates: true + }, + digitClassifier: { + enabled: false, + endpoint: 'http://127.0.0.1:8001/digit/predict-cells', + timeoutMs: 1800, + minCellConfidence: 0.28, + fallbackPreferNonEdge: true, + fallbackTargetAspect: 2.6, + singleCellRefine: true, + singleCellLowConfidence: 0.56, + singleCellRefineMinConfidence: 42, + singleCellRefineSwitchMargin: 4, + fallbackOnNoDigitsOnly: true, + fallbackMinAcceptedCells: 4, + fallbackEdgeMinAverageConfidence: 65, + fallbackEdgeMinCellConfidence: 35, + fallbackEdgeRequireNonEdgeSupport: false, + disableAfterFailures: 2, + cooldownMs: 8000 + }, + neuralRoi: { + enabled: true, + endpoint: 'http://127.0.0.1:8001/roi/detect', + timeoutMs: 8000, + minConfidence: 0.01, + expandX: 0.26, + expandY: 0.16, + sanity: { + minCenterX: 0.28, + maxCenterX: 0.62, + minCenterY: 0.28, + maxCenterY: 0.68, + minArea: 0.003, + maxArea: 0.03, + minAspect: 0.35, + maxAspect: 3.2 + } + } +}; + +const applyRuntimeOverrides = () => { + if (typeof window === 'undefined') { + return; + } + const overrides = window.__JARVIS_OCR_CONFIG_OVERRIDE__; + if (!overrides || typeof overrides !== 'object') { + return; + } + if (overrides.digitClassifier && typeof overrides.digitClassifier === 'object') { + OCR_CONFIG.digitClassifier = { + ...OCR_CONFIG.digitClassifier, + ...overrides.digitClassifier + }; + } +}; + +applyRuntimeOverrides(); + +const ALIGNMENT_CONFIG = { + centerOffsetLimit: 0.14, + centerOffsetStep: 0.05, + radiusMinRatio: 0.25, + radiusMaxRatio: 0.42, + radiusStepRatio: 0.02, + facePadding: 1.04, + stripSearchBand: { + offsetXFromCenterRadius: -0.36, + offsetYFromCenterRadius: -0.44, + widthFromRadius: 1.26, + heightFromRadius: 0.82 + }, + stripGates: { + maxRedRatio: 0.12, + minAspect: 1.35, + maxAspect: 4.4, + minDistanceFromCenterRadius: 0.32, + maxDistanceFromCenterRadius: 0.92, + expectedDistanceFromCenterRadius: 0.6, + distanceTolerance: 0.4, + minVerticalPeriodicity: 0.2, + minAcceptedScore: 2.45 + }, + stripDebugTopK: 6, + stripWindows: [ + { name: 'strip-main', scaleX: 1.0, scaleY: 1.0, shiftX: 0, shiftY: 0 }, + { name: 'strip-tight', scaleX: 0.86, scaleY: 0.84, shiftX: 0.02, shiftY: 0.02 } + ], + fallbackWindows: [ + { name: 'fallback-main', x: 0.08, y: 0.14, width: 0.48, height: 0.24 }, + { name: 'fallback-tight', x: 0.1, y: 0.17, width: 0.44, height: 0.2 } + ] +}; + +const DEBUG_CONFIG = { + maxSessions: 14, + previewWidth: 260, + colors: ['#ef4444', '#2563eb', '#16a34a', '#f59e0b', '#ec4899'] +}; + +export { OCR_CONFIG, ALIGNMENT_CONFIG, DEBUG_CONFIG }; diff --git a/src/ocr/debug-hooks.js b/src/ocr/debug-hooks.js new file mode 100644 index 0000000..06ce710 --- /dev/null +++ b/src/ocr/debug-hooks.js @@ -0,0 +1,28 @@ +let debugHooks = null; + +const setOcrDebugHooks = (hooks) => { + debugHooks = hooks || null; +}; + +const startDebugSession = (label) => { + if (!debugHooks || typeof debugHooks.startSession !== 'function') { + return null; + } + return debugHooks.startSession(label); +}; + +const addDebugStage = (session, name, canvas) => { + if (!debugHooks || typeof debugHooks.addStage !== 'function') { + return; + } + debugHooks.addStage(session, name, canvas); +}; + +const commitDebugSession = (session) => { + if (!debugHooks || typeof debugHooks.commitSession !== 'function') { + return; + } + debugHooks.commitSession(session); +}; + +export { setOcrDebugHooks, startDebugSession, addDebugStage, commitDebugSession }; diff --git a/src/ocr/digit-classifier.js b/src/ocr/digit-classifier.js new file mode 100644 index 0000000..95a15cf --- /dev/null +++ b/src/ocr/digit-classifier.js @@ -0,0 +1,152 @@ +const toFiniteNumber = (value) => { + const parsed = Number(value); + return Number.isFinite(parsed) ? parsed : null; +}; + +const createProbeMiss = (reason, extra = {}) => ({ + ok: false, + reason, + ...extra +}); + +const runtimeState = { + consecutiveFailures: 0, + disabledUntilTs: 0 +}; + +const canvasToBlob = (canvas) => { + return new Promise((resolve, reject) => { + if (!canvas || typeof canvas.toBlob !== 'function') { + reject(new Error('canvas-to-blob-unavailable')); + return; + } + canvas.toBlob((blob) => { + if (!blob) { + reject(new Error('canvas-to-blob-failed')); + return; + } + resolve(blob); + }, 'image/png'); + }); +}; + +const normalizeDigit = (value) => { + const raw = value === undefined || value === null ? '' : String(value); + return /^\d$/.test(raw) ? raw : ''; +}; + +const setFailureCooldown = (config) => { + const maxFailures = Number.isFinite(config.disableAfterFailures) ? config.disableAfterFailures : 2; + const cooldownMs = Number.isFinite(config.cooldownMs) ? config.cooldownMs : 8000; + runtimeState.consecutiveFailures += 1; + if (runtimeState.consecutiveFailures >= maxFailures) { + runtimeState.disabledUntilTs = Date.now() + cooldownMs; + } +}; + +const clearFailureState = () => { + runtimeState.consecutiveFailures = 0; + runtimeState.disabledUntilTs = 0; +}; + +const predictDigitCells = async (cellCanvases, classifierConfig) => { + if (!classifierConfig || !classifierConfig.enabled || !classifierConfig.endpoint) { + return createProbeMiss('disabled'); + } + if (!Array.isArray(cellCanvases) || !cellCanvases.length) { + return createProbeMiss('missing-cells'); + } + if (Date.now() < runtimeState.disabledUntilTs) { + return createProbeMiss('cooldown'); + } + if (typeof fetch !== 'function' || typeof FormData === 'undefined') { + return createProbeMiss('unsupported-environment'); + } + + const timeoutMs = Number.isFinite(classifierConfig.timeoutMs) ? classifierConfig.timeoutMs : 1800; + const minCellConfidence = Number.isFinite(classifierConfig.minCellConfidence) + ? classifierConfig.minCellConfidence + : 0; + const abortController = typeof AbortController !== 'undefined' ? new AbortController() : null; + const timeoutId = abortController ? setTimeout(() => abortController.abort(), timeoutMs) : null; + + try { + const formData = new FormData(); + for (let index = 0; index < cellCanvases.length; index += 1) { + const blob = await canvasToBlob(cellCanvases[index]); + formData.append('images', blob, `cell_${index}.png`); + } + + const response = await fetch(classifierConfig.endpoint, { + method: 'POST', + body: formData, + signal: abortController ? abortController.signal : undefined + }); + if (!response.ok) { + setFailureCooldown(classifierConfig); + return createProbeMiss('http-error', { status: response.status }); + } + + let payload = null; + try { + payload = await response.json(); + } catch (error) { + setFailureCooldown(classifierConfig); + return createProbeMiss('invalid-json'); + } + + if (!payload || !payload.ok || !Array.isArray(payload.predictions)) { + setFailureCooldown(classifierConfig); + return createProbeMiss('invalid-payload'); + } + + const predictions = []; + let acceptedCount = 0; + for (let index = 0; index < cellCanvases.length; index += 1) { + const item = payload.predictions[index] || {}; + const confidence = toFiniteNumber(item.confidence) ?? 0; + const candidateDigit = normalizeDigit(item.digit || item.predicted_digit); + const accepted = ( + item.accepted !== false + && !!candidateDigit + && confidence >= minCellConfidence + ); + if (accepted) { + acceptedCount += 1; + } + predictions.push({ + digit: accepted ? candidateDigit : '', + confidence, + accepted + }); + } + + if (!predictions.length) { + setFailureCooldown(classifierConfig); + return createProbeMiss('empty-predictions'); + } + + clearFailureState(); + return { + ok: true, + model: payload.model || null, + device: payload.device || null, + predictions, + acceptedCount, + minCellConfidence + }; + } catch (error) { + if (error && error.name === 'AbortError') { + setFailureCooldown(classifierConfig); + return createProbeMiss('timeout'); + } + setFailureCooldown(classifierConfig); + return createProbeMiss('network-error'); + } finally { + if (timeoutId) { + clearTimeout(timeoutId); + } + } +}; + +export { predictDigitCells }; diff --git a/src/ocr/face-detection.js b/src/ocr/face-detection.js new file mode 100644 index 0000000..118041f --- /dev/null +++ b/src/ocr/face-detection.js @@ -0,0 +1,108 @@ +import { ALIGNMENT_CONFIG } from './config.js'; +import { getLuminanceAt, analyzeRegion, getRectFromNormalized } from './canvas-utils.js'; + +const detectMeterFace = (meterCrop) => { + const ctx = meterCrop.getContext('2d', { willReadFrequently: true }); + const { width, height } = meterCrop; + const data = ctx.getImageData(0, 0, width, height).data; + const minDim = Math.min(width, height); + let best = null; + + for ( + let yOffset = -ALIGNMENT_CONFIG.centerOffsetLimit; + yOffset <= ALIGNMENT_CONFIG.centerOffsetLimit; + yOffset += ALIGNMENT_CONFIG.centerOffsetStep + ) { + for ( + let xOffset = -ALIGNMENT_CONFIG.centerOffsetLimit; + xOffset <= ALIGNMENT_CONFIG.centerOffsetLimit; + xOffset += ALIGNMENT_CONFIG.centerOffsetStep + ) { + const cx = width * (0.5 + xOffset); + const cy = height * (0.5 + yOffset); + for ( + let radiusRatio = ALIGNMENT_CONFIG.radiusMinRatio; + radiusRatio <= ALIGNMENT_CONFIG.radiusMaxRatio; + radiusRatio += ALIGNMENT_CONFIG.radiusStepRatio + ) { + const radius = minDim * radiusRatio; + const padded = radius * ALIGNMENT_CONFIG.facePadding; + if (cx - padded < 0 || cy - padded < 0 || cx + padded >= width || cy + padded >= height) { + continue; + } + + let insideLum = 0; + let ringLum = 0; + let outsideLum = 0; + const steps = 24; + for (let i = 0; i < steps; i += 1) { + const angle = (i / steps) * Math.PI * 2; + const cos = Math.cos(angle); + const sin = Math.sin(angle); + insideLum += getLuminanceAt(data, width, height, cx + cos * radius * 0.58, cy + sin * radius * 0.58); + ringLum += getLuminanceAt(data, width, height, cx + cos * radius * 1.0, cy + sin * radius * 1.0); + outsideLum += getLuminanceAt(data, width, height, cx + cos * radius * 1.26, cy + sin * radius * 1.26); + } + insideLum /= steps; + ringLum /= steps; + outsideLum /= steps; + const centerPenalty = (Math.abs(xOffset) + Math.abs(yOffset)) * 20; + const score = insideLum * 0.7 + (255 - ringLum) * 0.95 - outsideLum * 0.25 - centerPenalty; + + if (!best || score > best.score) { + best = { cx, cy, radius, score }; + } + } + } + } + + if (!best) { + return { + faceRect: { x: 0, y: 0, width, height }, + circle: { cx: width / 2, cy: height / 2, radius: minDim * 0.35 } + }; + } + + const paddedRadius = best.radius * ALIGNMENT_CONFIG.facePadding; + return { + faceRect: { + x: best.cx - paddedRadius, + y: best.cy - paddedRadius, + width: paddedRadius * 2, + height: paddedRadius * 2 + }, + circle: { + cx: best.cx, + cy: best.cy, + radius: best.radius + } + }; +}; + +const scoreCanonicalRotation = (faceCanvas) => { + const ctx = faceCanvas.getContext('2d', { willReadFrequently: true }); + const { width, height } = faceCanvas; + const data = ctx.getImageData(0, 0, width, height).data; + const blackWindow = analyzeRegion( + data, + width, + height, + getRectFromNormalized(faceCanvas, { x: 0.06, y: 0.11, width: 0.52, height: 0.34 }) + ); + const redDialArea = analyzeRegion( + data, + width, + height, + getRectFromNormalized(faceCanvas, { x: 0.45, y: 0.18, width: 0.5, height: 0.58 }) + ); + + return ( + blackWindow.darkRatio * 1.7 + + blackWindow.edgeXRatio * 2.4 + + redDialArea.redRatio * 1.8 + - blackWindow.redRatio * 2.5 + - redDialArea.darkRatio * 0.4 + ); +}; + +export { detectMeterFace, scoreCanonicalRotation }; diff --git a/src/ocr/neural-roi.js b/src/ocr/neural-roi.js new file mode 100644 index 0000000..46aa5ed --- /dev/null +++ b/src/ocr/neural-roi.js @@ -0,0 +1,171 @@ +import { clamp } from './canvas-utils.js'; + +const toFiniteNumber = (value) => { + const parsed = Number(value); + return Number.isFinite(parsed) ? parsed : null; +}; + +const normalizeRect = (rect) => { + if (!rect || typeof rect !== 'object') { + return null; + } + const x = toFiniteNumber(rect.x); + const y = toFiniteNumber(rect.y); + const width = toFiniteNumber(rect.width); + const height = toFiniteNumber(rect.height); + if (x === null || y === null || width === null || height === null) { + return null; + } + if (width <= 0 || height <= 0) { + return null; + } + const normalizedX = clamp(x, 0, 0.999); + const normalizedY = clamp(y, 0, 0.999); + const maxWidth = Math.max(0.001, 1 - normalizedX); + const maxHeight = Math.max(0.001, 1 - normalizedY); + const normalizedWidth = clamp(width, 0.001, maxWidth); + const normalizedHeight = clamp(height, 0.001, maxHeight); + return { + x: normalizedX, + y: normalizedY, + width: normalizedWidth, + height: normalizedHeight + }; +}; + +const createProbeMiss = (reason, extra = {}) => ({ + ok: false, + reason, + ...extra +}); + +const evaluateRectSanity = (rect, sanityConfig) => { + if (!sanityConfig || sanityConfig.enabled === false) { + return { ok: true }; + } + + const centerX = rect.x + rect.width * 0.5; + const centerY = rect.y + rect.height * 0.5; + const area = rect.width * rect.height; + const aspect = rect.width / Math.max(rect.height, 1e-6); + + const minCenterX = Number.isFinite(sanityConfig.minCenterX) ? sanityConfig.minCenterX : 0; + const maxCenterX = Number.isFinite(sanityConfig.maxCenterX) ? sanityConfig.maxCenterX : 1; + if (centerX < minCenterX || centerX > maxCenterX) { + return createProbeMiss('invalid-geometry', { + geometry: { metric: 'centerX', value: centerX, min: minCenterX, max: maxCenterX } + }); + } + + const minCenterY = Number.isFinite(sanityConfig.minCenterY) ? sanityConfig.minCenterY : 0; + const maxCenterY = Number.isFinite(sanityConfig.maxCenterY) ? sanityConfig.maxCenterY : 1; + if (centerY < minCenterY || centerY > maxCenterY) { + return createProbeMiss('invalid-geometry', { + geometry: { metric: 'centerY', value: centerY, min: minCenterY, max: maxCenterY } + }); + } + + const minArea = Number.isFinite(sanityConfig.minArea) ? sanityConfig.minArea : 0; + const maxArea = Number.isFinite(sanityConfig.maxArea) ? sanityConfig.maxArea : 1; + if (area < minArea || area > maxArea) { + return createProbeMiss('invalid-geometry', { + geometry: { metric: 'area', value: area, min: minArea, max: maxArea } + }); + } + + const minAspect = Number.isFinite(sanityConfig.minAspect) ? sanityConfig.minAspect : 0; + const maxAspect = Number.isFinite(sanityConfig.maxAspect) ? sanityConfig.maxAspect : Number.POSITIVE_INFINITY; + if (aspect < minAspect || aspect > maxAspect) { + return createProbeMiss('invalid-geometry', { + geometry: { metric: 'aspect', value: aspect, min: minAspect, max: maxAspect } + }); + } + + return { + ok: true, + geometry: { centerX, centerY, area, aspect } + }; +}; + +const detectNeuralRoi = async (file, neuralRoiConfig) => { + if (!neuralRoiConfig || !neuralRoiConfig.enabled || !neuralRoiConfig.endpoint) { + return createProbeMiss('disabled'); + } + if (!file || typeof fetch !== 'function' || typeof FormData === 'undefined') { + return createProbeMiss('unsupported-environment'); + } + + const formData = new FormData(); + formData.append('image', file, file.name || 'meter.jpg'); + + const timeoutMs = Number.isFinite(neuralRoiConfig.timeoutMs) ? neuralRoiConfig.timeoutMs : 8000; + const abortController = typeof AbortController !== 'undefined' ? new AbortController() : null; + const timeoutId = abortController ? setTimeout(() => abortController.abort(), timeoutMs) : null; + try { + const response = await fetch(neuralRoiConfig.endpoint, { + method: 'POST', + body: formData, + signal: abortController ? abortController.signal : undefined + }); + if (!response.ok) { + return createProbeMiss('http-error', { status: response.status }); + } + + let payload = null; + try { + payload = await response.json(); + } catch (error) { + return createProbeMiss('invalid-json'); + } + if (!payload || !payload.ok || !payload.bbox_norm) { + return createProbeMiss('no-detection', { + confidence: toFiniteNumber(payload && payload.confidence), + model: payload && payload.model ? payload.model : null + }); + } + + const rect = normalizeRect(payload.bbox_norm); + if (!rect) { + return createProbeMiss('invalid-bbox'); + } + + const confidence = toFiniteNumber(payload.confidence); + if (confidence === null) { + return createProbeMiss('invalid-confidence'); + } + const minConfidence = Number.isFinite(neuralRoiConfig.minConfidence) ? neuralRoiConfig.minConfidence : 0; + if (confidence < minConfidence) { + return createProbeMiss('low-confidence', { + confidence, + minConfidence + }); + } + + const sanity = evaluateRectSanity(rect, neuralRoiConfig.sanity); + if (!sanity.ok) { + return createProbeMiss(sanity.reason, { + confidence, + geometry: sanity.geometry + }); + } + + return { + ok: true, + rect, + confidence, + model: payload.model || 'unknown', + geometry: sanity.geometry || null + }; + } catch (error) { + if (error && error.name === 'AbortError') { + return createProbeMiss('timeout'); + } + return createProbeMiss('network-error'); + } finally { + if (timeoutId) { + clearTimeout(timeoutId); + } + } +}; + +export { detectNeuralRoi }; diff --git a/src/ocr/pipeline.js b/src/ocr/pipeline.js new file mode 100644 index 0000000..ea601a1 --- /dev/null +++ b/src/ocr/pipeline.js @@ -0,0 +1,1184 @@ +import { OCR_CONFIG } from './config.js'; +import { setOcrDebugHooks, startDebugSession, addDebugStage, commitDebugSession } from './debug-hooks.js'; +import { + loadImageBitmap, + drawImageToCanvas, + preprocessCanvas, + scaleCanvas, + cropCanvas, + splitIntoCells, + normalizeRectToCanvas, + drawOverlayCanvas, + normalizeAngle +} from './canvas-utils.js'; +import { buildDigitCandidates } from './alignment.js'; +import { getWorker, selectBestReading, readDigitsByCells } from './recognition.js'; +import { predictDigitCells } from './digit-classifier.js'; +import { detectNeuralRoi } from './neural-roi.js'; + +const resolveNeuralRoiRect = (canvas, roiDetection, roiConfig) => { + const rawRect = normalizeRectToCanvas(canvas, { + x: roiDetection.rect.x * canvas.width, + y: roiDetection.rect.y * canvas.height, + width: roiDetection.rect.width * canvas.width, + height: roiDetection.rect.height * canvas.height + }); + const expandX = Number.isFinite(roiConfig.expandX) ? roiConfig.expandX : 0; + const expandY = Number.isFinite(roiConfig.expandY) ? roiConfig.expandY : 0; + return normalizeRectToCanvas(canvas, { + x: rawRect.x - rawRect.width * expandX, + y: rawRect.y - rawRect.height * expandY, + width: rawRect.width * (1 + expandX * 2), + height: rawRect.height * (1 + expandY * 2) + }); +}; + +const addNeuralRoiDebugStages = (debugSession, baseCanvas, roiRect, roiDetection) => { + if (!debugSession) { + return; + } + const overlay = drawOverlayCanvas(baseCanvas, [ + { + x: roiRect.x, + y: roiRect.y, + width: roiRect.width, + height: roiRect.height, + label: `neural roi ${(roiDetection.confidence * 100).toFixed(0)}%`, + color: '#06b6d4' + } + ]); + addDebugStage(debugSession, '0. neural roi detection', overlay); +}; + +const addNeuralRoiMissStage = (debugSession, baseCanvas, probe) => { + if (!debugSession) { + return; + } + const reason = probe && probe.reason ? probe.reason : 'unknown'; + const geometry = probe && probe.geometry ? probe.geometry : null; + const geometrySuffix = geometry && geometry.metric + ? ` (${geometry.metric}=${Number.isFinite(geometry.value) ? geometry.value.toFixed(3) : 'n/a'})` + : ''; + const confidence = Number.isFinite(probe && probe.confidence) ? ` ${(probe.confidence * 100).toFixed(1)}%` : ''; + const overlay = drawOverlayCanvas(baseCanvas, [ + { + x: Math.round(baseCanvas.width * 0.02), + y: Math.round(baseCanvas.height * 0.02), + width: Math.round(baseCanvas.width * 0.96), + height: Math.round(baseCanvas.height * 0.96), + label: `neural roi miss: ${reason}${geometrySuffix}${confidence}`, + color: '#ef4444' + } + ]); + addDebugStage(debugSession, '0. neural roi detection', overlay); +}; + +const formatNeuralRoiMissReason = (probe) => { + const reason = probe && probe.reason ? probe.reason : 'unknown'; + const geometry = probe && probe.geometry ? probe.geometry : null; + if (geometry && geometry.metric) { + const value = Number.isFinite(geometry.value) ? geometry.value.toFixed(3) : 'n/a'; + return `${reason}:${geometry.metric}=${value}`; + } + return reason; +}; + +const clamp = (value, min, max) => Math.min(max, Math.max(min, value)); +const isEdgeSourceLabel = (sourceLabel) => ( + typeof sourceLabel === 'string' && sourceLabel.includes('-edge') +); + +const recordSelectionEvidence = ( + evidenceMap, + reading, + { sourceLabel = '', isTopPick = false } = {} +) => { + if (!reading || !reading.value) { + return; + } + + const value = String(reading.value).replace(/\D/g, ''); + if (!value) { + return; + } + + const score = Number.isFinite(reading.score) ? reading.score : 0; + const confidence = Number.isFinite(reading.confidence) ? reading.confidence : 0; + const existing = evidenceMap.get(value) || { + value, + hits: 0, + topHits: 0, + totalScore: 0, + bestScore: -1, + bestConfidence: 0, + sources: new Set(), + edgeHits: 0, + edgeTopHits: 0, + nonEdgeHits: 0, + nonEdgeTopHits: 0, + edgeSources: new Set(), + nonEdgeSources: new Set() + }; + + existing.hits += 1; + existing.totalScore += score; + if (isTopPick) { + existing.topHits += 1; + } + if (sourceLabel) { + existing.sources.add(sourceLabel); + if (isEdgeSourceLabel(sourceLabel)) { + existing.edgeHits += 1; + existing.edgeSources.add(sourceLabel); + if (isTopPick) { + existing.edgeTopHits += 1; + } + } else { + existing.nonEdgeHits += 1; + existing.nonEdgeSources.add(sourceLabel); + if (isTopPick) { + existing.nonEdgeTopHits += 1; + } + } + } + if (score > existing.bestScore) { + existing.bestScore = score; + } + if (confidence > existing.bestConfidence) { + existing.bestConfidence = confidence; + } + + evidenceMap.set(value, existing); +}; + +const rankSelectionEvidence = (evidenceMap) => { + return [...evidenceMap.values()] + .map((entry) => { + const averageScore = entry.hits ? entry.totalScore / entry.hits : 0; + const consensusBoost = clamp((entry.topHits - 1) * 0.12 + (entry.hits - entry.topHits) * 0.04, 0, 0.3); + const sourceSpreadBoost = clamp((entry.sources.size - 1) * 0.02, 0, 0.08); + const nonEdgeSupportBoost = clamp(entry.nonEdgeHits * 0.03 + entry.nonEdgeTopHits * 0.04, 0, 0.12); + const edgeOnlyPenalty = entry.edgeHits > 0 && entry.nonEdgeHits === 0 ? 0.07 : 0; + const preferredLengthBoost = entry.value.length === OCR_CONFIG.preferredDigits ? 0.05 : -0.08; + const leadingZeroPenalty = ( + entry.value.length === OCR_CONFIG.preferredDigits + && entry.value.startsWith('0') + ) ? 0.07 : 0; + const score = clamp( + entry.bestScore * 0.58 + + averageScore * 0.27 + + consensusBoost + + sourceSpreadBoost + + nonEdgeSupportBoost + + preferredLengthBoost + - edgeOnlyPenalty + - leadingZeroPenalty, + 0, + 0.99 + ); + + return { + ...entry, + averageScore, + score, + sourceCount: entry.sources.size, + edgeHits: entry.edgeHits, + edgeTopHits: entry.edgeTopHits, + nonEdgeHits: entry.nonEdgeHits, + nonEdgeTopHits: entry.nonEdgeTopHits, + edgeSourceCount: entry.edgeSources.size, + nonEdgeSourceCount: entry.nonEdgeSources.size + }; + }) + .sort((a, b) => b.score - a.score || b.topHits - a.topHits || b.hits - a.hits || b.bestScore - a.bestScore); +}; + +const buildSelectionSummary = (rankedEvidence, limit = 3) => { + return rankedEvidence.slice(0, limit).map((entry) => ({ + value: entry.value, + score: Number(entry.score.toFixed(3)), + bestScore: Number(entry.bestScore.toFixed(3)), + averageScore: Number(entry.averageScore.toFixed(3)), + hits: entry.hits, + topHits: entry.topHits, + sourceCount: entry.sourceCount + })); +}; + +const pushSelectionLog = (payload) => { + if (typeof window !== 'undefined') { + if (!Array.isArray(window.__jarvisOcrSelectionLogs)) { + window.__jarvisOcrSelectionLogs = []; + } + window.__jarvisOcrSelectionLogs.push(payload); + if (window.__jarvisOcrSelectionLogs.length > 300) { + window.__jarvisOcrSelectionLogs.shift(); + } + } + console.info('[OCR] selection', JSON.stringify(payload)); +}; + +const serializeCellConfidences = (confidences) => { + if (!Array.isArray(confidences)) { + return null; + } + return confidences.map((value) => { + if (!Number.isFinite(value)) { + return null; + } + return Number(value.toFixed(1)); + }); +}; + +const finalizeSelection = ({ debugLabel, roiUsed, bestResult, evidenceMap, branchUsed, rejectSummary = [] }) => { + const rankedEvidence = rankSelectionEvidence(evidenceMap); + const evidenceBest = rankedEvidence[0] || null; + const roiDeterministic = OCR_CONFIG.roiDeterministic || {}; + const classifierConfig = OCR_CONFIG.digitClassifier || {}; + const minWordPassHits = Number.isFinite(roiDeterministic.minWordPassHits) + ? Math.max(1, Math.round(roiDeterministic.minWordPassHits)) + : 2; + let finalResult = bestResult; + let finalRejectReason = null; + let finalRejectDetail = null; + + if (evidenceBest) { + const shouldPromoteEvidence = ( + !finalResult + || evidenceBest.score >= (finalResult.score ?? -1) - 0.03 + || evidenceBest.topHits >= 2 + ); + + if (shouldPromoteEvidence) { + const confidenceFromBest = finalResult && finalResult.value === evidenceBest.value + ? (finalResult.confidence ?? 0) + : 0; + const carryMetadata = finalResult && finalResult.value === evidenceBest.value + ? finalResult + : null; + finalResult = { + value: evidenceBest.value, + confidence: Math.max(evidenceBest.bestConfidence, confidenceFromBest), + areaRatio: finalResult && finalResult.value === evidenceBest.value + ? (finalResult.areaRatio ?? 0.28) + : 0.28, + score: evidenceBest.score, + branch: carryMetadata && carryMetadata.branch ? carryMetadata.branch : branchUsed, + method: carryMetadata && carryMetadata.method ? carryMetadata.method : null, + sourceLabel: carryMetadata && carryMetadata.sourceLabel ? carryMetadata.sourceLabel : null, + preprocessMode: carryMetadata && carryMetadata.preprocessMode ? carryMetadata.preprocessMode : null, + angle: carryMetadata && Number.isFinite(carryMetadata.angle) ? carryMetadata.angle : null, + cellDigits: carryMetadata && Array.isArray(carryMetadata.cellDigits) ? carryMetadata.cellDigits : null, + cellConfidences: carryMetadata ? serializeCellConfidences(carryMetadata.cellConfidences) : null + }; + } + } + + if (finalResult && finalResult.method === 'word-pass') { + const support = rankedEvidence.find((entry) => entry.value === finalResult.value) || null; + const confirmed = !!support && ( + support.hits >= minWordPassHits + || support.topHits >= minWordPassHits + ); + if (!confirmed) { + finalRejectReason = 'word-pass-unconfirmed-finalize'; + finalRejectDetail = { + stage: 'selection-finalize', + method: finalResult.method || null, + sourceLabel: finalResult.sourceLabel || null, + value: finalResult.value || null, + requiredHits: minWordPassHits, + supportHits: support ? support.hits : 0, + supportTopHits: support ? support.topHits : 0 + }; + finalResult = null; + } + } + + if (finalResult && isEdgeSourceLabel(finalResult.sourceLabel)) { + const support = rankedEvidence.find((entry) => entry.value === finalResult.value) || null; + const hasNonEdgeSupport = !!support && ( + support.nonEdgeHits >= 1 + || support.nonEdgeTopHits >= 1 + || support.nonEdgeSourceCount >= 1 + ); + const cellConfidences = Array.isArray(finalResult.cellConfidences) + ? finalResult.cellConfidences.filter((value) => Number.isFinite(value)) + : []; + const averageCellConfidence = cellConfidences.length + ? (cellConfidences.reduce((sum, value) => sum + value, 0) / cellConfidences.length) + : 0; + const minCellConfidence = cellConfidences.length + ? Math.min(...cellConfidences) + : 0; + const isClassifierFallback = String(finalResult.method || '').startsWith('digit-classifier-fallback'); + + if (isClassifierFallback) { + const fallbackEdgeMinAverageConfidence = Number.isFinite(classifierConfig.fallbackEdgeMinAverageConfidence) + ? clamp(classifierConfig.fallbackEdgeMinAverageConfidence, 0, 100) + : 65; + const fallbackEdgeMinCellConfidence = Number.isFinite(classifierConfig.fallbackEdgeMinCellConfidence) + ? clamp(classifierConfig.fallbackEdgeMinCellConfidence, 0, 100) + : 35; + const fallbackEdgeRequireNonEdgeSupport = classifierConfig.fallbackEdgeRequireNonEdgeSupport === true; + const hasClassifierCellEvidence = ( + cellConfidences.length >= OCR_CONFIG.digitCellCount + && averageCellConfidence >= fallbackEdgeMinAverageConfidence + && minCellConfidence >= fallbackEdgeMinCellConfidence + ); + const classifierEdgeAccepted = fallbackEdgeRequireNonEdgeSupport + ? hasNonEdgeSupport + : (hasNonEdgeSupport || hasClassifierCellEvidence); + if (!classifierEdgeAccepted) { + finalRejectReason = 'classifier-edge-gate-final-drop'; + finalRejectDetail = { + stage: 'selection-finalize', + method: finalResult.method || null, + sourceLabel: finalResult.sourceLabel || null, + value: finalResult.value || null, + hasNonEdgeSupport, + averageCellConfidence: Number(averageCellConfidence.toFixed(1)), + minCellConfidence: Number(minCellConfidence.toFixed(1)), + requiredAverageConfidence: fallbackEdgeMinAverageConfidence, + requiredMinConfidence: fallbackEdgeMinCellConfidence, + requireNonEdgeSupport: fallbackEdgeRequireNonEdgeSupport + }; + finalResult = null; + } + } else { + const hasStrongCellEvidence = ( + cellConfidences.length >= OCR_CONFIG.digitCellCount + && averageCellConfidence >= 90 + && minCellConfidence >= 82 + ); + if (!hasNonEdgeSupport && !hasStrongCellEvidence) { + finalRejectReason = 'edge-gate-final-drop'; + finalRejectDetail = { + stage: 'selection-finalize', + method: finalResult.method || null, + sourceLabel: finalResult.sourceLabel || null, + value: finalResult.value || null, + hasNonEdgeSupport, + averageCellConfidence: Number(averageCellConfidence.toFixed(1)), + minCellConfidence: Number(minCellConfidence.toFixed(1)) + }; + finalResult = null; + } + } + } + + if (finalRejectReason) { + prependRejectSummary(rejectSummary, finalRejectReason, finalRejectDetail || {}); + } + + pushSelectionLog({ + image: debugLabel, + roiUsed, + branchUsed, + rejectSummary, + finalRejectReason, + finalRejectDetail, + selected: finalResult ? { + value: finalResult.value, + score: Number((finalResult.score ?? 0).toFixed(3)), + confidence: Number((finalResult.confidence ?? 0).toFixed(1)), + branch: finalResult.branch || branchUsed, + method: finalResult.method || null, + sourceLabel: finalResult.sourceLabel || null, + preprocessMode: finalResult.preprocessMode || null, + angle: Number.isFinite(finalResult.angle) ? finalResult.angle : null, + cellDigits: Array.isArray(finalResult.cellDigits) ? finalResult.cellDigits : null, + cellConfidences: serializeCellConfidences(finalResult.cellConfidences) + } : null, + topCandidates: buildSelectionSummary(rankedEvidence, 3) + }); + + return finalResult; +}; + +const extractCandidateAngle = (label) => { + if (!label || typeof label !== 'string') { + return Number.NaN; + } + const tokens = label.split('-'); + for (const token of tokens) { + const parsed = Number.parseInt(token, 10); + if (!Number.isFinite(parsed)) { + continue; + } + const normalized = ((parsed % 360) + 360) % 360; + if (normalized % 90 === 0) { + return normalized; + } + } + return Number.NaN; +}; + +const addWinningCandidateDebugStage = (debugSession, candidates, finalSelection) => { + if (!debugSession || !Array.isArray(candidates) || !finalSelection || !finalSelection.sourceLabel) { + return; + } + const selectedCandidate = candidates.find((candidate) => ( + candidate + && candidate.label + && candidate.label === finalSelection.sourceLabel + && candidate.canvas + )); + if (!selectedCandidate) { + return; + } + const mode = typeof finalSelection.preprocessMode === 'string' ? finalSelection.preprocessMode : 'raw'; + const preview = mode !== 'raw' + ? preprocessCanvas(selectedCandidate.canvas, mode) + : selectedCandidate.canvas; + addDebugStage(debugSession, '6. OCR input candidate', preview); +}; + +const isPreferredLengthReading = (reading) => { + return !!(reading && reading.value && reading.value.length === OCR_CONFIG.preferredDigits); +}; + +const summarizeRejectMap = (rejectMap) => { + return [...rejectMap.values()] + .sort((a, b) => b.count - a.count) + .map((entry) => ({ + reason: entry.reason, + count: entry.count, + samples: entry.samples + })); +}; + +const prependRejectSummary = (rejectSummary, reason, detail = {}) => { + if (!Array.isArray(rejectSummary) || !reason) { + return; + } + const existingIndex = rejectSummary.findIndex((entry) => entry && entry.reason === reason); + if (existingIndex >= 0) { + const existing = rejectSummary[existingIndex]; + existing.count = Number.isFinite(existing.count) ? (existing.count + 1) : 1; + if (!Array.isArray(existing.samples)) { + existing.samples = []; + } + if (existing.samples.length < 3) { + existing.samples.push(detail); + } + if (existingIndex > 0) { + rejectSummary.splice(existingIndex, 1); + rejectSummary.unshift(existing); + } + return; + } + rejectSummary.unshift({ + reason, + count: 1, + samples: [detail] + }); +}; + +const evaluateCandidateBranch = async ({ + candidates, + worker, + setProgress, + useWordPass = true, + allowSparseScan = false, + scanCanvas = null +}) => { + const activeCandidates = Array.isArray(candidates) && candidates.length + ? candidates + : [{ canvas: scanCanvas, label: 'raw-fallback-roi' }]; + let bestResult = null; + const valueEvidence = new Map(); + const roiDeterministic = OCR_CONFIG.roiDeterministic || {}; + const configuredModes = Array.isArray(roiDeterministic.wordPassModes) + ? roiDeterministic.wordPassModes + : []; + const modes = (configuredModes.length ? configuredModes : ['raw']) + .filter((mode) => mode === 'soft' || mode === 'binary' || mode === 'raw'); + if (!modes.length) { + modes.push('raw'); + } + let pass = 0; + const expectedPasses = Math.max(1, activeCandidates.length * modes.length); + const branchLabel = 'roi'; + const geometryConfig = OCR_CONFIG.geometry || {}; + const minCandidateWidth = Number.isFinite(geometryConfig.minCandidateWidth) ? geometryConfig.minCandidateWidth : 120; + const minCandidateHeight = Number.isFinite(geometryConfig.minCandidateHeight) ? geometryConfig.minCandidateHeight : 28; + const minCandidateAspect = Number.isFinite(geometryConfig.minCandidateAspect) ? geometryConfig.minCandidateAspect : 0.12; + const maxCandidateAspect = Number.isFinite(geometryConfig.maxCandidateAspect) ? geometryConfig.maxCandidateAspect : 18; + const minCellWidth = Number.isFinite(geometryConfig.minCellWidth) ? geometryConfig.minCellWidth : 20; + const minCellHeight = Number.isFinite(geometryConfig.minCellHeight) ? geometryConfig.minCellHeight : 24; + const rejectMap = new Map(); + + const recordReject = (reason, detail = {}) => { + const key = reason || 'unknown'; + const existing = rejectMap.get(key) || { + reason: key, + count: 0, + samples: [] + }; + existing.count += 1; + if (existing.samples.length < 3) { + existing.samples.push(detail); + } + rejectMap.set(key, existing); + }; + + const hasValidCandidateGeometry = (candidate, stage) => { + if (!candidate || !candidate.canvas) { + recordReject('candidate-missing', { + stage, + sourceLabel: candidate && candidate.label ? candidate.label : null + }); + return false; + } + const width = candidate.canvas.width; + const height = candidate.canvas.height; + const aspect = width / Math.max(1, height); + if (width < minCandidateWidth || height < minCandidateHeight) { + recordReject('candidate-too-small', { + stage, + sourceLabel: candidate.label, + width, + height + }); + return false; + } + if (aspect < minCandidateAspect || aspect > maxCandidateAspect) { + recordReject('candidate-bad-aspect', { + stage, + sourceLabel: candidate.label, + width, + height, + aspect: Number(aspect.toFixed(3)) + }); + return false; + } + return true; + }; + + const applyReadingMetadata = (reading, candidate, method) => { + if (!reading) { + return null; + } + const candidateAngle = candidate && candidate.label ? extractCandidateAngle(candidate.label) : Number.NaN; + const orientationOffset = Number.isFinite(reading.orientation) ? normalizeAngle(reading.orientation) : 0; + const resolvedAngle = Number.isFinite(candidateAngle) + ? normalizeAngle(candidateAngle + orientationOffset) + : null; + return { + ...reading, + branch: branchLabel, + method, + sourceLabel: candidate && candidate.label ? candidate.label : null, + angle: Number.isFinite(resolvedAngle) ? resolvedAngle : null + }; + }; + + const recordCandidateReadings = (reading, sourceLabel) => { + if (!reading) { + return; + } + if (Array.isArray(reading.topCandidates) && reading.topCandidates.length) { + reading.topCandidates.forEach((entry, index) => { + recordSelectionEvidence(valueEvidence, entry, { + sourceLabel, + isTopPick: index === 0 + }); + }); + } else { + recordSelectionEvidence(valueEvidence, reading, { + sourceLabel, + isTopPick: true + }); + } + }; + + const hasValidCellGeometry = (cellCanvases, stage, extra = {}) => { + for (let i = 0; i < cellCanvases.length; i += 1) { + const cell = cellCanvases[i]; + if (!cell || cell.width < minCellWidth || cell.height < minCellHeight) { + recordReject('cell-too-small', { + stage, + index: i, + sourceLabel: extra.sourceLabel || null, + width: cell ? cell.width : 0, + height: cell ? cell.height : 0 + }); + return false; + } + } + return true; + }; + + const pickSingleDigit = (data) => { + const symbolDigits = (data && Array.isArray(data.symbols) ? data.symbols : []) + .map((item) => ({ + digit: (item && item.text ? String(item.text) : '').replace(/\D/g, '').slice(0, 1), + confidence: Number.isFinite(item && item.confidence) ? item.confidence : (Number.isFinite(data && data.confidence) ? data.confidence : 0) + })) + .filter((item) => item.digit); + const bestSymbol = symbolDigits.sort((a, b) => b.confidence - a.confidence)[0]; + if (bestSymbol) { + return bestSymbol; + } + const textDigits = (data && data.text ? String(data.text) : '').replace(/\D/g, ''); + if (textDigits) { + return { + digit: textDigits[0], + confidence: Number.isFinite(data && data.confidence) ? data.confidence : 0 + }; + } + return null; + }; + + const refineSingleCellWithTesseract = async ({ cellCanvases, index, sourceLabel }) => { + if (!Array.isArray(cellCanvases) || !cellCanvases[index]) { + return null; + } + const modes = ['binary', 'soft', 'raw']; + let best = null; + for (const mode of modes) { + const processed = mode === 'raw' + ? cellCanvases[index] + : preprocessCanvas(cellCanvases[index], mode); + const scaled = scaleCanvas(processed, OCR_CONFIG.minDigitWidth); + const { data } = await worker.recognize(scaled); + const picked = pickSingleDigit(data); + if (!picked) { + continue; + } + const confidence = Number.isFinite(picked.confidence) + ? clamp(picked.confidence, 0, 100) + : 0; + if (!best || confidence > best.confidence) { + best = { + digit: picked.digit, + confidence, + mode + }; + } + } + if (!best) { + recordReject('classifier-single-cell-refine-no-digit', { + stage: 'classifier-fallback', + sourceLabel, + index + }); + return null; + } + return best; + }; + + const verifyEdgeWordPassCandidate = async ({ candidate, mode, reading }) => { + if (!reading || !candidate || !candidate.label || mode !== 'raw' || !candidate.label.includes('-edge')) { + return { + reading, + method: 'word-pass' + }; + } + + const verified = await readDigitsByCells(worker, candidate.canvas, null, { + roiMode: true, + onReject: (detail) => { + recordReject('edge-word-pass-cell-reject', { + stage: 'word-pass', + sourceLabel: candidate.label, + mode, + reason: detail && detail.reason ? detail.reason : null + }); + } + }); + + if (!verified || !isPreferredLengthReading(verified)) { + recordReject('edge-word-pass-unverified', { + stage: 'word-pass', + sourceLabel: candidate.label, + mode, + value: reading.value || null + }); + return { + reading: null, + method: 'word-pass' + }; + } + + if (verified.value !== reading.value) { + const wordDigits = String(reading.value || '').replace(/\D/g, ''); + const cellDigits = String(verified.value || '').replace(/\D/g, ''); + const wordUniqueDigits = new Set(wordDigits.split('').filter(Boolean)).size; + const cellUniqueDigits = new Set(cellDigits.split('').filter(Boolean)).size; + if (cellUniqueDigits === 1 && wordUniqueDigits > 1) { + recordReject('edge-word-pass-cell-collapse', { + stage: 'word-pass', + sourceLabel: candidate.label, + mode, + wordPassValue: reading.value || null, + cellValue: verified.value || null + }); + return { + reading: { + ...reading, + preprocessMode: mode + }, + method: 'word-pass' + }; + } + recordReject('edge-word-pass-cell-mismatch', { + stage: 'word-pass', + sourceLabel: candidate.label, + mode, + wordPassValue: reading.value || null, + cellValue: verified.value || null + }); + return { + reading: { + ...verified, + preprocessMode: mode + }, + method: 'cell-verify' + }; + } + + return { + reading: { + ...reading, + preprocessMode: mode, + cellDigits: Array.isArray(verified.cellDigits) ? verified.cellDigits : (reading.cellDigits || null), + cellConfidences: Array.isArray(verified.cellConfidences) ? verified.cellConfidences : (reading.cellConfidences || null) + }, + method: 'word-pass' + }; + }; + + const rankClassifierFallbackCandidates = (rawCandidates) => { + if (!Array.isArray(rawCandidates) || !rawCandidates.length) { + return []; + } + const classifierConfig = OCR_CONFIG.digitClassifier || {}; + const fallbackPreferNonEdge = classifierConfig.fallbackPreferNonEdge !== false; + const fallbackTargetAspect = Number.isFinite(classifierConfig.fallbackTargetAspect) + ? Math.max(0.4, classifierConfig.fallbackTargetAspect) + : 2.6; + const minStripAspect = Number.isFinite(roiDeterministic.minStripAspect) ? roiDeterministic.minStripAspect : 1.45; + const maxStripAspect = Number.isFinite(roiDeterministic.maxStripAspect) ? roiDeterministic.maxStripAspect : 8.2; + + return rawCandidates + .filter((candidate) => hasValidCandidateGeometry(candidate, 'classifier-fallback')) + .map((candidate) => { + const width = candidate.canvas.width; + const height = candidate.canvas.height; + const aspect = width / Math.max(1, height); + const isEdge = isEdgeSourceLabel(candidate.label); + const angle = extractCandidateAngle(candidate.label); + const inStripRange = aspect >= minStripAspect && aspect <= maxStripAspect; + const aspectCloseness = 1 - Math.min(1, Math.abs(aspect - fallbackTargetAspect) / fallbackTargetAspect); + const expectedHeight = width / fallbackTargetAspect; + const heightCloseness = 1 - Math.min(1, Math.abs(height - expectedHeight) / Math.max(expectedHeight, 1)); + + let fallbackScore = 0; + fallbackScore += inStripRange ? 0.75 : -0.4; + fallbackScore += aspectCloseness * 0.5; + fallbackScore += heightCloseness * 0.2; + if (fallbackPreferNonEdge) { + fallbackScore += isEdge ? -0.35 : 0.35; + } + if (Number.isFinite(angle) && (angle === 90 || angle === 270)) { + fallbackScore += 0.1; + } + if (candidate.label === 'scan-roi') { + fallbackScore -= 0.05; + } + + return { + ...candidate, + fallbackScore, + fallbackAspect: aspect, + fallbackInStripRange: inStripRange + }; + }) + .sort((a, b) => ( + b.fallbackScore - a.fallbackScore + || ((isEdgeSourceLabel(a.label) ? 1 : 0) - (isEdgeSourceLabel(b.label) ? 1 : 0)) + || (b.canvas.width - a.canvas.width) + )); + }; + + const tryClassifierFallback = async () => { + if (bestResult) { + return null; + } + + const classifierConfig = OCR_CONFIG.digitClassifier || {}; + if (!classifierConfig.enabled) { + return null; + } + + const fallbackOnNoDigitsOnly = classifierConfig.fallbackOnNoDigitsOnly !== false; + const noDigitsRejects = rejectMap.get('ocr-no-digits'); + if (fallbackOnNoDigitsOnly && !(noDigitsRejects && noDigitsRejects.count > 0)) { + return null; + } + + const fallbackCandidates = [...activeCandidates]; + if (scanCanvas) { + fallbackCandidates.push({ canvas: scanCanvas, label: 'scan-roi' }); + } + + const rankedFallbackCandidates = rankClassifierFallbackCandidates(fallbackCandidates); + const fallbackCandidate = rankedFallbackCandidates[0] || null; + if (!fallbackCandidate) { + recordReject('classifier-no-candidate', { + stage: 'classifier-fallback' + }); + return null; + } + if (isEdgeSourceLabel(fallbackCandidate.label)) { + recordReject('classifier-fallback-edge-selected', { + stage: 'classifier-fallback', + sourceLabel: fallbackCandidate.label, + score: Number(fallbackCandidate.fallbackScore.toFixed(3)), + aspect: Number(fallbackCandidate.fallbackAspect.toFixed(3)), + nonEdgeAlternative: rankedFallbackCandidates.some((candidate) => !isEdgeSourceLabel(candidate.label)) + }); + } + + const overlap = Number.isFinite(roiDeterministic.cellOverlap) ? roiDeterministic.cellOverlap : 0.03; + const cellCanvases = splitIntoCells(fallbackCandidate.canvas, OCR_CONFIG.digitCellCount, overlap); + if (!hasValidCellGeometry(cellCanvases, 'classifier-fallback', { sourceLabel: fallbackCandidate.label })) { + return null; + } + + if (setProgress) { + setProgress('Fallback: classifier check...'); + } + const classifierProbe = await predictDigitCells(cellCanvases, classifierConfig); + if (!classifierProbe.ok) { + if (classifierProbe.reason !== 'disabled') { + recordReject('classifier-unavailable', { + stage: 'classifier-fallback', + sourceLabel: fallbackCandidate.label, + reason: classifierProbe.reason + }); + } + return null; + } + + const minAcceptedCells = Number.isFinite(classifierConfig.fallbackMinAcceptedCells) + ? Math.max(1, Math.min(OCR_CONFIG.digitCellCount, Math.round(classifierConfig.fallbackMinAcceptedCells))) + : OCR_CONFIG.digitCellCount; + const digits = classifierProbe.predictions.map((item) => (item && item.accepted ? item.digit : '')); + const rawCellConfidences = classifierProbe.predictions.map((item) => { + if (!item || !Number.isFinite(item.confidence)) { + return 0; + } + return clamp(item.confidence, 0, 1); + }); + const cellConfidences = classifierProbe.predictions.map((item) => { + if (!item || !item.accepted || !Number.isFinite(item.confidence)) { + return 0; + } + return clamp(item.confidence * 100, 0, 100); + }); + let acceptedCount = digits.filter(Boolean).length; + + const singleCellRefineEnabled = classifierConfig.singleCellRefine !== false; + const lowConfidenceThresholdRaw = Number.isFinite(classifierConfig.singleCellLowConfidence) + ? classifierConfig.singleCellLowConfidence + : ( + Number.isFinite(classifierConfig.minCellConfidence) + ? classifierConfig.minCellConfidence + 0.2 + : 0.55 + ); + const lowConfidenceThreshold = clamp(lowConfidenceThresholdRaw, 0, 1); + const singleCellRefineMinConfidence = Number.isFinite(classifierConfig.singleCellRefineMinConfidence) + ? clamp(classifierConfig.singleCellRefineMinConfidence, 0, 100) + : 42; + const singleCellRefineSwitchMargin = Number.isFinite(classifierConfig.singleCellRefineSwitchMargin) + ? Math.max(0, classifierConfig.singleCellRefineSwitchMargin) + : 4; + const lowConfidenceIndices = classifierProbe.predictions + .map((item, index) => { + const accepted = !!(item && item.accepted && item.digit); + const confidence = rawCellConfidences[index]; + return (!accepted || confidence < lowConfidenceThreshold) ? index : -1; + }) + .filter((index) => index >= 0); + let refinedCellIndex = null; + + if ( + singleCellRefineEnabled + && lowConfidenceIndices.length === 1 + && acceptedCount >= Math.max(0, minAcceptedCells - 1) + ) { + const targetIndex = lowConfidenceIndices[0]; + if (setProgress) { + setProgress('Fallback: refining one low-confidence section...'); + } + const refined = await refineSingleCellWithTesseract({ + cellCanvases, + index: targetIndex, + sourceLabel: fallbackCandidate.label + }); + if (refined && refined.digit) { + const previousDigit = digits[targetIndex] || ''; + const previousConfidence = cellConfidences[targetIndex] || 0; + const shouldApply = !previousDigit + ? refined.confidence >= singleCellRefineMinConfidence + : ( + refined.digit === previousDigit + || refined.confidence >= (previousConfidence + singleCellRefineSwitchMargin) + ); + if (shouldApply) { + digits[targetIndex] = refined.digit; + cellConfidences[targetIndex] = Math.max(previousConfidence, refined.confidence); + acceptedCount = digits.filter(Boolean).length; + refinedCellIndex = targetIndex; + } else { + recordReject('classifier-single-cell-refine-skipped', { + stage: 'classifier-fallback', + sourceLabel: fallbackCandidate.label, + index: targetIndex, + classifierDigit: previousDigit || null, + classifierConfidence: Number(previousConfidence.toFixed(1)), + refinedDigit: refined.digit, + refinedConfidence: Number(refined.confidence.toFixed(1)) + }); + } + } + } + + if (acceptedCount < minAcceptedCells) { + recordReject('classifier-insufficient-cell-digits', { + stage: 'classifier-fallback', + sourceLabel: fallbackCandidate.label, + accepted: acceptedCount, + required: minAcceptedCells + }); + return null; + } + + const value = digits.join(''); + if (!value || value.length !== OCR_CONFIG.preferredDigits) { + recordReject('classifier-non4-reading', { + stage: 'classifier-fallback', + sourceLabel: fallbackCandidate.label, + value + }); + return null; + } + + const confidenceSum = cellConfidences.reduce((sum, score) => sum + score, 0); + const averageConfidence = confidenceSum / Math.max(acceptedCount, 1); + const normalizedConfidence = clamp(averageConfidence / 100, 0, 1); + const score = clamp(0.42 + normalizedConfidence * 0.46, 0, 0.94); + + const fallbackReading = applyReadingMetadata({ + value, + confidence: averageConfidence, + areaRatio: 0.28, + score, + decoder: refinedCellIndex === null ? 'digit-classifier' : 'digit-classifier-single-cell-refine', + cellDigits: digits, + cellConfidences, + refinedCellIndex + }, fallbackCandidate, 'digit-classifier-fallback'); + if (!fallbackReading) { + return null; + } + recordCandidateReadings(fallbackReading, `${fallbackCandidate.label}:classifier`); + return fallbackReading; + }; + + if (useWordPass) { + await worker.setParameters({ + tessedit_pageseg_mode: Tesseract.PSM.SINGLE_WORD, + tessedit_char_whitelist: '0123456789', + classify_bln_numeric_mode: 1 + }); + for (const candidate of activeCandidates) { + if (!hasValidCandidateGeometry(candidate, 'word-pass')) { + continue; + } + for (const mode of modes) { + pass += 1; + if (setProgress) { + setProgress(`Analyzing meter (${pass}/${expectedPasses})`); + } + const processed = mode === 'raw' + ? candidate.canvas + : preprocessCanvas(candidate.canvas, mode); + const { data } = await worker.recognize(processed); + const candidateRawBest = selectBestReading(data, processed); + let candidateBest = candidateRawBest; + let candidateMethod = 'word-pass'; + if (!candidateRawBest) { + recordReject('ocr-no-digits', { + stage: 'word-pass', + sourceLabel: candidate.label, + mode + }); + } else if (!isPreferredLengthReading(candidateRawBest)) { + recordReject('ocr-non4-reading', { + stage: 'word-pass', + sourceLabel: candidate.label, + mode, + value: candidateRawBest.value || null + }); + candidateBest = null; + } + if (candidateBest) { + const verified = await verifyEdgeWordPassCandidate({ + candidate, + mode, + reading: { + ...candidateBest, + preprocessMode: mode + } + }); + candidateBest = verified.reading; + candidateMethod = verified.method; + } + candidateBest = applyReadingMetadata(candidateBest, candidate, candidateMethod); + if (candidateBest && (!bestResult || candidateBest.score > bestResult.score)) { + bestResult = candidateBest; + } + if (candidateBest) { + recordCandidateReadings(candidateBest, candidate.label); + } + } + } + } + + if (!bestResult && allowSparseScan && scanCanvas) { + if (setProgress) { + setProgress('Scanning full image...'); + } + await worker.setParameters({ + tessedit_pageseg_mode: Tesseract.PSM.SPARSE_TEXT, + tessedit_char_whitelist: '0123456789' + }); + const softened = preprocessCanvas(scanCanvas, 'soft'); + const { data } = await worker.recognize(softened); + const sparseRawCandidate = selectBestReading(data, softened); + let fullCandidate = sparseRawCandidate; + if (!sparseRawCandidate) { + recordReject('ocr-no-digits', { + stage: 'sparse-scan', + sourceLabel: 'scan-roi', + mode: 'soft' + }); + } else if (!isPreferredLengthReading(sparseRawCandidate)) { + recordReject('ocr-non4-reading', { + stage: 'sparse-scan', + sourceLabel: 'scan-roi', + mode: 'soft', + value: sparseRawCandidate.value || null + }); + fullCandidate = null; + } + fullCandidate = applyReadingMetadata(fullCandidate, { label: 'scan-roi' }, 'sparse-scan'); + if (fullCandidate) { + bestResult = fullCandidate; + recordCandidateReadings(fullCandidate, 'scan-roi'); + } + } + + if (!bestResult) { + const classifierFallback = await tryClassifierFallback(); + if (classifierFallback) { + bestResult = classifierFallback; + } + } + + return { + bestResult, + evidenceMap: valueEvidence, + rejectSummary: summarizeRejectMap(rejectMap) + }; +}; + +const runMeterOcr = async (file, setProgress) => { + const image = await loadImageBitmap(file); + const baseCanvas = drawImageToCanvas(image, OCR_CONFIG.maxDimension); + const debugLabel = file && file.name ? file.name : `manual-${Date.now()}`; + const debugSession = startDebugSession(debugLabel); + + try { + const neuralRoiConfig = OCR_CONFIG.neuralRoi || {}; + if (!neuralRoiConfig.enabled || !neuralRoiConfig.endpoint) { + const message = 'Neural ROI is disabled or misconfigured. Enter the measurement manually.'; + if (setProgress) { + setProgress(message); + } + throw new Error(message); + } + + if (setProgress) { + setProgress('Requesting neural ROI...'); + } + const roiProbe = await detectNeuralRoi(file, neuralRoiConfig); + if (!roiProbe.ok) { + addNeuralRoiMissStage(debugSession, baseCanvas, roiProbe); + const reason = formatNeuralRoiMissReason(roiProbe); + const message = `Neural ROI failed (${reason}). Enter the measurement manually.`; + if (setProgress) { + setProgress(message); + } + throw new Error(message); + } + + const roiRect = resolveNeuralRoiRect(baseCanvas, roiProbe, neuralRoiConfig); + addNeuralRoiDebugStages(debugSession, baseCanvas, roiRect, roiProbe); + const roiCrop = cropCanvas(baseCanvas, roiRect); + addDebugStage(debugSession, '0b. neural roi crop', roiCrop); + + const roiCandidates = roiCrop + ? buildDigitCandidates(roiCrop, debugSession, addDebugStage).map((candidate) => ({ + ...candidate, + label: `${candidate.label}-roi` + })) + : []; + + const worker = await getWorker(); + if (!roiCandidates.length) { + const message = 'Neural ROI crop did not produce OCR candidates. Enter the measurement manually.'; + if (setProgress) { + setProgress(message); + } + throw new Error(message); + } + + const roiBranch = await evaluateCandidateBranch({ + candidates: roiCandidates, + worker, + setProgress, + useWordPass: true, + allowSparseScan: true, + scanCanvas: roiCrop + }); + + const finalSelection = finalizeSelection({ + debugLabel, + roiUsed: true, + bestResult: roiBranch.bestResult, + evidenceMap: roiBranch.evidenceMap, + branchUsed: isPreferredLengthReading(roiBranch.bestResult) ? 'roi-accepted' : 'roi-uncertain', + rejectSummary: roiBranch.rejectSummary || [] + }); + addWinningCandidateDebugStage(debugSession, roiCandidates, finalSelection); + + if (setProgress) { + if (finalSelection && finalSelection.value) { + setProgress(`Neural ROI + OCR complete (${finalSelection.value}).`); + } else { + setProgress('Neural ROI found, OCR uncertain. Enter the measurement manually.'); + } + } + + return finalSelection; + } finally { + commitDebugSession(debugSession); + } +}; + +export { runMeterOcr, setOcrDebugHooks }; diff --git a/src/ocr/recognition.js b/src/ocr/recognition.js new file mode 100644 index 0000000..2f3fe45 --- /dev/null +++ b/src/ocr/recognition.js @@ -0,0 +1,829 @@ +import { OCR_CONFIG } from './config.js'; +import { + clamp, + preprocessCanvas, + scaleCanvas, + splitIntoCells, + cropCanvas, + tightenCropByInk, + rotateCanvas, + normalizeAngle +} from './canvas-utils.js'; +import { predictDigitCells } from './digit-classifier.js'; + +let ocrWorker = null; + +const getWorker = async () => { + if (ocrWorker) { + return ocrWorker; + } + + const worker = await Tesseract.createWorker('eng'); + if (worker.loadLanguage && worker.initialize) { + await worker.loadLanguage('eng'); + await worker.initialize('eng'); + } + if (worker.setParameters) { + await worker.setParameters({ + tessedit_char_whitelist: '0123456789', + tessedit_pageseg_mode: Tesseract.PSM.SINGLE_WORD, + classify_bln_numeric_mode: 1 + }); + } + + ocrWorker = worker; + return ocrWorker; +}; + +const buildCandidateScores = (data, canvas) => { + const candidates = []; + + const pushCandidate = (value, confidence, areaRatio) => { + if (!value) { + return; + } + candidates.push({ + value, + confidence: confidence ?? data.confidence ?? 0, + areaRatio: areaRatio ?? 0 + }); + }; + + const collectDigitSequence = (items) => { + if (!items) { + return null; + } + + const canvasArea = Math.max(1, canvas.width * canvas.height); + const digits = items + .filter((item) => item.text && /^\d$/.test(item.text)) + .map((item) => { + const box = item.bbox || item; + const x0 = Number.isFinite(box.x0) ? box.x0 : 0; + const y0 = Number.isFinite(box.y0) ? box.y0 : 0; + const x1 = Number.isFinite(box.x1) ? box.x1 : x0 + 1; + const y1 = Number.isFinite(box.y1) ? box.y1 : y0 + 1; + const width = Math.max(1, x1 - x0); + const height = Math.max(1, y1 - y0); + return { + digit: item.text, + x0, + x1: x0 + width, + centerY: y0 + height / 2, + width, + height, + areaRatio: (width * height) / canvasArea, + confidence: item.confidence ?? data.confidence ?? 0 + }; + }) + .sort((a, b) => a.x0 - b.x0); + + if (digits.length < OCR_CONFIG.preferredDigits) { + return null; + } + + const isNeighbor = (left, right) => { + const gap = right.x0 - left.x1; + const maxGap = Math.max(left.width, right.width) * 2.4; + const yDelta = Math.abs(right.centerY - left.centerY); + const maxYDelta = Math.max(left.height, right.height) * 1.2; + return gap <= maxGap && yDelta <= maxYDelta; + }; + + let bestSequence = null; + for (let i = 0; i <= digits.length - OCR_CONFIG.preferredDigits; i += 1) { + const sequence = digits.slice(i, i + OCR_CONFIG.preferredDigits); + const contiguous = sequence.every((digit, index) => index === 0 || isNeighbor(sequence[index - 1], digit)); + if (!contiguous) { + continue; + } + + const confidence = sequence.reduce((sum, item) => sum + item.confidence, 0) / sequence.length; + const areaRatio = sequence.reduce((sum, item) => sum + item.areaRatio, 0) / sequence.length; + const candidate = { + value: sequence.map((item) => item.digit).join(''), + confidence, + areaRatio + }; + + if ( + !bestSequence + || candidate.areaRatio > bestSequence.areaRatio + || (candidate.areaRatio === bestSequence.areaRatio && candidate.confidence > bestSequence.confidence) + ) { + bestSequence = candidate; + } + } + + return bestSequence; + }; + + const textMatches = (data.text || '').match(/\d+/g); + if (textMatches) { + textMatches.forEach((chunk) => { + if (chunk.length >= OCR_CONFIG.minDigits) { + pushCandidate(chunk, data.confidence, 0.15); + if (chunk.length > OCR_CONFIG.preferredDigits) { + pushCandidate(chunk.slice(0, OCR_CONFIG.preferredDigits), data.confidence, 0.15); + pushCandidate(chunk.slice(-OCR_CONFIG.preferredDigits), data.confidence, 0.15); + } + } + }); + } + + if (data.words) { + const wordSequence = collectDigitSequence(data.words); + if (wordSequence) { + pushCandidate(wordSequence.value, wordSequence.confidence, wordSequence.areaRatio); + } + data.words.forEach((word) => { + const digits = (word.text || '').replace(/\D/g, ''); + if (digits.length >= OCR_CONFIG.minDigits) { + const box = word.bbox || {}; + const area = (box.x1 - box.x0 || 0) * (box.y1 - box.y0 || 0); + const ratio = area / (canvas.width * canvas.height); + pushCandidate(digits, word.confidence, ratio); + if (digits.length > OCR_CONFIG.preferredDigits) { + pushCandidate(digits.slice(0, OCR_CONFIG.preferredDigits), word.confidence, ratio); + pushCandidate(digits.slice(-OCR_CONFIG.preferredDigits), word.confidence, ratio); + } + } + }); + } + + if (data.symbols) { + const symbolSequence = collectDigitSequence(data.symbols); + if (symbolSequence) { + pushCandidate(symbolSequence.value, symbolSequence.confidence, symbolSequence.areaRatio); + } + } + + return candidates; +}; + +const scoreCandidate = (candidate) => { + const length = candidate.value.length; + const lengthScore = length === OCR_CONFIG.preferredDigits ? 1 : length === 5 ? 0.75 : length === 3 ? 0.45 : 0.2; + const confidenceScore = clamp(candidate.confidence / 100, 0, 1); + const areaScore = clamp(candidate.areaRatio * 4, 0, 1); + return confidenceScore * 0.6 + lengthScore * 0.3 + areaScore * 0.1; +}; + +const rankReadings = (data, canvas, limit = 3) => { + const candidates = buildCandidateScores(data, canvas); + if (!candidates.length) { + return []; + } + + const valueBuckets = new Map(); + candidates.forEach((candidate) => { + const score = scoreCandidate(candidate); + const value = candidate.value; + if (!valueBuckets.has(value)) { + valueBuckets.set(value, { + value, + confidence: candidate.confidence, + areaRatio: candidate.areaRatio, + bestScore: score, + totalScore: score, + hits: 1 + }); + return; + } + + const existing = valueBuckets.get(value); + existing.hits += 1; + existing.totalScore += score; + if (score > existing.bestScore) { + existing.bestScore = score; + existing.confidence = candidate.confidence; + existing.areaRatio = candidate.areaRatio; + } + }); + + const ranked = [...valueBuckets.values()] + .map((entry) => { + const averageScore = entry.totalScore / entry.hits; + const consensusBoost = clamp((entry.hits - 1) * 0.08, 0, 0.24); + const preferredLengthBoost = entry.value.length === OCR_CONFIG.preferredDigits ? 0.05 : -0.05; + const leadingZeroPenalty = ( + entry.value.length === OCR_CONFIG.preferredDigits + && entry.value.startsWith('0') + ) ? 0.06 : 0; + const score = clamp( + entry.bestScore * 0.7 + averageScore * 0.25 + consensusBoost + preferredLengthBoost - leadingZeroPenalty, + 0, + 0.99 + ); + + return { + value: entry.value, + confidence: entry.confidence, + areaRatio: entry.areaRatio, + score, + bestScore: entry.bestScore, + averageScore, + hits: entry.hits + }; + }) + .sort((a, b) => b.score - a.score || b.hits - a.hits || b.bestScore - a.bestScore); + + if (!Number.isFinite(limit) || limit <= 0) { + return ranked; + } + return ranked.slice(0, limit); +}; + +const selectBestReading = (data, canvas) => { + const ranked = rankReadings(data, canvas, 3); + if (!ranked.length) { + return null; + } + return { + ...ranked[0], + topCandidates: ranked + }; +}; + +const readDigitsByCells = async (worker, source, setProgress, options = {}) => { + const geometry = OCR_CONFIG.geometry || {}; + const roiDeterministic = OCR_CONFIG.roiDeterministic || {}; + const minCandidateWidth = Number.isFinite(geometry.minCandidateWidth) ? geometry.minCandidateWidth : 120; + const minCandidateHeight = Number.isFinite(geometry.minCandidateHeight) ? geometry.minCandidateHeight : 28; + const minCandidateAspect = Number.isFinite(geometry.minCandidateAspect) ? geometry.minCandidateAspect : 0.12; + const maxCandidateAspect = Number.isFinite(geometry.maxCandidateAspect) ? geometry.maxCandidateAspect : 18; + const minCellWidth = Number.isFinite(geometry.minCellWidth) ? geometry.minCellWidth : 20; + const minCellHeight = Number.isFinite(geometry.minCellHeight) ? geometry.minCellHeight : 24; + + const emitReject = (reason, detail = {}) => { + if (typeof options.onReject === 'function') { + options.onReject({ reason, ...detail }); + } + }; + + const pickDigit = (data) => { + const symbolDigits = (data.symbols || []) + .map((item) => ({ + digit: (item.text || '').replace(/\D/g, '').slice(0, 1), + confidence: Number.isFinite(item.confidence) ? item.confidence : data.confidence ?? 0 + })) + .filter((item) => item.digit); + const bestSymbol = symbolDigits.sort((a, b) => b.confidence - a.confidence)[0]; + if (bestSymbol) { + return bestSymbol; + } + const textDigits = (data.text || '').replace(/\D/g, ''); + if (textDigits) { + return { + digit: textDigits[0], + confidence: Number.isFinite(data.confidence) ? data.confidence : 0 + }; + } + return null; + }; + + const hasValidCandidateGeometry = (canvas, context = {}) => { + if (!canvas) { + emitReject('candidate-missing', context); + return false; + } + const width = canvas.width; + const height = canvas.height; + const aspect = width / Math.max(1, height); + const mode = context && typeof context.mode === 'string' ? context.mode : ''; + const isRoiMode = mode.startsWith('roi-'); + if (width < minCandidateWidth || height < minCandidateHeight) { + emitReject('candidate-too-small', { width, height, ...context }); + return false; + } + if (aspect < minCandidateAspect || aspect > maxCandidateAspect) { + if (isRoiMode) { + const relaxedMinAspect = minCandidateAspect * 0.8; + const relaxedMaxAspect = maxCandidateAspect * 1.12; + if (aspect >= relaxedMinAspect && aspect <= relaxedMaxAspect) { + emitReject('candidate-bad-aspect-soft', { + width, + height, + aspect: Number(aspect.toFixed(3)), + minCandidateAspect, + maxCandidateAspect, + relaxedMinAspect: Number(relaxedMinAspect.toFixed(3)), + relaxedMaxAspect: Number(relaxedMaxAspect.toFixed(3)), + ...context + }); + return true; + } + } + emitReject('candidate-bad-aspect', { width, height, aspect: Number(aspect.toFixed(3)), ...context }); + return false; + } + return true; + }; + + const hasValidCellGeometry = (cellCanvases, context = {}) => { + for (let i = 0; i < cellCanvases.length; i += 1) { + const cell = cellCanvases[i]; + if (!cell || cell.width < minCellWidth || cell.height < minCellHeight) { + emitReject('cell-too-small', { + index: i, + width: cell ? cell.width : 0, + height: cell ? cell.height : 0, + ...context + }); + return false; + } + } + return true; + }; + + const resizeCanvasWidth = (canvas, targetWidth) => { + if (!canvas || !Number.isFinite(targetWidth) || targetWidth <= 0) { + return canvas; + } + if (canvas.width === Math.round(targetWidth)) { + return canvas; + } + const scale = targetWidth / Math.max(1, canvas.width); + const resized = document.createElement('canvas'); + resized.width = Math.max(1, Math.round(canvas.width * scale)); + resized.height = Math.max(1, Math.round(canvas.height * scale)); + const ctx = resized.getContext('2d'); + ctx.imageSmoothingEnabled = true; + ctx.drawImage(canvas, 0, 0, resized.width, resized.height); + return resized; + }; + + const rotateCanvasExpanded = (canvas, angle) => { + const normalized = normalizeAngle(angle); + if (!Number.isFinite(normalized) || normalized === 0) { + return canvas; + } + if (normalized % 90 === 0) { + return rotateCanvas(canvas, normalized); + } + const radians = (normalized * Math.PI) / 180; + const cos = Math.cos(radians); + const sin = Math.sin(radians); + const width = Math.max( + 1, + Math.round(Math.abs(canvas.width * cos) + Math.abs(canvas.height * sin)) + ); + const height = Math.max( + 1, + Math.round(Math.abs(canvas.width * sin) + Math.abs(canvas.height * cos)) + ); + const rotated = document.createElement('canvas'); + rotated.width = width; + rotated.height = height; + const ctx = rotated.getContext('2d'); + ctx.translate(width * 0.5, height * 0.5); + ctx.rotate(radians); + ctx.drawImage(canvas, -canvas.width * 0.5, -canvas.height * 0.5); + return rotated; + }; + + const buildDeskewAngles = () => { + const maxAngleRaw = Number.isFinite(roiDeterministic.deskewMaxAngle) + ? roiDeterministic.deskewMaxAngle + : 8; + const stepRaw = Number.isFinite(roiDeterministic.deskewStep) + ? roiDeterministic.deskewStep + : 2; + const maxAngle = Math.max(0, Math.min(20, Math.abs(maxAngleRaw))); + const step = Math.max(1, Math.min(10, Math.abs(stepRaw))); + const angles = [0]; + for (let delta = step; delta <= maxAngle; delta += step) { + const rounded = Number(delta.toFixed(3)); + angles.push(rounded); + angles.push(-rounded); + } + return angles; + }; + + const scoreDeskewCandidate = (sourceCanvas, tightenRatio, angle) => { + const rotated = angle === 0 ? sourceCanvas : rotateCanvasExpanded(sourceCanvas, angle); + const tightened = tightenCropByInk(rotated, tightenRatio); + if (!tightened) { + return null; + } + const aspect = tightened.width / Math.max(1, tightened.height); + const areaRatio = (tightened.width * tightened.height) / Math.max(1, rotated.width * rotated.height); + // Prefer wider strips; penalize rotations that shrink ink area below 14% of the bounding box. + const score = aspect - Math.max(0, 0.14 - areaRatio) * 3.5; + return { + canvas: tightened, + angle, + score + }; + }; + + const normalizeRoiOrientation = (sourceCanvas, tightenRatio) => { + const angles = buildDeskewAngles(); + let best = scoreDeskewCandidate(sourceCanvas, tightenRatio, 0); + if (!best) { + return { + canvas: sourceCanvas, + deskewAngle: 0 + }; + } + angles.forEach((angle) => { + if (angle === 0) { + return; + } + const candidate = scoreDeskewCandidate(sourceCanvas, tightenRatio, angle); + if (!candidate) { + return; + } + if (candidate.score > best.score + 0.02) { + best = candidate; + } + }); + return { + canvas: best.canvas, + deskewAngle: normalizeAngle(best.angle) + }; + }; + + const buildInkProjection = (canvas) => { + const ctx = canvas.getContext('2d', { willReadFrequently: true }); + const { width, height } = canvas; + const data = ctx.getImageData(0, 0, width, height).data; + const columns = new Array(width).fill(0); + const rows = new Array(height).fill(0); + + for (let y = 0; y < height; y += 1) { + for (let x = 0; x < width; x += 1) { + const idx = (y * width + x) * 4; + const lum = data[idx] * 0.2126 + data[idx + 1] * 0.7152 + data[idx + 2] * 0.0722; + const ink = 255 - lum; + columns[x] += ink; + rows[y] += ink; + } + } + + return { columns, rows }; + }; + + const findMaxInkWindowStart = (values, windowSize) => { + if (!Array.isArray(values) || !values.length) { + return 0; + } + const size = Math.min(values.length, Math.max(1, Math.round(windowSize))); + let windowSum = 0; + for (let i = 0; i < size; i += 1) { + windowSum += values[i]; + } + let bestSum = windowSum; + let bestStart = 0; + for (let i = size; i < values.length; i += 1) { + windowSum += values[i] - values[i - size]; + const start = i - size + 1; + if (windowSum > bestSum) { + bestSum = windowSum; + bestStart = start; + } + } + return bestStart; + }; + + const normalizeRoiStripCanvas = (canvas) => { + const tightenRatio = Number.isFinite(roiDeterministic.tightenInk) ? roiDeterministic.tightenInk : 0.08; + const minStripAspect = Number.isFinite(roiDeterministic.minStripAspect) ? roiDeterministic.minStripAspect : 1.8; + const maxStripAspect = Number.isFinite(roiDeterministic.maxStripAspect) ? roiDeterministic.maxStripAspect : 6.5; + const normalizeWidth = Number.isFinite(roiDeterministic.normalizeWidth) ? roiDeterministic.normalizeWidth : OCR_CONFIG.minScaleWidth; + const orientationNormalized = normalizeRoiOrientation(canvas, tightenRatio); + let normalized = orientationNormalized.canvas; + let majorAxisRotation = 0; + if (normalized && normalized.height > normalized.width) { + normalized = rotateCanvas(normalized, 90); + majorAxisRotation = 90; + } + if (!hasValidCandidateGeometry(normalized, { mode: 'roi-initial' })) { + return null; + } + + let aspect = normalized.width / Math.max(1, normalized.height); + if (aspect < minStripAspect) { + const targetHeight = Math.max(minCandidateHeight, Math.min(normalized.height, Math.round(normalized.width / minStripAspect))); + if (targetHeight < normalized.height) { + const { rows } = buildInkProjection(normalized); + const startY = findMaxInkWindowStart(rows, targetHeight); + normalized = cropCanvas(normalized, { + x: 0, + y: startY, + width: normalized.width, + height: targetHeight + }); + } + } else if (aspect > maxStripAspect) { + const targetWidth = Math.max(minCandidateWidth, Math.min(normalized.width, Math.round(normalized.height * maxStripAspect))); + if (targetWidth < normalized.width) { + const { columns } = buildInkProjection(normalized); + const startX = findMaxInkWindowStart(columns, targetWidth); + normalized = cropCanvas(normalized, { + x: startX, + y: 0, + width: targetWidth, + height: normalized.height + }); + } + } + + normalized = resizeCanvasWidth(normalized, normalizeWidth); + if (normalized && normalized.height > normalized.width) { + normalized = rotateCanvas(normalized, 90); + majorAxisRotation = normalizeAngle(majorAxisRotation + 90); + } + if (!hasValidCandidateGeometry(normalized, { mode: 'roi-normalized' })) { + return null; + } + + aspect = normalized.width / Math.max(1, normalized.height); + if (aspect < minStripAspect || aspect > maxStripAspect) { + const hardMinStripAspect = minStripAspect * 0.96; + const hardMaxStripAspect = maxStripAspect * 1.06; + const detail = { + width: normalized.width, + height: normalized.height, + aspect: Number(aspect.toFixed(3)), + minStripAspect, + maxStripAspect, + hardMinStripAspect: Number(hardMinStripAspect.toFixed(3)), + hardMaxStripAspect: Number(hardMaxStripAspect.toFixed(3)) + }; + if (aspect < hardMinStripAspect || aspect > hardMaxStripAspect) { + emitReject('roi-strip-aspect-out-of-range', detail); + return null; + } + emitReject('roi-strip-aspect-soft', detail); + } + + return { + canvas: normalized, + deskewAngle: Number.isFinite(orientationNormalized.deskewAngle) + ? orientationNormalized.deskewAngle + : 0, + majorAxisRotation + }; + }; + + const decodeCells = async (cellCanvases, metadata = {}, decodeOptions = {}) => { + const requireAllCells = !!decodeOptions.requireAllCells; + const minFound = requireAllCells ? cellCanvases.length : OCR_CONFIG.minDigits; + + const buildReading = (digits, cellConfidences, decoder, extra = {}) => { + let confidenceTotal = 0; + let found = 0; + for (let i = 0; i < digits.length; i += 1) { + if (!digits[i]) { + continue; + } + found += 1; + confidenceTotal += Number.isFinite(cellConfidences[i]) ? cellConfidences[i] : 0; + } + const value = digits.join(''); + if (found < minFound || !value) { + return { + ok: false, + found, + value + }; + } + return { + ok: true, + reading: { + value, + foundRatio: found / cellCanvases.length, + averageConfidence: confidenceTotal / Math.max(found, 1), + cellDigits: digits, + cellConfidences, + variantIndex: metadata.variantIndex, + overlap: metadata.overlap, + orientation: Number.isFinite(metadata.orientation) ? metadata.orientation : null, + deskewAngle: Number.isFinite(metadata.deskewAngle) ? metadata.deskewAngle : null, + decoder, + ...extra + } + }; + }; + + const digitClassifierConfig = OCR_CONFIG.digitClassifier || {}; + if (digitClassifierConfig.enabled) { + if (setProgress) { + setProgress('Refining digits (classifier)...'); + } + const classifierProbe = await predictDigitCells(cellCanvases, digitClassifierConfig); + if (classifierProbe.ok) { + const digits = classifierProbe.predictions.map((item) => (item && item.accepted ? item.digit : '')); + const cellConfidences = classifierProbe.predictions.map((item) => { + if (!item || !item.accepted || !Number.isFinite(item.confidence)) { + return 0; + } + return clamp(item.confidence * 100, 0, 100); + }); + const classifierReading = buildReading( + digits, + cellConfidences, + 'digit-classifier', + { classifierModel: classifierProbe.model || null } + ); + if (classifierReading.ok) { + return classifierReading.reading; + } + emitReject( + requireAllCells ? 'classifier-missing-cell-digit' : 'classifier-insufficient-cell-digits', + { + found: classifierReading.found, + required: minFound, + ...metadata + } + ); + } else if (classifierProbe.reason !== 'disabled') { + emitReject('classifier-unavailable', { + reason: classifierProbe.reason, + ...metadata + }); + } + } + + const digits = []; + const cellConfidences = []; + const cellDecodeModes = ['binary', 'soft', 'raw']; + + for (let i = 0; i < cellCanvases.length; i += 1) { + if (setProgress) { + setProgress(`Refining digits (${i + 1}/${cellCanvases.length})`); + } + let best = null; + for (const mode of cellDecodeModes) { + let cell = mode === 'raw' ? cellCanvases[i] : preprocessCanvas(cellCanvases[i], mode); + cell = scaleCanvas(cell, OCR_CONFIG.minDigitWidth); + const { data } = await worker.recognize(cell); + const picked = pickDigit(data); + if (picked && (!best || picked.confidence > best.confidence)) { + best = picked; + } + } + + if (best) { + digits.push(best.digit); + cellConfidences.push(best.confidence); + } else { + digits.push(''); + cellConfidences.push(0); + } + } + + const tesseractReading = buildReading(digits, cellConfidences, 'tesseract'); + if (!tesseractReading.ok) { + emitReject(requireAllCells ? 'missing-cell-digit' : 'insufficient-cell-digits', { + found: tesseractReading.found, + required: minFound, + ...metadata + }); + return null; + } + return tesseractReading.reading; + }; + + const finalizeReading = (reading) => { + if (!reading) { + return null; + } + if (options.roiMode && reading.value.length !== OCR_CONFIG.preferredDigits) { + emitReject('roi-non4-output', { value: reading.value }); + return null; + } + + const tunedConfidence = clamp(reading.averageConfidence + reading.foundRatio * 20, 0, 100); + const bonus = reading.foundRatio === 1 ? 0.2 : 0.04; + const score = scoreCandidate({ value: reading.value, confidence: tunedConfidence, areaRatio: 0.28 }) + bonus; + + return { + value: reading.value, + confidence: tunedConfidence, + areaRatio: 0.28, + score: clamp(score, 0, 0.99), + cellDigits: reading.cellDigits || [], + cellConfidences: reading.cellConfidences || [], + decoder: reading.decoder || 'tesseract', + classifierModel: reading.classifierModel || null, + variantIndex: Number.isFinite(reading.variantIndex) ? reading.variantIndex : null, + overlap: Number.isFinite(reading.overlap) ? reading.overlap : null, + orientation: Number.isFinite(reading.orientation) ? reading.orientation : null, + deskewAngle: Number.isFinite(reading.deskewAngle) ? reading.deskewAngle : null + }; + }; + + if (options.roiMode) { + const overlap = Number.isFinite(roiDeterministic.cellOverlap) ? roiDeterministic.cellOverlap : 0.03; + const requireAllCells = roiDeterministic.requireAllCells !== false; + const roiNormalized = normalizeRoiStripCanvas(source); + if (!roiNormalized || !roiNormalized.canvas) { + return null; + } + const baseOrientation = Number.isFinite(roiNormalized.majorAxisRotation) + ? normalizeAngle(roiNormalized.majorAxisRotation) + : 0; + const orientationVariants = [ + { orientation: baseOrientation, canvas: roiNormalized.canvas }, + { orientation: normalizeAngle(baseOrientation + 180), canvas: rotateCanvas(roiNormalized.canvas, 180) } + ]; + let best = null; + + for (let i = 0; i < orientationVariants.length; i += 1) { + const variant = orientationVariants[i]; + const cellCanvases = splitIntoCells(variant.canvas, OCR_CONFIG.digitCellCount, overlap); + if (!hasValidCellGeometry(cellCanvases, { + mode: 'roi-deterministic', + overlap, + orientation: variant.orientation + })) { + continue; + } + const reading = await decodeCells( + cellCanvases, + { + variantIndex: i, + overlap, + orientation: variant.orientation, + deskewAngle: roiNormalized.deskewAngle + }, + { requireAllCells } + ); + const finalized = finalizeReading(reading); + if (!finalized) { + continue; + } + if ( + !best + || finalized.score > best.score + || (finalized.score === best.score && finalized.confidence > best.confidence) + ) { + best = finalized; + } + } + + return best; + } + + const cropToFocus = (canvas, rect) => { + return cropCanvas(canvas, { + x: canvas.width * rect.x, + y: canvas.height * rect.y, + width: canvas.width * rect.width, + height: canvas.height * rect.height + }); + }; + + const focusRects = [ + { x: 0.0, y: 0.0, width: 0.62, height: 1.0 }, + { x: 0.02, y: 0.05, width: 0.6, height: 0.88 }, + { x: 0.05, y: 0.1, width: 0.58, height: 0.8 } + ]; + const variants = []; + focusRects.forEach((rect) => { + const focused = cropToFocus(source, rect); + variants.push(focused); + variants.push(tightenCropByInk(focused, 0.08)); + variants.push(tightenCropByInk(focused, 0.18)); + }); + let bestReading = null; + + for (let variantIndex = 0; variantIndex < variants.length; variantIndex += 1) { + const variant = variants[variantIndex]; + if (!hasValidCandidateGeometry(variant, { mode: 'fallback', variantIndex })) { + continue; + } + for (const overlap of [0.03, OCR_CONFIG.digitCellOverlap]) { + const cellCanvases = splitIntoCells(variant, OCR_CONFIG.digitCellCount, overlap); + if (!hasValidCellGeometry(cellCanvases, { mode: 'fallback', variantIndex, overlap })) { + continue; + } + const reading = await decodeCells( + cellCanvases, + { variantIndex, overlap }, + { requireAllCells: false } + ); + if (!reading) { + continue; + } + if ( + !bestReading + || reading.foundRatio > bestReading.foundRatio + || (reading.foundRatio === bestReading.foundRatio && reading.averageConfidence > bestReading.averageConfidence) + ) { + bestReading = reading; + } + } + } + + if (!bestReading) { + emitReject('fallback-no-reading'); + return null; + } + + return finalizeReading(bestReading); +}; + +export { getWorker, selectBestReading, readDigitsByCells }; diff --git a/src/ocr/strip-detection.js b/src/ocr/strip-detection.js new file mode 100644 index 0000000..b9f297e --- /dev/null +++ b/src/ocr/strip-detection.js @@ -0,0 +1,298 @@ +import { ALIGNMENT_CONFIG } from './config.js'; +import { + drawOverlayCanvas, + normalizeRectToCanvas, + preprocessCanvas, + clamp, + analyzeRegion +} from './canvas-utils.js'; + +const getStrictStripSearchBand = (canvas, circle) => { + const config = ALIGNMENT_CONFIG.stripSearchBand; + const bandCenterX = circle.cx + circle.radius * config.offsetXFromCenterRadius; + const bandCenterY = circle.cy + circle.radius * config.offsetYFromCenterRadius; + const bandWidth = circle.radius * config.widthFromRadius; + const bandHeight = circle.radius * config.heightFromRadius; + return normalizeRectToCanvas(canvas, { + x: bandCenterX - bandWidth * 0.5, + y: bandCenterY - bandHeight * 0.5, + width: bandWidth, + height: bandHeight + }); +}; + +const getDigitStripCandidatesInBand = (canvas, bandRect) => { + const templates = []; + const xOffsets = [0.0, 0.06, 0.12, 0.18, 0.24]; + const yOffsets = [0.0, 0.08, 0.16, 0.24]; + const widths = [0.5, 0.62, 0.74, 0.86]; + const heights = [0.18, 0.24, 0.3, 0.36]; + + xOffsets.forEach((x) => { + yOffsets.forEach((y) => { + widths.forEach((width) => { + heights.forEach((height) => { + if (x + width <= 0.98 && y + height <= 0.98) { + templates.push({ + orientation: 'horizontal', + rect: normalizeRectToCanvas(canvas, { + x: bandRect.x + x * bandRect.width, + y: bandRect.y + y * bandRect.height, + width: width * bandRect.width, + height: height * bandRect.height + }) + }); + } + }); + }); + }); + }); + + return templates; +}; + +const buildStripDecisionMaps = (faceCanvas) => { + const ctx = faceCanvas.getContext('2d', { willReadFrequently: true }); + const { width, height } = faceCanvas; + const sourceImageData = ctx.getImageData(0, 0, width, height); + const sourceData = sourceImageData.data; + + const binaryCanvas = preprocessCanvas(faceCanvas, 'binary'); + const binaryCtx = binaryCanvas.getContext('2d', { willReadFrequently: true }); + const binaryData = binaryCtx.getImageData(0, 0, width, height).data; + + const edgeCanvas = document.createElement('canvas'); + edgeCanvas.width = width; + edgeCanvas.height = height; + const edgeCtx = edgeCanvas.getContext('2d'); + const edgeImageData = edgeCtx.createImageData(width, height); + const edgeData = edgeImageData.data; + + const luminanceAt = (x, y) => { + const safeX = clamp(x, 0, width - 1); + const safeY = clamp(y, 0, height - 1); + const idx = (safeY * width + safeX) * 4; + return sourceData[idx] * 0.2126 + sourceData[idx + 1] * 0.7152 + sourceData[idx + 2] * 0.0722; + }; + + for (let y = 0; y < height; y += 1) { + for (let x = 0; x < width; x += 1) { + const gx = Math.abs(luminanceAt(x + 1, y) - luminanceAt(x - 1, y)); + const gy = Math.abs(luminanceAt(x, y + 1) - luminanceAt(x, y - 1)); + const edge = clamp(gx * 0.9 + gy * 0.35, 0, 255) | 0; + const idx = (y * width + x) * 4; + edgeData[idx] = edge; + edgeData[idx + 1] = edge; + edgeData[idx + 2] = edge; + edgeData[idx + 3] = 255; + } + } + edgeCtx.putImageData(edgeImageData, 0, 0); + + const combinedCanvas = document.createElement('canvas'); + combinedCanvas.width = width * 2 + 8; + combinedCanvas.height = height; + const combinedCtx = combinedCanvas.getContext('2d'); + combinedCtx.fillStyle = '#0f172a'; + combinedCtx.fillRect(0, 0, combinedCanvas.width, combinedCanvas.height); + combinedCtx.drawImage(binaryCanvas, 0, 0); + combinedCtx.drawImage(edgeCanvas, width + 8, 0); + + return { + sourceData, + binaryData, + edgeData, + width, + height, + binaryCanvas, + edgeCanvas, + combinedCanvas + }; +}; + +const measureVerticalStrokePeriodicity = (edgeData, width, height, rect) => { + const x0 = clamp(Math.floor(rect.x), 0, width - 1); + const y0 = clamp(Math.floor(rect.y + rect.height * 0.08), 0, height - 1); + const x1 = clamp(Math.ceil(rect.x + rect.width), x0 + 1, width); + const y1 = clamp(Math.ceil(rect.y + rect.height * 0.92), y0 + 1, height); + const profile = []; + const rows = Math.max(1, y1 - y0); + + for (let x = x0; x < x1; x += 1) { + let sum = 0; + for (let y = y0; y < y1; y += 1) { + const idx = (y * width + x) * 4; + sum += edgeData[idx] / 255; + } + profile.push(sum / rows); + } + + if (profile.length < 8) { + return 0; + } + + const smooth = profile.map((value, index) => { + const left = profile[Math.max(0, index - 1)]; + const right = profile[Math.min(profile.length - 1, index + 1)]; + return (left + value + right) / 3; + }); + const mean = smooth.reduce((sum, value) => sum + value, 0) / smooth.length; + const variance = smooth.reduce((sum, value) => sum + (value - mean) ** 2, 0) / smooth.length; + const std = Math.sqrt(variance); + const peakThreshold = mean + std * 0.55; + const highThreshold = mean + std * 0.24; + + let peaks = 0; + for (let i = 1; i < smooth.length - 1; i += 1) { + if (smooth[i] >= peakThreshold && smooth[i] >= smooth[i - 1] && smooth[i] >= smooth[i + 1]) { + peaks += 1; + } + } + + let transitions = 0; + let previousHigh = smooth[0] >= highThreshold; + for (let i = 1; i < smooth.length; i += 1) { + const currentHigh = smooth[i] >= highThreshold; + if (currentHigh !== previousHigh) { + transitions += 1; + previousHigh = currentHigh; + } + } + + const peakScore = clamp(peaks / Math.max(2, smooth.length * 0.11), 0, 1); + const transitionScore = clamp(transitions / Math.max(2, smooth.length * 0.26), 0, 1); + return clamp(peakScore * 0.65 + transitionScore * 0.35, 0, 1); +}; + +const evaluateStripGates = (stats, rect, circle, periodicity) => { + const gates = ALIGNMENT_CONFIG.stripGates; + const aspect = rect.width / Math.max(1, rect.height); + const centerX = rect.x + rect.width * 0.5; + const centerY = rect.y + rect.height * 0.5; + const distanceRatio = Math.hypot(centerX - circle.cx, centerY - circle.cy) / Math.max(1, circle.radius); + const checks = { + red: stats.redRatio <= gates.maxRedRatio, + aspect: aspect >= gates.minAspect && aspect <= gates.maxAspect, + distance: distanceRatio >= gates.minDistanceFromCenterRadius && distanceRatio <= gates.maxDistanceFromCenterRadius, + periodicity: periodicity >= gates.minVerticalPeriodicity + }; + + return { + checks, + passed: Object.values(checks).every(Boolean), + aspect, + distanceRatio + }; +}; + +const scoreDigitStripRect = (stats, gateEval, periodicity) => { + const gates = ALIGNMENT_CONFIG.stripGates; + const aspectScore = 1 - Math.min(1, Math.abs(gateEval.aspect - 2.2) / 1.8); + const distanceScore = 1 - Math.min( + 1, + Math.abs(gateEval.distanceRatio - gates.expectedDistanceFromCenterRadius) / gates.distanceTolerance + ); + const edgeScore = stats.edgeXRatio * 2.7 + stats.edgeYRatio * 0.9; + const darkScore = 1 - Math.min(1, Math.abs(stats.darkRatio - 0.22) / 0.2); + const brightnessScore = 1 - Math.min(1, Math.abs(stats.meanLum - 155) / 110); + + const gatePenalty = ( + (gateEval.checks.red ? 0 : -1.9) + + (gateEval.checks.aspect ? 0 : -1.3) + + (gateEval.checks.distance ? 0 : -1.4) + + (gateEval.checks.periodicity ? 0 : -2.1) + ); + + return ( + edgeScore * 1.8 + + darkScore * 1.15 + + brightnessScore * 0.95 + + aspectScore * 1.35 + + distanceScore * 1.25 + + periodicity * 2.3 + - stats.redRatio * 4.8 + + gatePenalty + ); +}; + +const detectDigitStripInBand = (faceCanvas, circle) => { + const decisionMaps = buildStripDecisionMaps(faceCanvas); + const searchBand = getStrictStripSearchBand(faceCanvas, circle); + const templates = getDigitStripCandidatesInBand(faceCanvas, searchBand); + + const scored = templates.map((template) => { + const rect = normalizeRectToCanvas(faceCanvas, template.rect); + const stats = analyzeRegion(decisionMaps.sourceData, decisionMaps.width, decisionMaps.height, rect); + const periodicity = measureVerticalStrokePeriodicity(decisionMaps.edgeData, decisionMaps.width, decisionMaps.height, rect); + const gateEval = evaluateStripGates(stats, rect, circle, periodicity); + const score = scoreDigitStripRect(stats, gateEval, periodicity); + return { + rect, + score, + orientation: template.orientation, + stats, + periodicity, + gateEval + }; + }).sort((a, b) => b.score - a.score); + + const topCandidates = scored.slice(0, ALIGNMENT_CONFIG.stripDebugTopK); + const bestAccepted = scored.find((candidate) => ( + candidate.gateEval.passed && candidate.score >= ALIGNMENT_CONFIG.stripGates.minAcceptedScore + )) || null; + + return { + searchBand, + topCandidates, + selected: bestAccepted, + usedFallback: !bestAccepted, + decisionMaps + }; +}; + +const buildStripTopKOverlay = (canvas, searchBand, topCandidates, selectedRect) => { + const shapes = [ + { + x: searchBand.x, + y: searchBand.y, + width: searchBand.width, + height: searchBand.height, + label: 'search-band', + color: '#f59e0b' + } + ]; + + topCandidates.forEach((candidate, index) => { + const passed = candidate.gateEval.passed && candidate.score >= ALIGNMENT_CONFIG.stripGates.minAcceptedScore; + const gateSuffix = [ + candidate.gateEval.checks.red ? 'r' : '!r', + candidate.gateEval.checks.aspect ? 'a' : '!a', + candidate.gateEval.checks.distance ? 'd' : '!d', + candidate.gateEval.checks.periodicity ? 'p' : '!p' + ].join(''); + + shapes.push({ + x: candidate.rect.x, + y: candidate.rect.y, + width: candidate.rect.width, + height: candidate.rect.height, + label: `#${index + 1} ${candidate.score.toFixed(2)} ${gateSuffix}`, + color: passed ? '#22c55e' : '#ef4444' + }); + }); + + if (selectedRect) { + shapes.push({ + x: selectedRect.x, + y: selectedRect.y, + width: selectedRect.width, + height: selectedRect.height, + label: 'accepted-strip', + color: '#16a34a' + }); + } + + return drawOverlayCanvas(canvas, shapes); +}; + +export { detectDigitStripInBand, buildStripTopKOverlay }; diff --git a/src/testset/run-test-set.js b/src/testset/run-test-set.js new file mode 100644 index 0000000..b1944ea --- /dev/null +++ b/src/testset/run-test-set.js @@ -0,0 +1,353 @@ +const parseCsv = (text) => { + return text + .trim() + .split('\n') + .slice(1) + .map((line) => line.split(',').map((cell) => cell.trim())) + .filter((parts) => parts.length >= 2 && parts[0] && parts[1]) + .map(([filename, value]) => ({ filename, value })); +}; + +const parseMeterValue = (value) => { + const digits = String(value || '').replace(/\D/g, ''); + if (!digits) { + return null; + } + const parsed = Number.parseInt(digits, 10); + return Number.isFinite(parsed) ? parsed : null; +}; + +const computeAbsoluteError = (expected, detected) => { + const expectedValue = parseMeterValue(expected); + const detectedValue = parseMeterValue(detected); + if (!Number.isFinite(expectedValue) || !Number.isFinite(detectedValue)) { + return null; + } + return Math.abs(expectedValue - detectedValue); +}; + +const getSelectionLogs = () => { + if (typeof window === 'undefined' || !Array.isArray(window.__jarvisOcrSelectionLogs)) { + return []; + } + return window.__jarvisOcrSelectionLogs; +}; + +const incrementHistogram = (histogram, key, amount = 1) => { + if (!(histogram instanceof Map)) { + return; + } + if (!Number.isFinite(amount) || amount <= 0) { + return; + } + const normalized = key ? String(key) : 'unknown'; + histogram.set(normalized, (histogram.get(normalized) || 0) + amount); +}; + +const histogramRows = (histogram) => { + if (!(histogram instanceof Map) || !histogram.size) { + return []; + } + return [...histogram.entries()] + .map(([reason, count]) => ({ reason, count })) + .sort((a, b) => b.count - a.count || a.reason.localeCompare(b.reason)); +}; + +const topRejectReason = (selectionLog) => { + if (!selectionLog || !Array.isArray(selectionLog.rejectSummary) || !selectionLog.rejectSummary.length) { + return null; + } + const top = selectionLog.rejectSummary[0]; + if (!top || !top.reason) { + return null; + } + return String(top.reason); +}; + +const renderHistogram = (title, rows, total) => { + const section = document.createElement('section'); + section.className = 'histogram'; + + const heading = document.createElement('h4'); + heading.textContent = `${title} (${total})`; + section.appendChild(heading); + + const table = document.createElement('table'); + const header = document.createElement('tr'); + ['Reason', 'Count', 'Share'].forEach((label) => { + const th = document.createElement('th'); + th.textContent = label; + header.appendChild(th); + }); + table.appendChild(header); + + rows.forEach((entry) => { + const row = document.createElement('tr'); + const share = total ? `${((entry.count / total) * 100).toFixed(1)}%` : '0.0%'; + [entry.reason, String(entry.count), share].forEach((value) => { + const cell = document.createElement('td'); + cell.textContent = value; + row.appendChild(cell); + }); + table.appendChild(row); + }); + + section.appendChild(table); + return section; +}; + +const renderTestResults = (resultsEl, results, total, histograms = {}) => { + if (!resultsEl) { + return; + } + + resultsEl.innerHTML = ''; + + const table = document.createElement('table'); + const header = document.createElement('tr'); + ['File', 'Expected', 'Detected', 'Absolute Error', 'Failure Reason', 'Result'].forEach((label) => { + const th = document.createElement('th'); + th.textContent = label; + header.appendChild(th); + }); + table.appendChild(header); + + results.forEach((result) => { + const row = document.createElement('tr'); + const statusClass = result.match ? 'pass' : 'fail'; + const errorDisplay = Number.isFinite(result.absoluteError) ? String(result.absoluteError) : '—'; + const failureDisplay = result.match ? '—' : (result.failureReason || 'unknown'); + [result.filename, result.expected, result.detected || '—', errorDisplay, failureDisplay].forEach((value) => { + const cell = document.createElement('td'); + cell.textContent = value; + row.appendChild(cell); + }); + const statusCell = document.createElement('td'); + statusCell.textContent = result.match ? 'Pass' : 'Fail'; + statusCell.className = statusClass; + row.appendChild(statusCell); + table.appendChild(row); + }); + + resultsEl.appendChild(table); + + const summary = document.createElement('p'); + summary.className = 'summary'; + const absoluteErrors = results + .map((result) => result.absoluteError) + .filter((value) => Number.isFinite(value)); + const mae = absoluteErrors.length + ? absoluteErrors.reduce((sum, value) => sum + value, 0) / absoluteErrors.length + : null; + const maeText = mae === null + ? 'n/a' + : mae.toFixed(2); + const exactMatchCount = results.filter((result) => result.match).length; + const noReadCount = results.filter((result) => !result.detected).length; + const exactMatchRate = total ? ((exactMatchCount / total) * 100).toFixed(1) : '0.0'; + const noReadRate = total ? ((noReadCount / total) * 100).toFixed(1) : '0.0'; + summary.textContent = `MAE: ${maeText} (${absoluteErrors.length}/${total} reads) | Exact Match: ${exactMatchCount}/${total} (${exactMatchRate}%) | No-read: ${noReadCount}/${total} (${noReadRate}%)`; + resultsEl.appendChild(summary); + + const failureRows = Array.isArray(histograms.failureReasons) ? histograms.failureReasons : []; + const rejectRows = Array.isArray(histograms.rejectReasons) ? histograms.rejectReasons : []; + const failureTotal = Number.isFinite(histograms.failureTotal) ? histograms.failureTotal : 0; + const rejectTotal = Number.isFinite(histograms.rejectTotal) ? histograms.rejectTotal : 0; + + if (failureRows.length) { + resultsEl.appendChild(renderHistogram('Failure reason histogram', failureRows, failureTotal)); + } + if (rejectRows.length) { + resultsEl.appendChild(renderHistogram('OCR reject histogram', rejectRows, rejectTotal)); + } +}; + +const createTestSetRunner = ({ + runButton, + statusEl, + resultsEl, + runMeterOcr +}) => { + const formatError = (error) => { + if (!error) { + return 'unknown error'; + } + if (typeof error === 'string') { + return error; + } + if (error instanceof Error) { + return error.message || error.name || 'error'; + } + return String(error); + }; + + const setStatus = (message) => { + if (statusEl) { + statusEl.textContent = message; + } + }; + + const runTestSet = async () => { + if (!runButton) { + return; + } + + runButton.disabled = true; + setStatus('Loading test set...'); + if (resultsEl) { + resultsEl.innerHTML = ''; + } + + try { + const csvResponse = await fetch('assets/meter_readings.csv', { cache: 'no-store' }); + if (!csvResponse.ok) { + throw new Error('Unable to load meter_readings.csv'); + } + const csvText = await csvResponse.text(); + const rows = parseCsv(csvText); + if (!rows.length) { + throw new Error('No test rows found.'); + } + + const results = []; + let correct = 0; + let rowErrors = 0; + let absoluteErrorSum = 0; + let absoluteErrorCount = 0; + let noReadCount = 0; + const failureReasonHistogram = new Map(); + const rejectReasonHistogram = new Map(); + + for (let i = 0; i < rows.length; i += 1) { + const row = rows[i]; + try { + setStatus(`Reading ${i + 1}/${rows.length}: ${row.filename}`); + const imageResponse = await fetch(`assets/${row.filename}`, { cache: 'no-store' }); + if (!imageResponse.ok) { + noReadCount += 1; + results.push({ + filename: row.filename, + expected: row.value, + detected: '', + match: false, + absoluteError: null, + failureReason: 'missing-image' + }); + incrementHistogram(failureReasonHistogram, 'missing-image'); + continue; + } + + const blob = await imageResponse.blob(); + const file = new File([blob], row.filename, { type: blob.type || 'image/jpeg' }); + const selectionLogCountBefore = getSelectionLogs().length; + const result = await runMeterOcr(file, (message) => { + setStatus(`Test ${i + 1}/${rows.length}: ${message}`); + }); + const selectionLogs = getSelectionLogs(); + const selectionLog = selectionLogs.length > selectionLogCountBefore + ? selectionLogs[selectionLogs.length - 1] + : null; + + const detected = result && result.value ? result.value : ''; + const match = detected === row.value; + const absoluteError = computeAbsoluteError(row.value, detected); + if (Number.isFinite(absoluteError)) { + absoluteErrorSum += absoluteError; + absoluteErrorCount += 1; + } + if (!detected) { + noReadCount += 1; + } + const rejectReason = topRejectReason(selectionLog); + const failureReason = match + ? '—' + : ( + (detected ? 'mismatch' : null) + || rejectReason + || (selectionLog && selectionLog.branchUsed ? `branch:${selectionLog.branchUsed}` : null) + || 'no-reading' + ); + if (match) { + correct += 1; + } else { + incrementHistogram(failureReasonHistogram, failureReason); + } + if (selectionLog && Array.isArray(selectionLog.rejectSummary)) { + selectionLog.rejectSummary.forEach((entry) => { + const reason = entry && entry.reason ? String(entry.reason) : 'unknown'; + const count = Number.isFinite(entry && entry.count) ? entry.count : 1; + incrementHistogram(rejectReasonHistogram, reason, count); + }); + } + + results.push({ + filename: row.filename, + expected: row.value, + detected, + match, + absoluteError, + failureReason + }); + } catch (rowError) { + rowErrors += 1; + console.error(`Test row failed for ${row.filename}`, rowError); + noReadCount += 1; + results.push({ + filename: row.filename, + expected: row.value, + detected: '', + match: false, + absoluteError: null, + failureReason: `error:${formatError(rowError)}` + }); + incrementHistogram(failureReasonHistogram, `error:${formatError(rowError)}`); + } + } + + const failureReasons = histogramRows(failureReasonHistogram); + const rejectReasons = histogramRows(rejectReasonHistogram); + const failureTotal = failureReasons.reduce((sum, entry) => sum + entry.count, 0); + const rejectTotal = rejectReasons.reduce((sum, entry) => sum + entry.count, 0); + const runHistogram = { + generatedAt: new Date().toISOString(), + totalRows: rows.length, + correct, + failed: rows.length - correct, + exactMatchRate: rows.length ? correct / rows.length : 0, + noReadCount, + noReadRate: rows.length ? noReadCount / rows.length : 0, + mae: absoluteErrorCount ? absoluteErrorSum / absoluteErrorCount : null, + maeRows: absoluteErrorCount, + maeSum: absoluteErrorSum, + failureReasons, + failureTotal, + rejectReasons, + rejectTotal + }; + if (typeof window !== 'undefined') { + window.__jarvisLastTestSetHistogram = runHistogram; + } + + const maeText = absoluteErrorCount ? (absoluteErrorSum / absoluteErrorCount).toFixed(2) : 'n/a'; + const exactMatchRateText = rows.length ? ((correct / rows.length) * 100).toFixed(1) : '0.0'; + const noReadRateText = rows.length ? ((noReadCount / rows.length) * 100).toFixed(1) : '0.0'; + setStatus( + rowErrors + ? `Done. MAE ${maeText}. Exact Match ${correct}/${rows.length} (${exactMatchRateText}%). No-read ${noReadCount}/${rows.length} (${noReadRateText}%). ${rowErrors} row error(s); see console.` + : `Done. MAE ${maeText}. Exact Match ${correct}/${rows.length} (${exactMatchRateText}%). No-read ${noReadCount}/${rows.length} (${noReadRateText}%).` + ); + renderTestResults(resultsEl, results, rows.length, runHistogram); + } catch (error) { + console.error(error); + setStatus(`Test run failed: ${formatError(error)}.`); + } finally { + runButton.disabled = false; + } + }; + + return { + runTestSet + }; +}; + +export { createTestSetRunner }; diff --git a/styles.css b/styles.css index ca06670..b39e35a 100644 --- a/styles.css +++ b/styles.css @@ -189,6 +189,75 @@ a:hover { animation-delay: 0.24s; } +.qa-card { + border-style: dashed; +} + +.debug-panel { + margin-top: 1.2rem; + border-top: 1px dashed var(--border); + padding-top: 1rem; +} + +.debug-toggle { + display: inline-flex; + align-items: center; + gap: 0.45rem; + color: var(--muted-strong); + font-size: 0.9rem; + font-weight: 600; +} + +.debug-results { + margin-top: 0.9rem; + display: grid; + gap: 0.9rem; +} + +.debug-session { + border: 1px solid var(--border); + border-radius: 10px; + background: var(--surface-muted); + padding: 0.75rem; +} + +.debug-session-title { + font-size: 0.82rem; + font-weight: 700; + letter-spacing: 0.02em; + color: var(--muted-strong); + margin-bottom: 0.5rem; +} + +.debug-stage-grid { + display: grid; + gap: 0.65rem; + grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); +} + +.debug-stage { + background: var(--surface); + border: 1px solid var(--border); + border-radius: 9px; + padding: 0.5rem; + display: grid; + gap: 0.45rem; +} + +.debug-stage img { + width: 100%; + height: auto; + border-radius: 7px; + border: 1px solid var(--border); + background: #fff; +} + +.debug-stage-name { + font-size: 0.75rem; + font-weight: 600; + color: var(--muted-strong); +} + .card-head { display: grid; gap: 0.4rem; @@ -200,6 +269,72 @@ a:hover { font-size: 0.95rem; } +.test-results { + margin-top: 1rem; +} + +.test-results table { + width: 100%; + border-collapse: collapse; + font-size: 0.9rem; +} + +.test-results th, +.test-results td { + text-align: left; + padding: 0.45rem 0.6rem; + border-bottom: 1px solid var(--border); +} + +.test-results th { + color: var(--muted-strong); + font-weight: 600; +} + +.test-results .pass { + color: #15803d; + font-weight: 600; +} + +.test-results .fail { + color: #b91c1c; + font-weight: 600; +} + +.test-results .summary { + margin-top: 0.8rem; + font-weight: 600; + color: var(--muted-strong); +} + +.test-results .histogram { + margin-top: 0.9rem; + border: 1px solid var(--border); + border-radius: 10px; + background: var(--surface-muted); + overflow: hidden; +} + +.test-results .histogram h4 { + font-size: 0.9rem; + font-weight: 700; + color: var(--muted-strong); + padding: 0.65rem 0.75rem 0.45rem; +} + +.test-results .histogram table { + width: 100%; + border-collapse: collapse; + font-size: 0.84rem; +} + +.test-results .histogram th, +.test-results .histogram td { + padding: 0.35rem 0.75rem; + border-bottom: none; + border-top: 1px solid var(--border); +} + .module-grid { display: grid; gap: 1rem; diff --git a/tests/e2e/neural-roi.spec.js b/tests/e2e/neural-roi.spec.js new file mode 100644 index 0000000..4cd7c29 --- /dev/null +++ b/tests/e2e/neural-roi.spec.js @@ -0,0 +1,340 @@ +const path = require('path'); +const { test, expect } = require('@playwright/test'); + +const METER_IMAGE_PATH = path.resolve(__dirname, '..', '..', 'assets', 'meter_02142026.JPEG'); +const TESSERACT_CDN_URL = 'https://cdn.jsdelivr.net/npm/tesseract.js@5/dist/tesseract.min.js'; + +const noDetectionPayload = JSON.stringify({ + ok: false, + bbox_norm: null, + confidence: 0, + class_id: null, + class_name: null, + model: 'mock-roi' +}); + +const successPayload = JSON.stringify({ + ok: true, + bbox_norm: { + x: 0.3, + y: 0.36, + width: 0.2, + height: 0.12 + }, + confidence: 0.9, + class_id: 0, + class_name: 'digit_window', + model: 'mock-roi' +}); + +const tallRoiPayload = JSON.stringify({ + ok: true, + bbox_norm: { + x: 0.42, + y: 0.43, + width: 0.08, + height: 0.14 + }, + confidence: 0.9, + class_id: 0, + class_name: 'digit_window', + model: 'mock-roi' +}); + +const buildFailIfUsedTesseractStub = () => ` +(() => { + window.__jarvisCreateWorkerCalls = 0; + window.__jarvisRecognizeCalls = 0; + window.Tesseract = { + PSM: { SINGLE_WORD: 8, SPARSE_TEXT: 11, SINGLE_CHAR: 10 }, + createWorker: async () => { + window.__jarvisCreateWorkerCalls += 1; + throw new Error('Tesseract worker should not run after neural ROI failure.'); + } + }; +})(); +`; + +const buildSuccessTesseractStub = (digits) => ` +(() => { + const sequence = ${JSON.stringify(digits)}; + const joined = sequence.join(''); + window.__jarvisCreateWorkerCalls = 0; + window.__jarvisRecognizeCalls = 0; + window.Tesseract = { + PSM: { SINGLE_WORD: 8, SPARSE_TEXT: 11, SINGLE_CHAR: 10 }, + createWorker: async () => { + window.__jarvisCreateWorkerCalls += 1; + return { + loadLanguage: async () => {}, + initialize: async () => {}, + setParameters: async () => {}, + recognize: async () => { + window.__jarvisRecognizeCalls += 1; + return { + data: { + text: joined || '0', + confidence: 96, + symbols: sequence.map((digit) => ({ text: digit, confidence: 96 })) + } + }; + } + }; + } + }; +})(); +`; + +const buildSingleHitWordPassStub = (digits) => ` +(() => { + const sequence = ${JSON.stringify(digits)}; + const joined = sequence.join(''); + window.__jarvisCreateWorkerCalls = 0; + window.__jarvisRecognizeCalls = 0; + let currentPsm = 8; + let servedWordPass = false; + window.Tesseract = { + PSM: { SINGLE_WORD: 8, SPARSE_TEXT: 11, SINGLE_CHAR: 10 }, + createWorker: async () => { + window.__jarvisCreateWorkerCalls += 1; + return { + loadLanguage: async () => {}, + initialize: async () => {}, + setParameters: async (params = {}) => { + if (Number.isFinite(params.tessedit_pageseg_mode)) { + currentPsm = params.tessedit_pageseg_mode; + } + }, + recognize: async () => { + window.__jarvisRecognizeCalls += 1; + if (currentPsm === 8 && !servedWordPass) { + servedWordPass = true; + return { + data: { + text: joined || '0', + confidence: 97, + symbols: sequence.map((digit) => ({ text: digit, confidence: 97 })) + } + }; + } + return { + data: { + text: '', + confidence: 0, + symbols: [] + } + }; + } + }; + } + }; +})(); +`; + +const installTesseractStub = async (page, scriptBody) => { + await page.route(TESSERACT_CDN_URL, async (route) => { + await route.fulfill({ + status: 200, + contentType: 'application/javascript', + body: scriptBody, + headers: { + 'access-control-allow-origin': '*' + } + }); + }); +}; + +const openAppAndUploadImage = async (page) => { + await page.goto('/'); + await page.setInputFiles('#photo-input', METER_IMAGE_PATH); +}; + +const waitForDebugStages = async (page, stageNames) => { + await page.waitForFunction((names) => { + const session = document.querySelector('.debug-session'); + if (!session) { + return false; + } + const cards = [...session.querySelectorAll('.debug-stage')]; + return names.every((name) => { + const card = cards.find((item) => { + const caption = item.querySelector('.debug-stage-name'); + return (caption && caption.textContent && caption.textContent.trim()) === name; + }); + if (!card) { + return false; + } + const image = card.querySelector('img'); + return !!(image && image.naturalWidth > 0 && image.naturalHeight > 0); + }); + }, stageNames); +}; + +const fulfillNoDetection = async (route) => { + await route.fulfill({ + status: 200, + contentType: 'application/json', + headers: { + 'access-control-allow-origin': '*' + }, + body: noDetectionPayload + }); +}; + +test('asks for manual input when neural ROI returns no detection', async ({ page }) => { + await installTesseractStub(page, buildFailIfUsedTesseractStub()); + await page.route('**/roi/detect', fulfillNoDetection); + await openAppAndUploadImage(page); + + await page.getByRole('button', { name: 'Read meter' }).click(); + + const status = page.locator('#ocr-status'); + await expect(status).toContainText('Neural ROI failed (no-detection). Enter the measurement manually.'); + await expect(page.locator('#reading-input')).toHaveValue(''); + + const workerCalls = await page.evaluate(() => window.__jarvisCreateWorkerCalls); + expect(workerCalls).toBe(0); +}); + +test('does not timeout on a 4.5s neural ROI response', async ({ page }) => { + await installTesseractStub(page, buildFailIfUsedTesseractStub()); + await page.route('**/roi/detect', async (route) => { + await new Promise((resolve) => setTimeout(resolve, 4500)); + await fulfillNoDetection(route); + }); + await openAppAndUploadImage(page); + + await page.getByRole('button', { name: 'Read meter' }).click(); + + const status = page.locator('#ocr-status'); + await expect(status).toContainText('Neural ROI failed (no-detection). Enter the measurement manually.', { + timeout: 20_000 + }); + await expect(status).not.toContainText('timeout'); +}); + +test('asks for manual input when neural ROI endpoint is unreachable', async ({ page }) => { + await installTesseractStub(page, buildFailIfUsedTesseractStub()); + await page.route('**/roi/detect', async (route) => { + await route.abort('failed'); + }); + await openAppAndUploadImage(page); + + await page.getByRole('button', { name: 'Read meter' }).click(); + + await expect(page.locator('#ocr-status')).toContainText( + 'Neural ROI failed (network-error). Enter the measurement manually.' + ); +}); + +test('completes with a detected reading when neural ROI succeeds', async ({ page }) => { + await installTesseractStub(page, buildSuccessTesseractStub(['2', '3', '1', '1'])); + await page.route('**/roi/detect', async (route) => { + await route.fulfill({ + status: 200, + contentType: 'application/json', + headers: { + 'access-control-allow-origin': '*' + }, + body: successPayload + }); + }); + await openAppAndUploadImage(page); + + await page.getByRole('button', { name: 'Read meter' }).click(); + + await expect(page.locator('#ocr-status')).toContainText('Reading detected: 2311. Review if needed.'); + await expect(page.locator('#reading-input')).toHaveValue('2311'); + + const workerCalls = await page.evaluate(() => window.__jarvisCreateWorkerCalls); + expect(workerCalls).toBeGreaterThan(0); +}); + +test('rejects an isolated edge word-pass hit in strip-only mode', async ({ page }) => { + await installTesseractStub(page, buildSingleHitWordPassStub(['8', '5', '8', '8'])); + await page.route('**/roi/detect', async (route) => { + await route.fulfill({ + status: 200, + contentType: 'application/json', + headers: { + 'access-control-allow-origin': '*' + }, + body: successPayload + }); + }); + await openAppAndUploadImage(page); + + await page.getByRole('button', { name: 'Read meter' }).click(); + + await expect(page.locator('#ocr-status')).toContainText('No clear reading detected. Enter it manually.'); + await expect(page.locator('#reading-input')).toHaveValue(''); +}); + +test('keeps ROI debug crop geometry stable for narrow neural ROI boxes', async ({ page }) => { + await installTesseractStub(page, buildSuccessTesseractStub(['2', '3', '1', '2'])); + await page.route('**/roi/detect', async (route) => { + await route.fulfill({ + status: 200, + contentType: 'application/json', + headers: { + 'access-control-allow-origin': '*' + }, + body: tallRoiPayload + }); + }); + await openAppAndUploadImage(page); + + await page.getByRole('button', { name: 'Read meter' }).click(); + + await expect(page.locator('#ocr-status')).toContainText( + /Reading detected|No clear reading detected|Enter it manually|Enter the measurement manually/, + { timeout: 20_000 } + ); + await waitForDebugStages(page, ['0b. neural roi crop', '5. detected strip crop', '6. OCR input candidate']); + + const dimensions = await page.evaluate(() => { + const session = document.querySelector('.debug-session'); + if (!session) { + return null; + } + const cards = [...session.querySelectorAll('.debug-stage')]; + const findStage = (name) => { + const card = cards.find((item) => { + const caption = item.querySelector('.debug-stage-name'); + return (caption && caption.textContent && caption.textContent.trim()) === name; + }); + if (!card) { + return null; + } + const image = card.querySelector('img'); + if (!image) { + return null; + } + return { + width: image.naturalWidth, + height: image.naturalHeight + }; + }; + return { + roi: findStage('0b. neural roi crop'), + strip: findStage('5. detected strip crop'), + ocr: findStage('6. OCR input candidate') + }; + }); + + expect(dimensions).not.toBeNull(); + expect(dimensions.roi).not.toBeNull(); + expect(dimensions.strip).not.toBeNull(); + expect(dimensions.ocr).not.toBeNull(); + + const roiArea = dimensions.roi.width * dimensions.roi.height; + const stripArea = dimensions.strip.width * dimensions.strip.height; + const roiMaxDim = Math.max(dimensions.roi.width, dimensions.roi.height); + const stripMaxDim = Math.max(dimensions.strip.width, dimensions.strip.height); + + expect(stripArea).toBeGreaterThanOrEqual(Math.floor(roiArea * 0.12)); + expect(stripArea).toBeLessThanOrEqual(Math.ceil(roiArea * 0.95)); + expect(stripMaxDim).toBeGreaterThanOrEqual(Math.floor(roiMaxDim * 0.55)); + expect(Math.min(dimensions.strip.width, dimensions.strip.height)).toBeGreaterThanOrEqual(24); + expect(dimensions.ocr.width).toBeGreaterThanOrEqual(dimensions.strip.width); +});